001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.visualweb.gravy;
042:
043: import java.awt.Component;
044: import java.awt.Container;
045: import java.awt.EventQueue;
046: import java.awt.event.ActionEvent;
047: import java.awt.event.KeyEvent;
048: import javax.swing.tree.TreePath;
049:
050: import org.netbeans.jellytools.JellyVersion;
051: import org.netbeans.modules.visualweb.gravy.MainWindowOperator;
052: import org.netbeans.jellytools.nodes.Node;
053: import org.netbeans.jemmy.EventTool;
054: import org.netbeans.jemmy.JemmyException;
055: import org.netbeans.jemmy.JemmyProperties;
056: import org.netbeans.jemmy.Timeouts;
057: import org.netbeans.jemmy.drivers.input.KeyRobotDriver;
058: import org.netbeans.jemmy.operators.ComponentOperator;
059: import org.netbeans.jemmy.operators.JPopupMenuOperator;
060: import org.netbeans.jemmy.operators.Operator;
061: import org.netbeans.jemmy.operators.Operator.ComponentVisualizer;
062: import org.netbeans.jemmy.operators.Operator.DefaultStringComparator;
063: import org.netbeans.jemmy.operators.Operator.StringComparator;
064: import org.netbeans.jemmy.util.EmptyVisualizer;
065: import org.openide.util.actions.SystemAction;
066:
067: /** Ancestor class for all blocking actions.<p>
068: * It handles performing action through main menu (MENU_MODE), popup menu
069: * (POPUP_MODE), IDE SystemAction API call (API_MODE) or through keyboard shortcut
070: * (SHORTCUT_MODE).<p>
071: * Action can be performed in exact mode by calling performMenu(...),
072: * performPopup(...), performAPI(...) or performShortcut(...).<p>
073: * If exact mode is not supported by the action it throws
074: * UnsupportedOperationException.<p>
075: * Current implementation supports MENU_MODE when menuPath is defined, POPUP_MODE
076: * when popupPath is defined, API_MODE when systemActionClass is defined and
077: * SHORTCUT_MODE when shorcut is defined (see Action constructors).<p>
078: * Action also can be performed using runtime default mode by calling perform(...).<p>
079: * When default mode is not support by the action other modes are tried till
080: * supported mode found and action is performed.
081: *
082: * <BR>Timeouts used: <BR>
083: * Action.WaitAfterShortcutTimeout - time to sleep between shortcuts in sequence (default 0) <BR>
084: *
085: * @author <a href="mailto:adam.sotona@sun.com">Adam Sotona</a> */
086: public class Action {
087:
088: /** through menu action performing mode */
089: public static final int MENU_MODE = 0;
090: /** through popup menu action performing mode */
091: public static final int POPUP_MODE = 1;
092: /** through API action performing mode */
093: public static final int API_MODE = 2;
094: /** through shortcut action performing mode */
095: public static final int SHORTCUT_MODE = 3;
096:
097: /** sleep time between nodes selection and action execution */
098: protected static final long SELECTION_WAIT_TIME = 300;
099: /** sleep time after action execution */
100: protected static final long AFTER_ACTION_WAIT_TIME = 0;
101: /** sleep time between sequence of shortcuts */
102: protected static final long WAIT_AFTER_SHORTCUT_TIMEOUT = 0;
103:
104: private static final int sequence[][] = {
105: { MENU_MODE, POPUP_MODE, SHORTCUT_MODE, API_MODE },
106: { POPUP_MODE, MENU_MODE, SHORTCUT_MODE, API_MODE },
107: { API_MODE, POPUP_MODE, MENU_MODE, SHORTCUT_MODE },
108: { SHORTCUT_MODE, POPUP_MODE, MENU_MODE, API_MODE } };
109:
110: /** menu path of current action or null when MENU_MODE is not supported */
111: protected String menuPath;
112: /** popup menu path of current action or null when POPUP_MODE is not supported */
113: protected String popupPath;
114: /** SystemAction class of current action or null when API_MODE is not supported */
115: protected Class systemActionClass;
116: /** array of shortcuts of current action or null when SHORTCUT_MODE is not supported */
117: protected Shortcut[] shortcuts;
118:
119: /** Comparator used as default for all Action instances. It is set in static clause. */
120: private static StringComparator defaultComparator;
121: /** Comparator used for this action instance. */
122: private StringComparator comparator;
123:
124: /** creates new Action instance without API_MODE and SHORTCUT_MODE support
125: * @param menuPath action path in main menu (use null value if menu mode is not supported)
126: * @param popupPath action path in popup menu (use null value if popup mode shell is not supported) */
127: public Action(String menuPath, String popupPath) {
128: this (menuPath, popupPath, null, (Shortcut[]) null);
129: }
130:
131: /** creates new Action instance without SHORTCUT_MODE support
132: * @param menuPath action path in main menu (use null value if menu mode is not supported)
133: * @param popupPath action path in popup menu (use null value if popup mode is not supported)
134: * @param systemActionClass String class name of SystemAction (use null value if API mode is not supported) */
135: public Action(String menuPath, String popupPath,
136: String systemActionClass) {
137: this (menuPath, popupPath, systemActionClass, (Shortcut[]) null);
138: }
139:
140: /** creates new Action instance without API_MODE support
141: * @param shortcuts array of Shortcut instances (use null value if shorcut mode is not supported)
142: * @param menuPath action path in main menu (use null value if menu mode is not supported)
143: * @param popupPath action path in popup menu (use null value if popup mode shell is not supported) */
144: public Action(String menuPath, String popupPath,
145: Shortcut[] shortcuts) {
146: this (menuPath, popupPath, null, shortcuts);
147: }
148:
149: /** creates new Action instance without API_MODE support
150: * @param shortcut Shortcut (use null value if menu mode is not supported)
151: * @param menuPath action path in main menu (use null value if menu mode is not supported)
152: * @param popupPath action path in popup menu (use null value if popup mode shell is not supported) */
153: public Action(String menuPath, String popupPath, Shortcut shortcut) {
154: this (menuPath, popupPath, null, new Shortcut[] { shortcut });
155: }
156:
157: /** creates new Action instance
158: * @param shortcuts array of Shortcut instances (use null value if shortcut mode is not supported)
159: * @param menuPath action path in main menu (use null value if menu mode is not supported)
160: * @param popupPath action path in popup menu (use null value if popup mode is not supported)
161: * @param systemActionClass String class name of SystemAction (use null value if API mode is not supported) */
162: public Action(String menuPath, String popupPath,
163: String systemActionClass, Shortcut[] shortcuts) {
164: this .menuPath = menuPath;
165: this .popupPath = popupPath;
166: if (systemActionClass == null) {
167: this .systemActionClass = null;
168: } else
169: try {
170: this .systemActionClass = Class
171: .forName(systemActionClass);
172: } catch (ClassNotFoundException e) {
173: this .systemActionClass = null;
174: }
175: this .shortcuts = shortcuts;
176: }
177:
178: /** creates new Action instance
179: * @param shortcut Shortcut String (use null value if menu mode is not supported)
180: * @param menuPath action path in main menu (use null value if menu mode is not supported)
181: * @param popupPath action path in popup menu (use null value if popup mode is not supported)
182: * @param systemActionClass String class name of SystemAction (use null value if API mode is not supported) */
183: public Action(String menuPath, String popupPath,
184: String systemActionClass, Shortcut shortcut) {
185: this (menuPath, popupPath, systemActionClass,
186: new Shortcut[] { shortcut });
187: }
188:
189: static {
190: // Checks if you run on correct jemmy version. Writes message to jemmy log if not.
191: JellyVersion.checkJemmyVersion();
192:
193: if (JemmyProperties.getCurrentProperty("Action.DefaultMode") == null)
194: JemmyProperties.setCurrentProperty("Action.DefaultMode",
195: new Integer(POPUP_MODE));
196: Timeouts.initDefault("Action.WaitAfterShortcutTimeout",
197: WAIT_AFTER_SHORTCUT_TIMEOUT);
198: // Set case sensitive comparator as default because of
199: // very often clash between Cut and Execute menu items.
200: // Substring criterion is set according to default string comparator
201: boolean compareExactly = !Operator.getDefaultStringComparator()
202: .equals("abc", "a"); // NOI18N
203: defaultComparator = new DefaultStringComparator(compareExactly,
204: true);
205: }
206:
207: private void perform(int mode) {
208: switch (mode) {
209: case POPUP_MODE:
210: performPopup();
211: break;
212: case MENU_MODE:
213: performMenu();
214: break;
215: case API_MODE:
216: performAPI();
217: break;
218: case SHORTCUT_MODE:
219: performShortcut();
220: break;
221: default:
222: throw new IllegalArgumentException("Wrong Action.MODE");
223: }
224: }
225:
226: private void perform(Node node, int mode) {
227: switch (mode) {
228: case POPUP_MODE:
229: performPopup(node);
230: break;
231: case MENU_MODE:
232: performMenu(node);
233: break;
234: case API_MODE:
235: performAPI(node);
236: break;
237: case SHORTCUT_MODE:
238: performShortcut(node);
239: break;
240: default:
241: throw new IllegalArgumentException("Wrong Action.MODE");
242: }
243: }
244:
245: private void perform(Node[] nodes, int mode) {
246: switch (mode) {
247: case POPUP_MODE:
248: performPopup(nodes);
249: break;
250: case MENU_MODE:
251: performMenu(nodes);
252: break;
253: case API_MODE:
254: performAPI(nodes);
255: break;
256: case SHORTCUT_MODE:
257: performShortcut(nodes);
258: break;
259: default:
260: throw new IllegalArgumentException("Wrong Action.MODE");
261: }
262: }
263:
264: private void perform(ComponentOperator component, int mode) {
265: switch (mode) {
266: case POPUP_MODE:
267: performPopup(component);
268: break;
269: case MENU_MODE:
270: performMenu(component);
271: break;
272: case API_MODE:
273: performAPI(component);
274: break;
275: case SHORTCUT_MODE:
276: performShortcut(component);
277: break;
278: default:
279: throw new IllegalArgumentException("Wrong Action.MODE");
280: }
281: }
282:
283: /** performs action depending on default mode,<br>
284: * calls performPopup(), performMenu() or performAPI(),<br>
285: * when default mode is not supported, others are tried */
286: public void perform() {
287: int modes[] = sequence[getDefaultMode()];
288: for (int i = 0; i < modes.length; i++)
289: try {
290: perform(modes[i]);
291: return;
292: } catch (UnsupportedOperationException e) {
293: }
294: }
295:
296: /** performs action depending on default mode,<br>
297: * calls performPopup(), performMenu() or performAPI(),<br>
298: * when default mode is not supported, others are tried
299: * @param node node to be action performed on */
300: public void perform(Node node) {
301: int modes[] = sequence[getDefaultMode()];
302: for (int i = 0; i < modes.length; i++)
303: try {
304: perform(node, modes[i]);
305: return;
306: } catch (UnsupportedOperationException e) {
307: }
308: }
309:
310: /** performs action depending on default mode,<br>
311: * calls performPopup(), performMenu() or performAPI(),<br>
312: * when default mode is not supported, others are tried
313: * @param nodes nodes to be action performed on */
314: public void perform(Node[] nodes) {
315: int modes[] = sequence[getDefaultMode()];
316: for (int i = 0; i < modes.length; i++)
317: try {
318: perform(nodes, modes[i]);
319: return;
320: } catch (UnsupportedOperationException e) {
321: }
322: }
323:
324: /** performs action depending on default mode,<br>
325: * calls performPopup(), performMenu() or performAPI(),<br>
326: * when default mode is not supported, others are tried
327: * @param component component to be action performed on */
328: public void perform(ComponentOperator component) {
329: int modes[] = sequence[getDefaultMode()];
330: for (int i = 0; i < modes.length; i++)
331: try {
332: perform(component, modes[i]);
333: return;
334: } catch (UnsupportedOperationException e) {
335: }
336: }
337:
338: /** performs action through main menu
339: * @throws UnsupportedOperationException when action does not support menu mode */
340: public void performMenu() {
341: if (menuPath == null) {
342: throw new UnsupportedOperationException(getClass()
343: .toString()
344: + " does not define menu path");
345: }
346: // Need to wait here to be more reliable.
347: // TBD - It can be removed after issue 23663 is solved.
348: new EventTool().waitNoEvent(500);
349: Util.getMainMenu().pushMenu(menuPath);
350: try {
351: Thread.sleep(AFTER_ACTION_WAIT_TIME);
352: } catch (Exception e) {
353: throw new JemmyException("Sleeping interrupted", e);
354: }
355: }
356:
357: /** performs action through main menu
358: * @param node node to be action performed on
359: * @throws UnsupportedOperationException when action does not support menu mode */
360: public void performMenu(Node node) {
361: performMenu(new Node[] { node });
362: }
363:
364: /** performs action through main menu
365: * @param nodes nodes to be action performed on
366: * @throws UnsupportedOperationException when action does not support menu mode */
367: public void performMenu(Node[] nodes) {
368: if (menuPath == null)
369: throw new UnsupportedOperationException(getClass()
370: .toString()
371: + " does not define menu path");
372: testNodes(nodes);
373: nodes[0].select();
374: for (int i = 1; i < nodes.length; i++)
375: nodes[i].addSelectionPath();
376: try {
377: Thread.sleep(SELECTION_WAIT_TIME);
378: } catch (Exception e) {
379: throw new JemmyException("Sleeping interrupted", e);
380: }
381: performMenu();
382: }
383:
384: /** performs action through main menu
385: * @param component component to be action performed on
386: * @throws UnsupportedOperationException when action does not support menu mode */
387: public void performMenu(ComponentOperator component) {
388: component.getFocus();
389: try {
390: Thread.sleep(SELECTION_WAIT_TIME);
391: } catch (Exception e) {
392: throw new JemmyException("Sleeping interrupted", e);
393: }
394: performMenu();
395: }
396:
397: /** performs action through popup menu
398: * @throws UnsupportedOperationException */
399: public void performPopup() {
400: throw new UnsupportedOperationException(getClass().toString()
401: + " does not implement performPopup()");
402: }
403:
404: /** performs action through popup menu
405: * @param node node to be action performed on
406: * @throws UnsupportedOperationException when action does not support popup mode */
407: public void performPopup(Node node) {
408: performPopup(new Node[] { node });
409: }
410:
411: /** performs action through popup menu
412: * @param nodes nodes to be action performed on
413: * @throws UnsupportedOperationException when action does not support popup mode */
414: public void performPopup(Node[] nodes) {
415: if (popupPath == null) {
416: throw new UnsupportedOperationException(getClass()
417: .toString()
418: + " does not define popup path");
419: }
420: testNodes(nodes);
421: TreePath paths[] = new TreePath[nodes.length];
422: for (int i = 0; i < nodes.length; i++) {
423: paths[i] = nodes[i].getTreePath();
424: }
425: ComponentVisualizer treeVisualizer = nodes[0].tree()
426: .getVisualizer();
427: ComponentVisualizer oldVisualizer = null;
428: // If visualizer of JTreeOperator is EmptyVisualizer, we need
429: // to avoid making tree component visible in callPopup method.
430: // So far only known case is tree from TreeTableOperator.
431: if (treeVisualizer instanceof EmptyVisualizer) {
432: oldVisualizer = Operator.getDefaultComponentVisualizer();
433: Operator.setDefaultComponentVisualizer(treeVisualizer);
434: }
435: // Need to wait here to be more reliable.
436: // TBD - It can be removed after issue 23663 is solved.
437: new EventTool().waitNoEvent(500);
438: JPopupMenuOperator popup = new JPopupMenuOperator(nodes[0]
439: .tree().callPopupOnPaths(paths));
440: // restore previously used default visualizer
441: if (oldVisualizer != null) {
442: Operator.setDefaultComponentVisualizer(oldVisualizer);
443: }
444: popup.pushMenu(popupPath, "|", getComparator());
445: try {
446: Thread.sleep(AFTER_ACTION_WAIT_TIME);
447: } catch (Exception e) {
448: throw new JemmyException("Sleeping interrupted", e);
449: }
450: }
451:
452: /** performs action through popup menu
453: * @param component component to be action performed on
454: * @throws UnsupportedOperationException when action does not support popup mode */
455: public void performPopup(ComponentOperator component) {
456: if (popupPath == null) {
457: throw new UnsupportedOperationException(getClass()
458: .toString()
459: + " does not define popup path");
460: }
461: // Need to wait here to be more reliable.
462: // TBD - It can be removed after issue 23663 is solved.
463: new EventTool().waitNoEvent(500);
464: component.clickForPopup();
465: new JPopupMenuOperator(component).pushMenu(popupPath, "|",
466: getComparator());
467: try {
468: Thread.sleep(AFTER_ACTION_WAIT_TIME);
469: } catch (Exception e) {
470: throw new JemmyException("Sleeping interrupted", e);
471: }
472: }
473:
474: /** performs action through API
475: * @throws UnsupportedOperationException when action does not support API mode */
476: public void performAPI() {
477: if (systemActionClass == null) {
478: throw new UnsupportedOperationException(getClass()
479: .toString()
480: + " does not support API call.");
481: }
482: try {
483: // actions has to be invoked in dispatch thread (see http://www.netbeans.org/issues/show_bug.cgi?id=35755)
484: EventQueue.invokeAndWait(new Runnable() {
485: public void run() {
486: if (SystemAction.class
487: .isAssignableFrom(systemActionClass)) {
488: // SystemAction used in IDE
489: SystemAction.get(systemActionClass)
490: .actionPerformed(
491: new ActionEvent(
492: new Container(), 0,
493: null));
494: } else {
495: // action implements javax.swing.Action
496: try {
497: ((javax.swing.Action) systemActionClass
498: .newInstance())
499: .actionPerformed(null);
500: } catch (Exception e) {
501: throw new JemmyException(
502: "Exception when trying to create instance of action \""
503: + systemActionClass
504: .getName() + "\".",
505: e);
506: }
507: }
508: }
509: });
510: Thread.sleep(AFTER_ACTION_WAIT_TIME);
511: } catch (Exception e) {
512: throw new JemmyException("Interrupted", e);
513: }
514: }
515:
516: /** performs action through API
517: * @param node node to be action performed on
518: * @throws UnsupportedOperationException when action does not support API mode */
519: public void performAPI(Node node) {
520: performAPI(new Node[] { node });
521: }
522:
523: /** performs action through API
524: * @param nodes nodes to be action performed on
525: * @throws UnsupportedOperationException when action does not support API mode */
526: public void performAPI(Node[] nodes) {
527: if (systemActionClass == null)
528: throw new UnsupportedOperationException(getClass()
529: .toString()
530: + " does not define SystemAction");
531: testNodes(nodes);
532: nodes[0].select();
533: for (int i = 1; i < nodes.length; i++)
534: nodes[i].addSelectionPath();
535: try {
536: Thread.sleep(SELECTION_WAIT_TIME);
537: } catch (Exception e) {
538: throw new JemmyException("Sleeping interrupted", e);
539: }
540: performAPI();
541: }
542:
543: /** performs action through API
544: * @param component component to be action performed on
545: * @throws UnsupportedOperationException when action does not support API mode */
546: public void performAPI(ComponentOperator component) {
547: component.getFocus();
548: try {
549: Thread.sleep(SELECTION_WAIT_TIME);
550: } catch (Exception e) {
551: throw new JemmyException("Sleeping interrupted", e);
552: }
553: performAPI();
554: }
555:
556: /** performs action through shortcut
557: * @throws UnsupportedOperationException if no shortcut is defined */
558: public void performShortcut() {
559: if (shortcuts == null) {
560: throw new UnsupportedOperationException(getClass()
561: .toString()
562: + " does not define shortcut");
563: }
564: for (int i = 0; i < shortcuts.length; i++) {
565: new KeyRobotDriver(null).pushKey(null, shortcuts[i]
566: .getKeyCode(), shortcuts[i].getKeyModifiers(),
567: JemmyProperties.getCurrentTimeouts().create(
568: "Timeouts.DeltaTimeout"));
569: JemmyProperties.getProperties().getTimeouts().sleep(
570: "Action.WaitAfterShortcutTimeout");
571: }
572: try {
573: Thread.sleep(AFTER_ACTION_WAIT_TIME);
574: } catch (Exception e) {
575: throw new JemmyException("Sleeping interrupted", e);
576: }
577: }
578:
579: /** performs action through shortcut
580: * @param node node to be action performed on
581: * @throws UnsupportedOperationException when action does not support shortcut mode */
582: public void performShortcut(Node node) {
583: performShortcut(new Node[] { node });
584: }
585:
586: /** performs action through shortcut
587: * @param nodes nodes to be action performed on
588: * @throws UnsupportedOperationException when action does not support shortcut mode */
589: public void performShortcut(Node[] nodes) {
590: if (shortcuts == null) {
591: throw new UnsupportedOperationException(getClass()
592: .toString()
593: + " does not define shortcut");
594: }
595: testNodes(nodes);
596: nodes[0].select();
597: for (int i = 1; i < nodes.length; i++)
598: nodes[i].addSelectionPath();
599: try {
600: Thread.sleep(SELECTION_WAIT_TIME);
601: } catch (Exception e) {
602: throw new JemmyException("Sleeping interrupted", e);
603: }
604: performShortcut();
605: }
606:
607: /** performs action through shortcut
608: * @param component component to be action performed on
609: * @throws UnsupportedOperationException when action does not support shortcut mode */
610: public void performShortcut(ComponentOperator component) {
611: component.getFocus();
612: try {
613: Thread.sleep(SELECTION_WAIT_TIME);
614: } catch (Exception e) {
615: throw new JemmyException("Sleeping interrupted", e);
616: }
617: performShortcut();
618: }
619:
620: /** tests if nodes are all from the same tree
621: * @param nodes nodes
622: * @throws IllegalArgumentException when given nodes does not pass */
623: protected void testNodes(Node[] nodes) {
624: if ((nodes == null) || (nodes.length == 0))
625: throw new IllegalArgumentException(
626: "argument nodes is null or empty");
627: Class myClass = getClass();
628: Component nodesTree = nodes[0].tree().getSource();
629: for (int i = 0; i < nodes.length; i++) {
630: // if (!nodes[i].hasAction(myClass))
631: // throw new IllegalArgumentException(this.toString()+" could not be performed on "+nodes[i].toString());
632: if (nodes[i] == null)
633: throw new IllegalArgumentException(
634: "argument nodes contains null value");
635: if (!nodesTree.equals(nodes[i].tree().getSource()))
636: throw new IllegalArgumentException(nodes[i].toString()
637: + " is from different tree");
638: }
639: }
640:
641: /** Returns default mode in which actions are performed.
642: * @return default mode in which actions are performed
643: * @see #POPUP_MODE
644: * @see #MENU_MODE
645: * @see #API_MODE
646: * @see #SHORTCUT_MODE
647: */
648: public int getDefaultMode() {
649: int mode = (((Integer) JemmyProperties
650: .getCurrentProperty("Action.DefaultMode")).intValue());
651: if (mode < 0 || mode > 3)
652: return POPUP_MODE;
653: return mode;
654: }
655:
656: /** Sets default mode in which actions are performed. If given mode value
657: * is not valid, it sets {@link #POPUP_MODE} as default.
658: * @param mode mode to be set
659: * @return previous value
660: * @see #POPUP_MODE
661: * @see #MENU_MODE
662: * @see #API_MODE
663: * @see #SHORTCUT_MODE
664: */
665: public int setDefaultMode(int mode) {
666: int oldMode = (((Integer) JemmyProperties
667: .getCurrentProperty("Action.DefaultMode")).intValue());
668: if (mode < 0 || mode > 3) {
669: mode = POPUP_MODE;
670: }
671: JemmyProperties.setCurrentProperty("Action.DefaultMode",
672: new Integer(mode));
673: return oldMode;
674: }
675:
676: /** Sets comparator fot this action. Comparator is used for all actions
677: * after this method is called.
678: * @param comparator new comparator to be set (e.g.
679: * new Operator.DefaultStringComparator(true, true);
680: * to search string item exactly and case sensitive)
681: */
682: public void setComparator(StringComparator comparator) {
683: this .comparator = comparator;
684: }
685:
686: /** Gets comparator set for this action instance.
687: * @return comparator set for this action instance.
688: */
689: public StringComparator getComparator() {
690: if (comparator == null) {
691: comparator = defaultComparator;
692: }
693: return comparator;
694: }
695:
696: /** getter for popup menu path
697: * @return String popup menu path (or null if not suported)
698: */
699: public String getPopupPath() {
700: return popupPath;
701: }
702:
703: /** getter for main menu path
704: * @return String main menu path (or null if not suported)
705: */
706: public String getMenuPath() {
707: return menuPath;
708: }
709:
710: /** getter for system action class
711: * @return Class of system action (or null if not suported)
712: */
713: public Class getSystemActionClass() {
714: return systemActionClass;
715: }
716:
717: /** getter for array of shortcuts
718: * @return Shortcut[] (or null if not suported)
719: */
720: public Shortcut[] getShortcuts() {
721: return shortcuts;
722: }
723:
724: /** Checks whether this action is enabled. If IDE system action class
725: * is defined, it calls its isEnabled() method else if main menu path is
726: * defined, it checks menu item is enabled. Otherwise it throws
727: * UnsupportedOperationException.
728: * @return true if this action is enabled; false otherwise
729: */
730: public boolean isEnabled() {
731: if (systemActionClass != null) {
732: return SystemAction.get(systemActionClass).isEnabled();
733: } else if (menuPath != null) {
734: return Util.getMainMenu().showMenuItem(menuPath, "|",
735: getComparator()).isEnabled();
736: } else {
737: throw new UnsupportedOperationException("Cannot detect if "
738: + getClass().getName() + " is enabled.");
739: }
740: }
741:
742: /** Checks whether this action on given nodes is enabled. If IDE system action class
743: * is defined, it calls its isEnabled() method. Nodes are selected first.
744: * Else if popup menu path is defined, it checks menu item is enabled.
745: * Otherwise it throws UnsupportedOperationException.
746: * @param nodes array of nodes to be selected before a check
747: * @return true if this action is enabled; false otherwise
748: */
749: public boolean isEnabled(Node[] nodes) {
750: testNodes(nodes);
751: if (systemActionClass != null) {
752: nodes[0].select();
753: for (int i = 1; i < nodes.length; i++)
754: nodes[i].addSelectionPath();
755: try {
756: Thread.sleep(SELECTION_WAIT_TIME);
757: } catch (Exception e) {
758: throw new JemmyException("Sleeping interrupted", e);
759: }
760: return SystemAction.get(systemActionClass).isEnabled();
761: } else if (popupPath != null) {
762: TreePath paths[] = new TreePath[nodes.length];
763: for (int i = 0; i < nodes.length; i++) {
764: paths[i] = nodes[i].getTreePath();
765: }
766: ComponentVisualizer treeVisualizer = nodes[0].tree()
767: .getVisualizer();
768: ComponentVisualizer oldVisualizer = null;
769: // If visualizer of JTreeOperator is EmptyVisualizer, we need
770: // to avoid making tree component visible in callPopup method.
771: // So far only known case is tree from TreeTableOperator.
772: if (treeVisualizer instanceof EmptyVisualizer) {
773: oldVisualizer = Operator
774: .getDefaultComponentVisualizer();
775: Operator.setDefaultComponentVisualizer(treeVisualizer);
776: }
777: // Need to wait here to be more reliable.
778: // TBD - It can be removed after issue 23663 is solved.
779: new EventTool().waitNoEvent(500);
780: JPopupMenuOperator popup = new JPopupMenuOperator(nodes[0]
781: .tree().callPopupOnPaths(paths));
782: // restore previously used default visualizer
783: if (oldVisualizer != null) {
784: Operator.setDefaultComponentVisualizer(oldVisualizer);
785: }
786: return popup.showMenuItem(popupPath, "|", getComparator())
787: .isEnabled();
788: } else {
789: throw new UnsupportedOperationException("Cannot detect if "
790: + getClass().getName() + " is enabled.");
791: }
792: }
793:
794: /** Checks whether this action on given node is enabled. If IDE system action class
795: * is defined, it calls its isEnabled() method. Node is selected first.
796: * Else if popup menu path is defined, it checks menu item is enabled.
797: * Otherwise it throws UnsupportedOperationException.
798: * @param node node to be selected before a check
799: * @return true if this action is enabled; false otherwise
800: */
801: public boolean isEnabled(Node node) {
802: return isEnabled(new Node[] { node });
803: }
804:
805: /** Checks whether this action is enabled for given ComponentOperator.
806: * First it makes component visible and focused.
807: * If IDE system action class is defined, it calls its isEnabled() method.
808: * Else if main menu path is defined, it checks menu item is enabled.
809: * Otherwise it throws UnsupportedOperationException.
810: * @param componentOperator instance of ComponentOperator
811: * @return true if this action is enabled; false otherwise
812: */
813: public boolean isEnabled(ComponentOperator componentOperator) {
814: componentOperator.makeComponentVisible();
815: componentOperator.getFocus();
816: if (systemActionClass != null) {
817: return SystemAction.get(systemActionClass).isEnabled();
818: } else if (popupPath != null) {
819: // Need to wait here to be more reliable.
820: // TBD - It can be removed after issue 23663 is solved.
821: new EventTool().waitNoEvent(500);
822: componentOperator.clickForPopup();
823: return new JPopupMenuOperator(componentOperator)
824: .showMenuItem(popupPath, "|", getComparator())
825: .isEnabled();
826: } else if (menuPath != null) {
827: return Util.getMainMenu().showMenuItem(menuPath, "|",
828: getComparator()).isEnabled();
829: } else {
830: throw new UnsupportedOperationException("Cannot detect if "
831: + getClass().getName() + " is enabled.");
832: }
833: }
834:
835: /** This class defines keyboard shortcut for action execution */
836: public static class Shortcut extends Object {
837: /** key code of shortcut (see KeyEvent) */
838: protected int keyCode;
839: /** key modifiers of shortcut (see KeyEvent) */
840: protected int keyModifiers;
841:
842: /** creates new shortcut
843: * @param keyCode int key code (see KeyEvent) */
844: public Shortcut(int keyCode) {
845: this (keyCode, 0);
846: }
847:
848: /** creates new shortcut
849: * @param keyCode int key code (see KeyEvent)
850: * @param keyModifiers int key modifiers (see KeyEvent) */
851: public Shortcut(int keyCode, int keyModifiers) {
852: this .keyCode = keyCode;
853: this .keyModifiers = keyModifiers;
854: }
855:
856: /** getter for key code
857: * @return int key code */
858: public int getKeyCode() {
859: return keyCode;
860: }
861:
862: /** getter for key modifiers
863: * @return int key modifiers */
864: public int getKeyModifiers() {
865: return keyModifiers;
866: }
867:
868: /** returns String representation of shortcut
869: * @return String representation of shortcut */
870: public String toString() {
871: String s = KeyEvent.getKeyModifiersText(getKeyModifiers());
872: return s + (s.length() > 0 ? "+" : "")
873: + KeyEvent.getKeyText(getKeyCode());
874: }
875: }
876: }
|