001: package abbot.tester;
002:
003: import java.awt.*;
004: import java.awt.event.KeyEvent;
005: import java.util.*;
006: import java.util.List;
007:
008: import javax.accessibility.AccessibleContext;
009: import javax.swing.*;
010:
011: import abbot.*;
012: import abbot.i18n.Strings;
013:
014: import abbot.script.ArgumentParser;
015:
016: import abbot.util.AWT;
017:
018: import java.lang.reflect.InvocationTargetException;
019: import java.lang.reflect.Method;
020:
021: /** Provides auto-scrolling prior to events for JComponent-derived classes. */
022: // NOTE may eventually need to push ComponentLocation up to Robot, so that
023: // only mousePress and actionDrop need to be overridden. This would be mostly
024: // aesthetic, since making the target point visible is sufficient; having the
025: // entire substructure visible is just more pleasing to the eye.
026: // FIXME may need to override key/focus actions to scroll prior to sending the
027: // events, similar to how click is overridden here
028: public class JComponentTester extends ContainerTester {
029:
030: /** This property is a duplicate of the one in JLabel, which we can't
031: * access.
032: */
033: private static final String LABELED_BY_PROPERTY = "labeledBy";
034:
035: /** Derive a tag for identifying this component. */
036: public String deriveTag(Component comp) {
037: // If the component class is custom, don't provide a tag
038: if (isCustom(comp.getClass()))
039: return null;
040:
041: JComponent jComp = ((JComponent) comp);
042: String tag = null;
043: // If label.setLabelFor has been used, then this component has
044: // a label; use its text
045: JLabel label = (JLabel) ((JComponent) comp)
046: .getClientProperty(LABELED_BY_PROPERTY);
047: if (label != null && label.getText() != null
048: && label.getText().length() > 0) {
049: tag = label.getText();
050: }
051: if (tag == null || "".equals(tag)) {
052: AccessibleContext context = jComp.getAccessibleContext();
053: tag = deriveAccessibleTag(context);
054: }
055: if (tag == null || "".equals(tag)) {
056: tag = super .deriveTag(comp);
057: }
058: return tag;
059: }
060:
061: /** Scrolls to ensure the substructure is in view before clicking.
062: @deprecated Use
063: {@link #actionClick(Component, ComponentLocation, int, int)}
064: instead.
065: */
066: public void actionClick(Component c, ComponentLocation loc,
067: String buttons, int count) {
068: actionClick(c, loc, AWT.getModifiers(buttons), count);
069: }
070:
071: /** Scrolls to ensure the substructure is in view before clicking. */
072: public void actionClick(Component c, ComponentLocation loc,
073: int buttons, int count) {
074: if (c instanceof JComponent) {
075: scrollToVisible(c, loc.getBounds(c));
076: }
077: super .actionClick(c, loc, buttons, count);
078: }
079:
080: /** @deprecated Use
081: {@link #actionDrag(Component, ComponentLocation, int)} instead.
082: */
083: public void actionDrag(Component c, ComponentLocation loc,
084: String mods) {
085: actionDrag(c, loc, AWT.getModifiers(mods));
086: }
087:
088: /** Scrolls to ensure the substructure is in view before starting the
089: * drag.
090: */
091: public void actionDrag(Component c, ComponentLocation loc,
092: int modifiers) {
093: if (c instanceof JComponent) {
094: scrollToVisible(c, loc.getBounds(c));
095: }
096: super .actionDrag(c, loc, modifiers);
097: }
098:
099: /** Scrolls to ensure the drop target substructure is in view before
100: dropping (normally handled by autoscroll).
101: */
102: public void actionDrop(Component c, ComponentLocation loc) {
103: if (c instanceof JComponent) {
104: scrollToVisible(c, loc.getBounds(c));
105: }
106: super .actionDrop(c, loc);
107: }
108:
109: /** Click in the given part of the component, scrolling the component if
110: * necessary to make the point visible. Performing the scroll here
111: * obviates the need for all derived classes to remember to do it for
112: * actions involving clicks.
113: */
114: public void mousePress(Component comp, int x, int y, int buttons) {
115: if (comp instanceof JComponent) {
116: scrollToVisible(comp, x, y);
117: }
118: super .mousePress(comp, x, y, buttons);
119: }
120:
121: /**
122: * Scrolls the component so that the coordinate x and y are visible. Has
123: * no effect if the component has no JViewport ancestor.
124: *
125: * @param comp the Component to scroll
126: * @param x the x coordinate to be visible
127: * @param y the y coordinate to be visible
128: */
129: protected void scrollToVisible(Component comp, int x, int y) {
130: Rectangle rect = new Rectangle(x, y, 1, 1);
131: scrollToVisible(comp, rect);
132: }
133:
134: /** Invoke {@link JComponent#scrollRectToVisible(Rectangle)} on the given
135: {@link JComponent} on the event dispatch thread.
136: */
137: protected void scrollRectToVisible(final JComponent jc,
138: final Rectangle rect) {
139: Log.debug("Scroll to visible: " + rect);
140: // Ideally, we'd use scrollbar commands to effect the scrolling,
141: // but that gets really complicated for no real gain in function.
142: // Fortunately, Swing's Scrollable makes for a simple solution.
143: // NOTE: absolutely MUST wait for idle in order for the scroll to
144: // finish, and the UI to update so that the next action goes
145: // to the proper location within the scrolled component.
146: invokeAndWait(new Runnable() {
147: public void run() {
148: jc.scrollRectToVisible(rect);
149: }
150: });
151: }
152:
153: protected boolean isVisible(JComponent c, Rectangle rect) {
154: Rectangle visible = c.getVisibleRect();
155: return visible.contains(rect);
156: }
157:
158: protected boolean isVisible(JComponent c, int x, int y) {
159: Rectangle visible = c.getVisibleRect();
160: return visible.contains(x, y);
161: }
162:
163: /**
164: * Scrolls the component so that the given rectangle is visible. Has no
165: * effect if the component has no JViewport ancestor. When this method
166: * returns, the requested rectangle's upper left corner will be visible
167: * (i.e. no {@link #waitForIdle()} is required.
168: *
169: * @param comp the Component to scroll
170: * @param rect the Rectangle to make visible.
171: */
172: protected void scrollToVisible(Component comp, final Rectangle rect) {
173: final JComponent jc = (JComponent) comp;
174: if (!isVisible(jc, rect)) {
175: scrollRectToVisible(jc, rect);
176: // Need to make at least the upper left corner of the requested
177: // rectangle visible.
178: if (!isVisible(jc, rect.x, rect.y)) {
179: String msg = Strings.get(
180: "tester.JComponent.not_visible", new Object[] {
181: new Integer(rect.x),
182: new Integer(rect.y), jc, });
183: throw new ActionFailedException(msg);
184: }
185: }
186: }
187:
188: /** Make sure the given point is visible. Note that this may have no
189: * effect if the component is not actually in a scroll pane.
190: */
191: public void actionScrollToVisible(Component comp,
192: ComponentLocation loc) {
193: scrollToVisible(comp, loc.getBounds(comp));
194: waitForIdle();
195: }
196:
197: /** Make sure the given point is visible. Note that this may have no
198: * effect if the component is not actually in a scroll pane.
199: */
200: public void actionScrollToVisible(Component comp, int x, int y) {
201: actionScrollToVisible(comp, new ComponentLocation(new Point(x,
202: y)));
203: }
204:
205: /** Make sure the given rectangle is visible. Note that this may have no
206: * effect if the component is not actually in a scroll pane.
207: */
208: public void actionScrollToVisible(Component comp, int x, int y,
209: int width, int height) {
210: scrollToVisible(comp, new Rectangle(x, y, width, height));
211: waitForIdle();
212: }
213:
214: /** Invoke an action from the component's action map. */
215: public void actionActionMap(Component comp, String name) {
216: focus(comp, true);
217: JComponent jc = (JComponent) comp;
218:
219: ActionMap am = jc.getActionMap();
220: // On OSX/1.3.1, some action map keys are actions instead of strings.
221: // On XP/1.4.1, all action map keys are strings.
222: // If we can't look it up with the string key we saved, check all the
223: // actions for a corresponding name.
224: Object action = am.get(name);
225: Object key = name;
226: if (action == null) {
227: Object[] keys = am.allKeys();
228: for (int i = 0; keys != null && i < keys.length; i++) {
229: Object value = am.get(keys[i]);
230: if ((value instanceof Action)) {
231: if (name.equals(((Action) value)
232: .getValue(Action.NAME))) {
233: action = value;
234: key = keys[i];
235: break;
236: }
237: }
238: }
239: }
240: if (action == null) {
241: String available = "Available actions are the following:";
242: Object[] names = am.allKeys();
243: if (names != null) {
244: Arrays.sort(names, new java.util.Comparator() {
245: public int compare(Object o1, Object o2) {
246: String n1 = o1.toString();
247: String n2 = o2.toString();
248: return n1.compareTo(n2);
249: }
250: });
251: for (int i = 0; i < names.length; i++) {
252: available += "\n" + names[i];
253: if (!(names[i] instanceof String))
254: available += " (" + names[i].getClass() + ")";
255: }
256: }
257: throw new AssertionFailedError("No such action '" + name
258: + "'. " + available);
259: }
260: InputMap im = jc.getInputMap();
261: KeyStroke[] events = im.allKeys();
262: List strokes = new ArrayList();
263: for (int i = 0; events != null && i < events.length; i++) {
264: KeyStroke ks = events[i];
265: if (key.equals(im.get(ks))) {
266: strokes.add(ks);
267: }
268: }
269: if (strokes.size() > 0) {
270: for (Iterator i = strokes.iterator(); i.hasNext();) {
271: KeyStroke ks = (KeyStroke) i.next();
272: Log.debug("Generating keystroke " + ks + " for action "
273: + name);
274: try {
275: if (ks.getKeyCode() == KeyEvent.VK_UNDEFINED)
276: keyStroke(ks.getKeyChar());
277: else
278: key(ks.getKeyCode(), ks.getModifiers());
279: waitForIdle();
280: return;
281: } catch (IllegalArgumentException e) {
282: // try the next one, if any
283: }
284: }
285: }
286: String msg = "No valid input event found for action with key '"
287: + name + "'";
288: if (strokes.size() > 0)
289: msg += " (tried " + strokes + ")";
290: throw new ActionFailedException(msg);
291: }
292:
293: /** Return a shared instance of JComponentTester. */
294: public static JComponentTester getTester(JComponent c) {
295: return (JComponentTester) ComponentTester
296: .getTester(JComponent.class);
297: }
298:
299: /**
300: * @param cr A custom cell renderer
301: * @return Either the name in the case or a label, or uses reflection to
302: * call "getText" which covers a range of other components and custom
303: * renderers
304: */
305:
306: public static String convertRendererToString(Component cr) {
307: String string = null;
308: if (cr instanceof JLabel) {
309: String label = ((JLabel) cr).getText();
310: if (label != null)
311: label = label.trim();
312: if (!"".equals(label)
313: && !ArgumentParser.isDefaultToString(label)) {
314: string = label;
315: }
316: } else {
317:
318: // Fall back to looking for a generic getText call
319: // This will work for JTextField and JButton for examples
320: // and provides an extension point for other renderes
321: //
322: Class cls = cr.getClass();
323: String className = cls.getName();
324: className = className
325: .substring(className.lastIndexOf(".") + 1);
326: try {
327: Method method;
328: method = cls.getMethod("getText", new Class[0]);
329: if (method != null) {
330: String label;
331:
332: label = (String) method.invoke(cr, new Object[0]);
333: if (label != null)
334: label = label.trim();
335: if (!"".equals(label)) {
336: string = label;
337: }
338: }
339: } catch (IllegalAccessException e) {
340: Log.log("Error calling getText on " + className, e);
341: } catch (InvocationTargetException e) {
342: Log.log("Error calling getText on " + className, e);
343: } catch (NoSuchMethodException e) {
344: Log.log("Cannot find getText method on render "
345: + className, e);
346: }
347: }
348:
349: // If we have a string do the argument converter thing
350: //
351:
352: if (string != null) {
353: String parsed = ArgumentParser.toString(string);
354: String filtered = parsed == ArgumentParser.DEFAULT_TOSTRING ? null
355: : parsed;
356: return filtered;
357: } else {
358: return null;
359: }
360: }
361:
362: }
|