001: package abbot.editor.widgets;
002:
003: import java.awt.*;
004: import java.awt.event.AWTEventListener;
005: import java.awt.event.MouseAdapter;
006: import java.awt.event.MouseEvent;
007: import java.awt.event.MouseMotionListener;
008: import java.lang.ref.WeakReference;
009: import java.util.Map;
010: import java.util.WeakHashMap;
011: import javax.swing.JComponent;
012: import javax.swing.JFrame;
013: import javax.swing.JLabel;
014: import javax.swing.JLayeredPane;
015: import javax.swing.JPanel;
016: import javax.swing.JScrollPane;
017: import javax.swing.JTabbedPane;
018: import javax.swing.JTable;
019: import javax.swing.JTextArea;
020: import javax.swing.JTextField;
021: import javax.swing.JViewport;
022: import javax.swing.RootPaneContainer;
023: import javax.swing.SwingUtilities;
024: import javax.swing.border.*;
025: import junit.extensions.abbot.ComponentTestFixture;
026: import junit.extensions.abbot.TestHelper;
027: import junit.extensions.abbot.Timer;
028: import abbot.tester.FrameTester;
029: import abbot.tester.JTabbedPaneLocation;
030: import abbot.tester.JTabbedPaneTester;
031: import abbot.tester.JTextComponentTester;
032: import abbot.Log;
033:
034: public class AbstractComponentDecoratorTest extends
035: ComponentTestFixture {
036:
037: private static final int SIZE = 100;
038: private static final Color COLOR = Color.red;
039: private static final Color BICOLOR = Color.black;
040: private static final Point COLORED = new Point(1, 1);
041:
042: private void wait(Component c, int x, int y, Color color) {
043: // X11/Linux is a little slow in painting
044: long start = System.currentTimeMillis();
045: Color actual = null;
046: do {
047: actual = getRobot().sample(c, x, y);
048: if (System.currentTimeMillis() - start > 2000)
049: break;
050: } while (!actual.equals(color));
051: }
052:
053: private void checkDecorationBounds(String message, Component comp) {
054: checkDecorationBounds(message, comp, COLOR);
055: }
056:
057: private void checkDecorationBounds(String message, Component comp,
058: Color expected) {
059: // Paint one pixel in from the edges, or of dimension SIZExSIZE,
060: // whichever is smaller
061: int h = Math.min(comp.getHeight() - 1, SIZE);
062: int w = Math.min(comp.getWidth() - 1, SIZE);
063: Point[] CORNERS = { new Point(COLORED.x, COLORED.y),
064: new Point(COLORED.x, COLORED.y + h - 1),
065: new Point(COLORED.x + w - 1, COLORED.y + h - 1),
066: new Point(COLORED.x + w - 1, COLORED.y), };
067: Point[] XCORNERS = { new Point(0, 0),
068: new Point(0, comp.getHeight() - 1),
069: new Point(comp.getWidth() - 1, comp.getHeight() - 1),
070: new Point(comp.getWidth() - 1, 0), };
071: wait(comp, CORNERS[0].x, CORNERS[0].y, expected);
072: for (int i = 0; i < CORNERS.length; i++) {
073: Color actual = getRobot().sample(comp, CORNERS[i].x,
074: CORNERS[i].y);
075: assertEquals(message + "@" + CORNERS[i], expected, actual);
076: }
077: for (int i = 0; i < XCORNERS.length; i++) {
078: Color actual = getRobot().sample(comp, XCORNERS[i].x,
079: XCORNERS[i].y);
080: if (expected == COLOR) {
081: assertFalse(message + "@" + XCORNERS[i], expected
082: .equals(actual));
083: } else {
084: assertEquals(message + "@" + XCORNERS[i], expected,
085: actual);
086: }
087: }
088: }
089:
090: private class Decorator extends AbstractComponentDecorator {
091: public boolean bicolor;
092:
093: public Decorator(JComponent c) {
094: this (c, 1);
095: }
096:
097: public Decorator(JComponent c, int offset) {
098: this (c, offset, TOP);
099: }
100:
101: public Decorator(JComponent c, int offset, int position) {
102: super (c, offset, position);
103: c.putClientProperty("decorator", this );
104: }
105:
106: /** Decorate all but the outermost pixels (up to SIZE max w/h). */
107: protected Rectangle getDecorationBounds() {
108: Rectangle r = new Rectangle(super .getDecorationBounds());
109: r.setLocation(COLORED.x, COLORED.y);
110: r.width = Math.min(getComponent().getWidth() - 1, SIZE);
111: r.height = Math.min(getComponent().getHeight() - 1, SIZE);
112: return r;
113: }
114:
115: /** Fill the decoration bounds. */
116: public void paint(Graphics g) {
117: Rectangle b = getDecorationBounds();
118: g.setColor(COLOR);
119: g.fillRect(b.x, b.y, b.width, b.height);
120: g.drawRect(b.x, b.y, b.width - 1, b.height - 1);
121: if (bicolor) {
122: g.setColor(BICOLOR);
123: g.fillRect(b.x + b.width / 2, b.y + b.height / 2,
124: b.width / 2, b.height / 2);
125: g.drawRect(b.x + b.width / 2, b.y + b.height / 2,
126: b.width / 2 - 1, b.height / 2 - 1);
127: }
128: }
129:
130: public Object getCursor() {
131: return getPainter().getCursor();
132: }
133: }
134:
135: private class MouseWatcher extends MouseAdapter implements
136: MouseMotionListener {
137: public boolean mousePressed;
138: public boolean mouseMoved;
139:
140: public void mousePressed(MouseEvent e) {
141: mousePressed = true;
142: }
143:
144: public void mouseDragged(MouseEvent e) {
145: }
146:
147: public void mouseMoved(MouseEvent e) {
148: mouseMoved = true;
149: }
150: }
151:
152: protected void setUp() {
153: //Log.addDebugClass(AbstractComponentDecorator.class);
154: //Log.setSynchronous(true);
155: }
156:
157: public void testLayeredPaneBounds() {
158: JLabel label = new JLabel(getName());
159: Frame frame = showFrame(label);
160: JLayeredPane lp = label.getRootPane().getLayeredPane();
161: Rectangle visible = lp.getVisibleRect();
162: Rectangle bounds = new Rectangle(0, 0, lp.getWidth(), lp
163: .getHeight());
164:
165: assertEquals("Layered pane is entirely visible", bounds,
166: visible);
167: Insets insets = frame.getInsets();
168: assertEquals("Layered pane is within Frame width", frame
169: .getWidth()
170: - insets.left - insets.right, lp.getWidth());
171: assertEquals("Layered pane is within Frame height", frame
172: .getHeight()
173: - insets.top - insets.bottom, lp.getHeight());
174:
175: }
176:
177: public void testIsVisible() {
178: JLabel label = new JLabel(getName());
179: Decorator d = new Decorator(label) {
180: public Rectangle getDecorationBounds() {
181: return new Rectangle(-100, -100, 1, 1);
182: }
183: };
184: showFrame(label);
185: assertFalse(
186: "Decoration should report not visible when decoration "
187: + "lies outside of the layered pane", d
188: .isVisible());
189: // Check visibility
190: // don't set cursor on main when not visible (prevents flicker
191: // in aux ghost frames)
192: // still need to check primary for flicker
193:
194: }
195:
196: public void testClipToLayeredPane() {
197: JLabel label = new JLabel(getName());
198: showFrame(label);
199: JLayeredPane lp = label.getRootPane().getLayeredPane();
200: final Decorator d = new Decorator(label);
201: final Point p = SwingUtilities.convertPoint(lp, 0, 0, label);
202: Point p2 = SwingUtilities.convertPoint(lp, lp.getWidth(), lp
203: .getHeight(), label);
204: p.x -= 10;
205: p.y -= 10;
206: final int w = p2.x - p.x;
207: final int h = p2.y - p.y;
208: invokeAndWait(new Runnable() {
209: public void run() {
210: d.setDecorationBounds(p.x, p.y, w, h);
211: d.repaint();
212: }
213: });
214: Rectangle bounds = new Rectangle(0, 0, lp.getWidth(), lp
215: .getHeight());
216: assertTrue(
217: "Painter bounds should be clipped to the layered pane: lp="
218: + bounds + ", painter="
219: + d.getPainter().getBounds(), bounds.contains(d
220: .getPainter().getBounds()));
221: }
222:
223: public void testNoEffectOnInputEvents() {
224: MouseWatcher watcher = new MouseWatcher();
225: JLabel label = new JLabel("input");
226: showFrame(label);
227: getRobot().mouseMove(label);
228: getRobot().waitForIdle();
229: label.addMouseListener(watcher);
230: label.addMouseMotionListener(watcher);
231: new Decorator(label);
232: getRobot().waitForIdle();
233: getRobot().click(label);
234: getRobot().waitForIdle();
235: assertTrue("Input not passed to label", watcher.mousePressed);
236: getRobot().mouseMove(label, COLORED.x, COLORED.y);
237: assertTrue("Motion not passed to label", watcher.mouseMoved);
238: }
239:
240: public void testSynchCursor() {
241: JLabel label = new JLabel(getName());
242: Decorator d = new Decorator(label);
243: showFrame(label);
244: label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
245: assertEquals("Decorator cursor should match",
246: label.getCursor(), d.getCursor());
247:
248: }
249:
250: public void testDecorateBeforeShow() {
251: JLabel label = new JLabel(getName());
252: new Decorator(label);
253: showFrame(label);
254: checkDecorationBounds(
255: "Decorator added before show didn't take effect", label);
256: }
257:
258: // TODO: BOTTOM position doesn't work
259: /*
260: public void testDecorateBeforeShowBottom() {
261: JLabel label = new JLabel(getName());
262: //new Decorator(label, 1, Decorator.BOTTOM);
263: new Decorator(label, 1, 0);
264: showFrame(label);
265: getRobot().delay(60000);
266: checkDecorationBounds("Decorator (bottom) added before show didn't take effect",
267: label);
268: }
269: */
270: public void testDecorateAfterShow() {
271: JLabel label = new JLabel(getName());
272: showFrame(label);
273: Decorator decorator = new Decorator(label);
274: getRobot().waitForIdle();
275: checkDecorationBounds(
276: "Decoration not applied when component already showing",
277: label);
278: decorator.dispose();
279: getRobot().waitForIdle();
280: Color actual = getRobot().sample(label, COLORED.x, COLORED.y);
281: assertFalse("Decoration not removed", COLOR.equals(actual));
282: }
283:
284: public void testDecorateAfterComponentRepaint() {
285: JLabel label = new JLabel(getName());
286: label.setBackground(Color.gray);
287: label.setOpaque(true);
288: showFrame(label);
289:
290: Decorator decorator = new Decorator(label);
291: getRobot().waitForIdle();
292: checkDecorationBounds("Decoration not initially applied", label);
293:
294: label.repaint();
295: getRobot().waitForIdle();
296: checkDecorationBounds(
297: "Decoration not reapplied after component repaint()",
298: label);
299:
300: decorator.dispose();
301: decorator = null;
302: getRobot().waitForIdle();
303: checkDecorationBounds("Decoration not removed after dispose",
304: label, label.getBackground());
305: }
306:
307: public void testDecorateAfterLayerChange() {
308: final JLabel label = new JLabel(getName());
309: label.setBackground(Color.gray);
310: label.setOpaque(true);
311: new Decorator(label);
312: Frame frame = showFrame(label);
313: assertTrue("Wrong frame type",
314: frame instanceof RootPaneContainer);
315: final JLayeredPane lp = ((RootPaneContainer) frame)
316: .getLayeredPane();
317: final Integer[] LAYERS = { JLayeredPane.DEFAULT_LAYER,
318: JLayeredPane.PALETTE_LAYER, JLayeredPane.MODAL_LAYER,
319: JLayeredPane.POPUP_LAYER, JLayeredPane.DRAG_LAYER, };
320: final String[] NAMES = { "default", "palette", "modal",
321: "popup", "drag", };
322: final Container content = ((RootPaneContainer) frame)
323: .getContentPane();
324: checkDecorationBounds("Initial decoration missing", label);
325: for (int i = 0; i < LAYERS.length; i++) {
326: final int idx = i;
327: invokeAndWait(new Runnable() {
328: public void run() {
329: label.setText("Decorated on " + NAMES[idx]
330: + " layer (" + LAYERS[idx] + ")");
331: lp.setLayer(content, LAYERS[idx].intValue());
332: }
333: });
334: checkDecorationBounds(
335: "Decorator missing when component layer "
336: + "changed to " + NAMES[i] + " layer ("
337: + LAYERS[idx] + ")", label);
338: }
339: }
340:
341: public void testDecorateAfterHierarchyChange() {
342: JLabel label = new JLabel(getName());
343: JPanel p1 = new JPanel();
344: JPanel p2 = new JPanel();
345: p2.add(p1);
346: p1.add(label);
347:
348: new Decorator(label);
349: Frame f1 = showFrame(p2);
350: JPanel p3 = new JPanel();
351: showFrame(p3, f1.getSize());
352: p3.add(p1);
353: p3.revalidate();
354: getRobot().waitForIdle();
355: checkDecorationBounds(
356: "Decoration not applied after hierarchy change", label);
357: }
358:
359: public void testDecorateScrolledComponent() {
360: Object data[][] = { { "one", "two" }, { "three", "four" },
361: { "x", "y" }, { "three", "four" }, { "one", "two" },
362: { "z", "w" }, { "one", "two" }, { "three", "four" },
363: { "one", "two" }, { "a", "1" }, { "b", "2" },
364: { "c", "3" }, { "d", "4" }, { "e", "5" }, { "f", "6" },
365: { "g", "7" }, { "h", "8" }, { "i", "9" },
366: { "j", "10" }, };
367: String[] cols = { "column 1", "column 2" };
368: final JTable table = new JTable(data, cols);
369: JScrollPane pane = new JScrollPane(table);
370:
371: showFrame(pane, new Dimension(200, 200));
372: new Decorator(table);
373: Rectangle rect = table.getParent().getBounds();
374: wait(table, COLORED.x, COLORED.y, COLOR);
375: Color actual = getRobot().sample(table, COLORED.x, COLORED.y);
376: assertEquals(
377: "Decoration should be applied to component within viewport",
378: COLOR, actual);
379: getRobot().invokeAndWait(new Runnable() {
380: public void run() {
381: table.scrollRectToVisible(table.getCellRect(table
382: .getRowCount() - 1, 0, true));
383: }
384: });
385: actual = getRobot().sample(pane, rect.x, rect.y);
386: assertNotSame("Decoration should scroll with component", COLOR,
387: actual);
388: getRobot().invokeAndWait(new Runnable() {
389: public void run() {
390: table
391: .scrollRectToVisible(table.getCellRect(0, 0,
392: true));
393: }
394: });
395: actual = getRobot().sample(table, COLORED.x, COLORED.y);
396: assertEquals(
397: "Decoration should be reapplied to component after scroll",
398: COLOR, actual);
399: }
400:
401: public void testDecorateResizedViewport() {
402: final JTextArea p = new JTextArea(getName());
403: p.setName("**TEST**");
404: p.setPreferredSize(new Dimension(200, 200));
405: new AbstractComponentDecorator(p) {
406: public void paint(Graphics g) {
407: Rectangle b = getDecorationBounds();
408: g.setColor(Color.red);
409: g.fillRect(b.x, b.y, b.width, b.height);
410: }
411: };
412: Frame f = showFrame(new JScrollPane(p), new Dimension(100, 100));
413: getRobot().invokeAndWait(new Runnable() {
414: public void run() {
415: p.setSize(500, 100);
416: }
417: });
418: FrameTester ft = new FrameTester();
419: ft.actionResizeBy(f, 100, 0);
420: Rectangle rect = p.getVisibleRect();
421: assertEquals("Newly exposed region should be colored", COLOR,
422: getRobot().sample(p, rect.width - 1, rect.height - 1));
423: }
424:
425: // BOTTOM doesn't currently work
426: /*
427: public void testDecorateBottomAcrossScrollBarAddition() {
428: final JTextArea text = new JTextArea(getName() + "\n" + getName());
429: showFrame(new JScrollPane(text), new Dimension(400, 200));
430: new AbstractComponentDecorator(text, AbstractComponentDecorator.BOTTOM) {
431: public void paint(Graphics g) {
432: Rectangle b = getDecorationBounds();
433: g.setColor(COLOR);
434: g.fillRect(b.x, b.y, b.width, b.height);
435: }
436: };
437: text.setText(text.getText() + "\n\n\n\n\n\n\n");
438: text.setCaretPosition(text.getText().length());
439: getRobot().waitForIdle();
440: Rectangle rect = text.getVisibleRect();
441: assertEquals("Component should still be fully decorated", COLOR,
442: getRobot().sample(text, rect.width-1, rect.height-1));
443: }
444: */
445: public void testDispose() throws Throwable {
446: Map map = new WeakHashMap();
447: JLabel label = new JLabel(getName());
448: Frame frame = showFrame(label);
449: Decorator decorator = new Decorator(label);
450: map.put(decorator, Boolean.TRUE);
451: map.put(label, Boolean.TRUE);
452: decorator = null;
453: frame.remove(label);
454: assertNull("Label should have no parent", label.getParent());
455: label = null;
456: getRobot().waitForIdle();
457: Timer timer = new Timer();
458: while (map.size() > 0 && timer.elapsed() < 5000) {
459: // This should get rid of the component and its decorator
460: System.gc();
461: Thread.sleep(10);
462: }
463: assertEquals("Objects still referenced after GC: "
464: + map.keySet(), 0, map.size());
465: }
466:
467: public void testHideWhenComponentHidden() throws Exception {
468: JLabel label = new JLabel(getName());
469: label.setBorder(new EmptyBorder(20, 20, 20, 20));
470: JTabbedPane pane = new JTabbedPane();
471: // explicitly set bg to avoid patterned bg on OSX
472: label.setBackground(Color.gray);
473: label.setOpaque(true);
474: pane.add("one", label);
475: JLabel label2 = new JLabel(getName());
476: label2.setBorder(new EmptyBorder(20, 20, 20, 20));
477: label2.setBackground(Color.gray);
478: label2.setOpaque(true);
479: pane.add("two", label2);
480: showFrame(pane);
481: new Decorator(label);
482: JTabbedPaneTester tester = new JTabbedPaneTester();
483: tester.actionSelectTab(pane, new JTabbedPaneLocation(1));
484: checkDecorationBounds(
485: "Decoration not hidden when target hidden", label2,
486: label2.getBackground());
487: }
488:
489: public void testViewportClipping() throws Exception {
490: final JLabel label = new JLabel(getName());
491: final int SIZE = 200;
492: label.setPreferredSize(new Dimension(SIZE, SIZE));
493: label.setBorder(new LineBorder(Color.black));
494: final Rectangle DB = new Rectangle(SIZE / 4, SIZE / 4,
495: SIZE / 2, SIZE / 2);
496: Decorator d = new Decorator(label) {
497: protected Rectangle getDecorationBounds() {
498: return DB;
499: }
500: };
501: d.bicolor = true;
502: showFrame(new JScrollPane(label), new Dimension(200, 200));
503: getRobot().invokeAndWait(new Runnable() {
504: public void run() {
505: label.scrollRectToVisible(DB);
506: }
507: });
508: Color sample1 = getRobot().sample(label, SIZE / 4, SIZE / 4);
509: assertEquals("Incorrect upper left", COLOR, sample1);
510: sample1 = getRobot().sample(label, SIZE / 2 - 1, SIZE / 2 - 1);
511: assertEquals("Incorrect center", COLOR, sample1);
512: Color sample2 = getRobot().sample(label,
513: SIZE / 4 + SIZE / 2 - 1, SIZE / 4 + SIZE / 2 - 1);
514: assertEquals("Incorrect lower right", BICOLOR, sample2);
515: }
516:
517: // Put the decorator on the background
518: public void testDecorateBackground() {
519: JLabel centerLabel = new JLabel("decorated");
520: Font font = centerLabel.getFont();
521: centerLabel.setFont(font.deriveFont(Font.BOLD,
522: font.getSize() * 4));
523: // TODO: handle decorated components with solid bg+ opaque (bg != null)
524: //jc.setBackground(Color.green);
525:
526: AbstractComponentDecorator d = new AbstractComponentDecorator(
527: centerLabel, -1) {
528: public void paint(Graphics g) {
529: Rectangle r = getDecorationBounds();
530: g.setColor(Color.red);
531: g.fillRect(r.x, r.y, r.width, r.height);
532: //g.setColor(Color.white);
533: //g.drawString("(black fg, default bg)", 20, 30);
534: }
535:
536: public Rectangle getDecorationBounds() {
537: Rectangle r = super .getDecorationBounds();
538: r.x = 1;
539: r.y = 1;
540: r.width /= 2;
541: r.height /= 2;
542: return r;
543: }
544: };
545: AbstractComponentDecorator d2 = new AbstractComponentDecorator(
546: centerLabel, -2) {
547: public void paint(Graphics g) {
548: Rectangle r = getDecorationBounds();
549: g.setColor(Color.green);
550: g.fillRect(r.x, r.y, r.width, r.height);
551: }
552:
553: public Rectangle getDecorationBounds() {
554: Rectangle r = super .getDecorationBounds();
555: r.x += r.width / 3;
556: r.y += r.height / 6;
557: r.width /= 2;
558: r.height /= 2;
559: return r;
560: }
561: };
562:
563: JPanel p = new JPanel(new BorderLayout());
564: p.setName("Content");
565: p.setBackground(Color.yellow);
566: p.add(new JLabel("Label on an opaque JPanel"),
567: BorderLayout.NORTH);
568: p.add(centerLabel, BorderLayout.CENTER);
569: p.add(new JLabel("Another Label"), BorderLayout.SOUTH);
570: showFrame(p);
571:
572: wait(centerLabel, 0, 0, Color.yellow);
573: //getRobot().delay(60000);
574: Color sample = getRobot().sample(centerLabel, 0, 0);
575: assertEquals("Component bg not properly exposesd",
576: Color.yellow, sample);
577: sample = getRobot()
578: .sample(centerLabel, centerLabel.getWidth() / 3,
579: centerLabel.getHeight() / 6);
580: assertEquals("-1 bg decorator should obscure -2", Color.red,
581: sample);
582: sample = getRobot().sample(
583: centerLabel,
584: centerLabel.getWidth() / 3 + centerLabel.getWidth() / 2
585: - 1,
586: centerLabel.getHeight() / 6 + centerLabel.getHeight()
587: / 2 - 1);
588: assertEquals("Second decorator not applied", Color.green,
589: sample);
590: }
591:
592: public static void main(String[] args) {
593: TestHelper.runTests(args, AbstractComponentDecoratorTest.class);
594: }
595: }
|