1 /*
   2  * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.jemmy.swt;
  24 
  25 import java.util.ArrayList;
  26 import java.util.Arrays;
  27 import java.util.List;
  28 
  29 import org.eclipse.swt.widgets.Control;
  30 import org.eclipse.swt.widgets.Display;
  31 import org.eclipse.swt.widgets.MenuItem;
  32 import org.eclipse.swt.widgets.Shell;
  33 import org.jemmy.JemmyException;
  34 import org.jemmy.control.Wrap;
  35 import org.jemmy.env.Timeout;
  36 import org.jemmy.interfaces.Keyboard.KeyboardButton;
  37 import org.jemmy.interfaces.Keyboard.KeyboardButtons;
  38 import org.jemmy.interfaces.MenuSelectable;
  39 import org.jemmy.lookup.LookupCriteria;
  40 import org.jemmy.resources.StringComparePolicy;
  41 import org.jemmy.swt.lookup.ByTextItem;
  42 import org.jemmy.timing.State;
  43 
  44 /**
  45  *
  46  * @author shura, erikgreijus
  47  */
  48 public class SWTMenu implements MenuSelectable<MenuItem> {
  49 
  50     public static final Timeout BETWEEN_KEYS_SLEEP = new Timeout(
  51             SWTMenu.class.getName() + ".between.keys.timeout", 100);
  52     public static final Timeout SUBMENU_WAIT_TIMEOUT = new Timeout(
  53             SWTMenu.class.getName() + ".submenu.wait.timeout", 1000);
  54     public static final String SELECTION_BUTTON_PROP
  55             = SWTMenu.class.getName() + ".selection.button";
  56     public static final String ESCAPE_BUTTON_PROP
  57             = SWTMenu.class.getName() + ".escape.button";
  58     public static final String MULTI_LEVEL_DISCARD_PROP
  59             = SWTMenu.class.getName() + ".multi.level.discard";
  60     public static final String SKIPS_DISABLED_PROP
  61             = SWTMenu.class.getName() + ".skips.disabled";
  62     Wrap<? extends Control> owner;
  63     boolean isBar;
  64     private final KeyboardButton selectionButton;
  65     private final KeyboardButton escapeButton;
  66     private final boolean skipsDisabled;
  67     private final boolean multiLevelDiscard;
  68 
  69     static {
  70         Shells.SHELLS.getEnvironment().initTimeout(BETWEEN_KEYS_SLEEP);
  71         Shells.SHELLS.getEnvironment().initTimeout(SUBMENU_WAIT_TIMEOUT);
  72     }
  73 
  74     public SWTMenu(Wrap<? extends Control> owner, boolean isBar) {
  75         this.owner = owner;
  76         this.isBar = isBar;
  77 
  78         KeyboardButtons defaultSelectionButton = System.getProperty("os.name")
  79                 .contains("Linux") ? KeyboardButtons.SPACE : KeyboardButtons.ENTER;
  80         KeyboardButtons defaultEscapeButton = KeyboardButtons.ESCAPE;
  81         Boolean defaultSkipsDisabled = System.getProperty("os.name").contains("Linux");
  82         Boolean defaultMultiLevelDiscard = System.getProperty("os.name").contains("Windows");
  83 
  84         selectionButton = (KeyboardButton) owner.getEnvironment().getProperty(KeyboardButton.class,
  85                 SELECTION_BUTTON_PROP, defaultSelectionButton);
  86         escapeButton = (KeyboardButton) owner.getEnvironment().getProperty(KeyboardButton.class,
  87                 ESCAPE_BUTTON_PROP, defaultEscapeButton);
  88         skipsDisabled = owner.getEnvironment().getProperty(Boolean.class,
  89                 SKIPS_DISABLED_PROP, defaultSkipsDisabled);
  90         multiLevelDiscard = owner.getEnvironment().getProperty(Boolean.class,
  91                 MULTI_LEVEL_DISCARD_PROP, defaultMultiLevelDiscard);
  92     }
  93 
  94     @Override
  95     public void push(LookupCriteria<MenuItem>... criteria) {
  96         select(criteria);
  97         owner.keyboard().pushKey(selectionButton);
  98         owner.getEnvironment().getTimeout(BETWEEN_KEYS_SLEEP).sleep();
  99     }
 100 
 101     @Override
 102     public void push(boolean desiredSelectionState, LookupCriteria<MenuItem>... criteria) {
 103         if (desiredSelectionState != getSelection(select(criteria))) {
 104             owner.keyboard().pushKey(selectionButton);
 105         } else {
 106             pushEscape((multiLevelDiscard) ? criteria.length : 1);
 107         }
 108         owner.getEnvironment().getTimeout(BETWEEN_KEYS_SLEEP).sleep();
 109     }
 110 
 111     @Override
 112     public boolean getState(LookupCriteria<MenuItem>... criteria) {
 113         boolean result = getSelection(select(criteria));
 114         pushEscape((multiLevelDiscard) ? criteria.length : 1);
 115         return result;
 116     }
 117 
 118     private void pushEscape(int times) {
 119         for (int i = 0; i < times; i++) {
 120             owner.keyboard().pushKey(escapeButton);
 121             owner.getEnvironment().getTimeout(BETWEEN_KEYS_SLEEP).sleep();
 122         }
 123     }
 124 
 125     private boolean getSelection(Wrap<MenuItem> menuItem) {
 126         final boolean[] result = new boolean[]{false};
 127         Display.getDefault().syncExec(() -> {
 128             result[0] = menuItem.getControl().getSelection();
 129         });
 130         return result[0];
 131     }
 132 
 133     @Override
 134     public Wrap<MenuItem> select(LookupCriteria<MenuItem>... criteria) {
 135         if (criteria.length == 0) {
 136             throw new IllegalArgumentException("Menu path length should be greater than 0");
 137         }
 138         final org.eclipse.swt.widgets.Menu[] start = new org.eclipse.swt.widgets.Menu[1];
 139         if (isBar) {
 140             if (!(owner.getControl() instanceof Shell)) {
 141                 throw new JemmyException("Menu bars are in shells");
 142             }
 143             Display.getDefault().syncExec(() -> {
 144                 start[0] = ((Shell) owner.getControl()).getMenuBar();
 145             });
 146         } else {
 147             Display.getDefault().syncExec(() -> {
 148                 start[0] = owner.getControl().getMenu();
 149             });
 150         }
 151         return select(start[0], Arrays.asList(criteria), true);
 152     }
 153 
 154     //selects hierarchically
 155     //assumes first item in the menu is selected
 156     private Wrap<MenuItem> select(final org.eclipse.swt.widgets.Menu menu, final List<LookupCriteria<MenuItem>> criteria, boolean entry) {
 157         waitVisible(menu);
 158         final int[] moveTimes = new int[]{0};
 159         final MenuItem[] current = new MenuItem[]{null};
 160         //find the one we're looking for
 161         Display.getDefault().syncExec(() -> {
 162             for (MenuItem item : menu.getItems()) {
 163                 if (criteria.get(0).check(item)) {
 164                     current[0] = item;
 165                     break;
 166                 }
 167                 if ((item.isEnabled() || !skipsDisabled) && !item.toString().contains("{|}")) {
 168                     moveTimes[0]++;
 169                 }
 170             }
 171         });
 172 
 173         if (current[0] == null) {
 174             throw new JemmyException("Unable to find menu item conforming criteria "
 175                     + criteria.get(0).toString(), menu);
 176         }
 177 
 178         boolean horizontal = entry ? isBar : false;
 179         move(moveTimes[0], horizontal);
 180 
 181         if (criteria.size() > 1) {
 182             final org.eclipse.swt.widgets.Menu[] nextMenu = new org.eclipse.swt.widgets.Menu[1];
 183             List<LookupCriteria<MenuItem>> nextCriteria = new ArrayList<>();
 184             nextCriteria.addAll(criteria);
 185             nextCriteria.remove(0);
 186             KeyboardButton key = horizontal ? KeyboardButtons.DOWN : KeyboardButtons.RIGHT;
 187             owner.keyboard().pushKey(key);
 188             owner.getEnvironment().getTimeout(BETWEEN_KEYS_SLEEP).sleep();
 189             Display.getDefault().syncExec(() -> {
 190                 nextMenu[0] = current[0].getMenu();
 191             });
 192             if (nextMenu[0] == null) {
 193                 throw new JemmyException("No submenu while criteria list length is still " + criteria.size(),
 194                         criteria.get(0));
 195             }
 196             return select(nextMenu[0], nextCriteria, false);
 197         }
 198         return new ItemWrap<>(owner, current[0]);
 199     }
 200 
 201     void waitVisible(final org.eclipse.swt.widgets.Menu menu) {
 202         owner.getEnvironment().getWaiter(SUBMENU_WAIT_TIMEOUT).waitValue(true,
 203                 new State<Boolean>() {
 204 
 205                     boolean state = false;
 206 
 207                     @Override
 208                     public Boolean reached() {
 209                         Display.getDefault().syncExec(() -> {
 210                             state = menu.isVisible();
 211                         });
 212                         return state;
 213                     }
 214                 });
 215     }
 216 
 217     private void move(int moveTimes, boolean horizontal) {
 218         KeyboardButton key = horizontal ? KeyboardButtons.RIGHT : KeyboardButtons.DOWN;
 219         for (int i = 0; i < moveTimes; i++) {
 220             owner.keyboard().pushKey(key);
 221             owner.getEnvironment().getTimeout(BETWEEN_KEYS_SLEEP).sleep();
 222         }
 223     }
 224 
 225     static LookupCriteria<MenuItem> createCriteria(String text, StringComparePolicy policy) {
 226         return new ByTextItem<>(text, policy);
 227     }
 228 
 229 }