1/*
2 * Copyright (c) 1997, 2016, 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 */
23package org.netbeans.jemmy.explorer;
24
25import java.awt.AWTEvent;
26import java.awt.AWTException;
27import java.awt.BorderLayout;
28import java.awt.Color;
29import java.awt.Component;
30import java.awt.Container;
31import java.awt.Dimension;
32import java.awt.Frame;
33import java.awt.Graphics;
34import java.awt.Rectangle;
35import java.awt.Robot;
36import java.awt.Window;
37import java.awt.event.ActionEvent;
38import java.awt.event.ActionListener;
39import java.awt.event.ComponentEvent;
40import java.awt.event.ComponentListener;
41import java.awt.event.WindowEvent;
42import java.awt.event.WindowListener;
43import java.awt.image.BufferedImage;
44import java.io.ByteArrayOutputStream;
45import java.io.File;
46import java.io.FileInputStream;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.io.OutputStream;
50import java.io.OutputStreamWriter;
51import java.io.PrintWriter;
52import java.lang.reflect.Field;
53import java.lang.reflect.InvocationTargetException;
54import java.lang.reflect.Method;
55import java.lang.reflect.Modifier;
56import java.util.Arrays;
57import java.util.Collections;
58import java.util.Comparator;
59import java.util.Hashtable;
60import java.util.Properties;
61import java.util.Vector;
62
63import javax.swing.DefaultListModel;
64import javax.swing.JButton;
65import javax.swing.JCheckBox;
66import javax.swing.JComboBox;
67import javax.swing.JDialog;
68import javax.swing.JFileChooser;
69import javax.swing.JFrame;
70import javax.swing.JLabel;
71import javax.swing.JList;
72import javax.swing.JPanel;
73import javax.swing.JScrollPane;
74import javax.swing.JSplitPane;
75import javax.swing.JTabbedPane;
76import javax.swing.JTable;
77import javax.swing.JTextArea;
78import javax.swing.JTextField;
79import javax.swing.JTree;
80import javax.swing.ListCellRenderer;
81import javax.swing.border.BevelBorder;
82import javax.swing.event.ChangeEvent;
83import javax.swing.event.ChangeListener;
84import javax.swing.event.ListSelectionEvent;
85import javax.swing.event.ListSelectionListener;
86import javax.swing.event.TreeModelListener;
87import javax.swing.event.TreeSelectionEvent;
88import javax.swing.event.TreeSelectionListener;
89import javax.swing.filechooser.FileFilter;
90import javax.swing.table.DefaultTableModel;
91import javax.swing.tree.TreeCellRenderer;
92import javax.swing.tree.TreeModel;
93import javax.swing.tree.TreePath;
94
95import org.netbeans.jemmy.ClassReference;
96import org.netbeans.jemmy.QueueTool;
97import org.netbeans.jemmy.TestOut;
98import org.netbeans.jemmy.operators.Operator;
99import org.netbeans.jemmy.util.Dumper;
100
101/**
102 * An application allowing to explore a Java GUI application. Could be executed
103 * by command: <br>
104 * <pre>
105 * java "application java options" \
106 *   org.netbeans.jemmy.explorer.GUIBrowser \
107 *   "application main class" \
108 *   "application parameters"
109 * </pre> or from java code by {@code GUIBrowser.showBrowser()} method
110 * using.
111 *
112 * @author Alexandre Iline (alexandre.iline@oracle.com)
113 */
114public class GUIBrowser extends JFrame {
115
116    private static String WINDOWS_TAB = "Subwindows";
117    private static String COMPONENTS_TAB = "Hierarchy";
118    private static String PROPERTIES_TAB = "Properties";
119    private static String REFLECTION_TAB = "Reflection";
120    private static String EVENT_TAB = "Events";
121    private static String IMAGE_TAB = "Image";
122
123    boolean exit;
124    PropertyDialog propDialog;
125    RootNode root;
126    QueueTool qt;
127    JTextField refreshDelay;
128    JTree mainTree;
129    JLabel status;
130    JButton viewButton;
131    JButton expandButton;
132    JSplitPane split;
133    ByteArrayOutputStream dumpData;
134    PrintWriter dumpWriter;
135
136    private GUIBrowser(boolean exitNecessary) {
137        super("GUI Browser");
138
139        dumpData = new ByteArrayOutputStream();
140        dumpWriter = new PrintWriter(new OutputStreamWriter(dumpData));
141
142        exit = exitNecessary;
143        propDialog = new PropertyDialog(this);
144        qt = new QueueTool();
145        qt.setOutput(TestOut.getNullOutput());
146        root = new RootNode();
147
148        mainTree = new JTree(root.getWindowModel());
149        mainTree.setCellRenderer(new WindowRenderer());
150        mainTree.setEditable(false);
151
152        refreshDelay = new JTextField(3);
153        refreshDelay.setText("0");
154
155        viewButton = new JButton("View");
156        viewButton.setEnabled(false);
157        viewButton.addActionListener(new ActionListener() {
158            @Override
159            public void actionPerformed(ActionEvent e) {
160                new ComponentBrowser(getOwnr(),
161                        (ComponentNode) mainTree.getSelectionPath().
162                        getLastPathComponent()).
163                        setVisible(true);
164            }
165        });
166
167        expandButton = new JButton("Expand All");
168        expandButton.setEnabled(false);
169        expandButton.addActionListener(new ActionListener() {
170            @Override
171            public void actionPerformed(ActionEvent e) {
172                expandAll(mainTree,
173                        mainTree.getSelectionPath());
174            }
175        });
176
177        JButton refreshButton = new JButton("Reload in ...");
178        refreshButton.addActionListener(new ActionListener() {
179            @Override
180            public void actionPerformed(ActionEvent e) {
181                reload(Integer.parseInt(refreshDelay.getText()));
182            }
183        });
184
185        JButton dumpButton = new JButton("Dump");
186        dumpButton.addActionListener(new ActionListener() {
187            @Override
188            public void actionPerformed(ActionEvent e) {
189                JFileChooser chooser
190                        = new JFileChooser(System.getProperty("user.home"));
191                chooser.addChoosableFileFilter(new FileFilter() {
192                    @Override
193                    public boolean accept(File f) {
194                        return f.getName().endsWith(".xml");
195                    }
196
197                    @Override
198                    public String getDescription() {
199                        return "xml files";
200                    }
201
202                    @Override
203                    public String toString() {
204                        return "dumpButton.ActionListener{description = " + getDescription() + '}';
205                    }
206                });
207                chooser.showSaveDialog(GUIBrowser.this);
208                try {
209                    File file = chooser.getSelectedFile();
210                    try (OutputStream output = new FileOutputStream(file)) {
211                        output.write(dumpData.toByteArray());
212                    }
213                } catch (IOException ee) {
214                    ee.printStackTrace();
215                }
216            }
217        });
218
219        JPanel refreshPane = new JPanel();
220        refreshPane.add(viewButton);
221        refreshPane.add(expandButton);
222        refreshPane.add(new JLabel(""));
223        refreshPane.add(new JLabel(""));
224        refreshPane.add(new JLabel(""));
225        refreshPane.add(new JLabel(""));
226        refreshPane.add(new JLabel(""));
227        refreshPane.add(new JLabel(""));
228        refreshPane.add(refreshButton);
229        refreshPane.add(refreshDelay);
230        refreshPane.add(new JLabel("seconds     "));
231        refreshPane.add(dumpButton);
232
233        split = createUnderPane(mainTree);
234
235        JPanel nonStatusPane = new JPanel();
236        nonStatusPane.setLayout(new BorderLayout());
237        nonStatusPane.add(refreshPane, BorderLayout.SOUTH);
238        nonStatusPane.add(split, BorderLayout.CENTER);
239
240        split.setDividerLocation(0.8);
241
242        status = new JLabel("Reloaded");
243
244        JPanel statusPane = new JPanel();
245        statusPane.setLayout(new BorderLayout());
246        statusPane.add(status, BorderLayout.CENTER);
247        statusPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
248
249        JPanel bottomPane = new JPanel();
250        bottomPane.setLayout(new BorderLayout());
251        bottomPane.add(statusPane, BorderLayout.NORTH);
252        bottomPane.add(statusPane, BorderLayout.CENTER);
253
254        getContentPane().setLayout(new BorderLayout());
255        getContentPane().add(bottomPane, BorderLayout.SOUTH);
256        getContentPane().add(nonStatusPane, BorderLayout.CENTER);
257
258        Component[] cpss = {viewButton, expandButton};
259        mainTree.addTreeSelectionListener(new SelectionManager(cpss));
260
261        addWindowListener(new WindowListener() {
262            @Override
263            public void windowActivated(WindowEvent e) {
264            }
265
266            @Override
267            public void windowClosed(WindowEvent e) {
268                setVisible(false);
269                if (exit) {
270                    System.exit(0);
271                }
272            }
273
274            @Override
275            public void windowClosing(WindowEvent e) {
276            }
277
278            @Override
279            public void windowDeactivated(WindowEvent e) {
280            }
281
282            @Override
283            public void windowDeiconified(WindowEvent e) {
284            }
285
286            @Override
287            public void windowIconified(WindowEvent e) {
288            }
289
290            @Override
291            public void windowOpened(WindowEvent e) {
292            }
293        });
294        addComponentListener(new ComponentListener() {
295            @Override
296            public void componentHidden(ComponentEvent e) {
297            }
298
299            @Override
300            public void componentMoved(ComponentEvent e) {
301            }
302
303            @Override
304            public void componentResized(ComponentEvent e) {
305                split.setDividerLocation(0.8);
306            }
307
308            @Override
309            public void componentShown(ComponentEvent e) {
310            }
311        });
312
313        setSize(800, 400);
314    }
315
316    boolean shown = false;
317
318    @Override
319    @Deprecated
320    public void show() {
321        super.show();
322        viewButton.setEnabled(false);
323        if (!shown) {
324            split.setDividerLocation(0.8);
325            shown = true;
326        }
327    }
328
329    /**
330     * Specifies a status text.
331     *
332     * @param st a status text.
333     */
334    public void setStatus(String st) {
335        status.setText(st);
336    }
337
338    /**
339     * Method to invoke GUIBrowser from java code.
340     */
341    public static void showBrowser() {
342        showBrowser(new String[0], false);
343    }
344
345    /**
346     * Method to invoke GUIBrowser as java application.
347     *
348     * @param argv Argument array. If not empty, first element should be<br>
349     * main class of an aplication to be browsed.<br>
350     * Other elements are application parameters.
351     */
352    public static void main(String[] argv) {
353        showBrowser(argv, true);
354    }
355
356    private static void showBrowser(String[] argv, boolean exitNecessary) {
357        if (argv.length >= 1) {
358            String[] newArgv = new String[argv.length - 1];
359            System.arraycopy(argv, 1, newArgv, 0, argv.length - 1);
360            try {
361                new ClassReference(argv[0]).startApplication(newArgv);
362            } catch (ClassNotFoundException
363                    | InvocationTargetException
364                    | NoSuchMethodException e) {
365                e.printStackTrace();
366                return;
367            }
368        }
369        new GUIBrowser(exitNecessary).show();
370    }
371
372    private void reload(final int delay) {
373        viewButton.setEnabled(false);
374        expandButton.setEnabled(false);
375        new Thread(new Runnable() {
376            @Override
377            public void run() {
378                try {
379                    for (int i = delay - 1; i >= 0; i--) {
380                        setStatus("Reloading after " + Integer.toString(i) + " second");
381                        Thread.sleep(1000);
382                    }
383                    setStatus("Reloading ...");
384                    Dumper.dumpAll(dumpWriter);
385                    //qt.lock();
386                    root = new RootNode();
387                    //qt.unlock();
388                    mainTree.setModel(root.getWindowModel());
389                    setStatus("Reloaded");
390                } catch (InterruptedException ignored) {
391                }
392            }
393        }).start();
394    }
395
396    private JFrame getOwnr() {
397        return this;
398    }
399
400    private class ClassNode {
401
402        Class<?> clzz;
403
404        protected ClassNode() {
405            clzz = null;
406        }
407
408        public ClassNode(Class<?> clzz) {
409            this.clzz = clzz;
410        }
411
412        public ClassNode[] getSuperClasses() {
413            int count = 0;
414            Class<?> parent = clzz;
415            while ((parent = parent.getSuperclass()) != null) {
416                count++;
417            }
418            Class<?>[] interfaces = clzz.getInterfaces();
419            ClassNode[] result = new ClassNode[count + interfaces.length + 1];
420            result[0] = new SuperClassNode(clzz);
421            count = 1;
422            parent = clzz;
423            while ((parent = parent.getSuperclass()) != null) {
424                result[count] = new SuperClassNode(parent);
425                count++;
426            }
427            for (int i = count; i < count + interfaces.length; i++) {
428                result[i] = new InterfaceNode(interfaces[i - count]);
429            }
430            return result;
431        }
432
433        public String toString() {
434            if (clzz.isArray()) {
435                return clzz.getComponentType().getName() + "[]";
436            } else {
437                return clzz.getName();
438            }
439        }
440
441        public TreeModel getMethodsModel() {
442            return new ClassModel(this);
443        }
444
445        public ClassNode[] getSubNodes() {
446            Vector<ClassNode> res = new Vector<>();
447            Field[] fields = clzz.getFields();
448            Arrays.sort(fields, new Comparator<Field>() {
449                @Override
450                public int compare(Field f1, Field f2) {
451                    return f1.getName().compareTo(f2.getName());
452                }
453            });
454            Method[] mtds = clzz.getMethods();
455            Arrays.sort(mtds, new Comparator<Method>() {
456                @Override
457                public int compare(Method m1, Method m2) {
458                    return m1.getName().compareTo(m2.getName());
459                }
460            });
461            for (Field field : fields) {
462                if (field.getDeclaringClass().getName().equals(clzz.getName())) {
463                    res.add(new FieldNode(field));
464                }
465            }
466            for (Method mtd : mtds) {
467                if (mtd.getDeclaringClass().getName().equals(clzz.getName())) {
468                    res.add(new MethodNode(mtd));
469                }
470            }
471            ClassNode[] result = new ClassNode[res.size()];
472            for (int i = 0; i < result.length; i++) {
473                result[i] = res.get(i);
474            }
475            return result;
476        }
477    }
478
479    private class SuperClassNode extends ClassNode {
480
481        public SuperClassNode(Class<?> clzz) {
482            super(clzz);
483        }
484
485        public String toString() {
486            return "Class: " + super.toString();
487        }
488    }
489
490    private class InterfaceNode extends ClassNode {
491
492        public InterfaceNode(Class<?> clzz) {
493            super(clzz);
494        }
495
496        public String toString() {
497            return "Interfac: " + super.toString();
498        }
499    }
500
501    private class FieldNode extends ClassNode {
502
503        Field field;
504
505        public FieldNode(Field fld) {
506            super(fld.getType());
507            field = fld;
508        }
509
510        public String toString() {
511            return ("Field: "
512                    + Modifier.toString(field.getModifiers()) + " "
513                    + super.toString() + " "
514                    + field.getName());
515        }
516    }
517
518    private class MethodNode extends ClassNode {
519
520        Method method;
521
522        public MethodNode(Method mtd) {
523            super(mtd.getReturnType());
524            method = mtd;
525        }
526
527        public String toString() {
528            return ("Method: "
529                    + Modifier.toString(method.getModifiers()) + " "
530                    + super.toString() + " "
531                    + method.getName());
532
533        }
534
535        public ClassNode[] getParameters() {
536            Class<?>[] ptps = method.getParameterTypes();
537            ClassNode[] result = new ClassNode[ptps.length];
538            for (int i = 0; i < ptps.length; i++) {
539                result[i] = new ClassNode(ptps[i]);
540            }
541            return result;
542        }
543    }
544
545    private class ComponentNode extends ClassNode {
546
547        protected Hashtable<String, Object> props;
548        protected String clss = "";
549        protected String compToString = "";
550        Component comp;
551        ComponentImageProvider image;
552        protected int x, y, w, h;
553
554        protected ComponentNode() {
555            props = new Hashtable<>();
556        }
557
558        public ComponentNode(Component comp) {
559            super(comp.getClass());
560            try {
561                props = Operator.createOperator(comp).getDump();
562            } catch (Exception e) {
563                props = new Hashtable<>();
564            }
565            clss = comp.getClass().getName();
566            compToString = comp.toString();
567            this.comp = comp;
568            x = comp.getLocationOnScreen().x;
569            y = comp.getLocationOnScreen().y;
570            w = comp.getWidth();
571            h = comp.getHeight();
572        }
573
574        public String toString() {
575            return clss;
576        }
577
578        public Hashtable<String, Object> getProperties() {
579            return props;
580        }
581
582        public String getToString() {
583            return compToString;
584        }
585
586        public void setComponentImageProvider(ComponentImageProvider provider) {
587            image = provider;
588        }
589
590        public BufferedImage getImage() {
591            if (image != null) {
592                return image.getImage(x, y, w, h);
593            } else {
594                return null;
595            }
596        }
597    }
598
599    private class ContainerNode extends ComponentNode {
600
601        protected ComponentNode[] comps;
602
603        protected ContainerNode() {
604            super();
605            comps = new ComponentNode[0];
606        }
607
608        public ContainerNode(Container comp) {
609            super(comp);
610            Component[] cmps = comp.getComponents();
611            Vector<ComponentNode> wwns = new Vector<>();
612            for (Component cmp : cmps) {
613                if (cmp != null && (propDialog.showAll || cmp.isVisible())) {
614                    if (cmp instanceof Container) {
615                        wwns.add(new ContainerNode((Container) cmp));
616                    } else {
617                        wwns.add(new ComponentNode(cmp));
618                    }
619                }
620            }
621            comps = new ComponentNode[wwns.size()];
622            for (int i = 0; i < wwns.size(); i++) {
623                comps[i] = wwns.get(i);
624            }
625        }
626
627        public ComponentNode[] getComponents() {
628            return comps;
629        }
630
631        public int getComponentIndex(ComponentNode comp) {
632            for (int i = 0; i < comps.length; i++) {
633                if (comps[i].equals(comp)) {
634                    return i;
635                }
636            }
637            return -1;
638        }
639
640        @Override
641        public void setComponentImageProvider(ComponentImageProvider provider) {
642            super.setComponentImageProvider(provider);
643            for (ComponentNode comp1 : comps) {
644                comp1.setComponentImageProvider(provider);
645            }
646        }
647
648        public ComponentModel getComponentModel() {
649            return new ComponentModel(this);
650        }
651    }
652
653    private class WindowNode extends ContainerNode {
654
655        protected WindowNode[] wins;
656        String title;
657
658        protected WindowNode() {
659            super();
660            wins = new WindowNode[0];
661            title = "All Frames";
662            clss = "";
663        }
664
665        public WindowNode(Window win) {
666            super(win);
667            Window[] wns = win.getOwnedWindows();
668            Vector<WindowNode> wwns = new Vector<>();
669            for (Window wn : wns) {
670                if (propDialog.showAll || wn.isVisible()) {
671                    wwns.add(new WindowNode(wn));
672                }
673            }
674            wins = new WindowNode[wwns.size()];
675            for (int i = 0; i < wwns.size(); i++) {
676                wins[i] = wwns.get(i);
677            }
678            title = win.toString();
679            clss = win.getClass().getName();
680            BufferedImage image = null;
681            try {
682                image = new Robot().
683                        createScreenCapture(new Rectangle(win.getLocationOnScreen(),
684                                win.getSize()));
685            } catch (AWTException e) {
686                e.printStackTrace();
687            }
688            setComponentImageProvider(new ComponentImageProvider(image, x, y));
689        }
690
691        public WindowNode[] getWindows() {
692            return wins;
693        }
694
695        public int getWindowIndex(WindowNode node) {
696            for (int i = 0; i < wins.length; i++) {
697                if (wins[i].equals(node)) {
698                    return i;
699                }
700            }
701            return -1;
702        }
703
704        public WindowModel getWindowModel() {
705            return new WindowModel(this);
706        }
707
708        public String toString() {
709            return clss + " \"" + title + "\"";
710        }
711    }
712
713    private class RootNode extends WindowNode {
714
715        public RootNode() {
716            super();
717            Window[] wns = Frame.getFrames();
718            wins = new WindowNode[wns.length];
719            int count = 0;
720            for (Window wn1 : wns) {
721                if (propDialog.showAll || wn1.isVisible()) {
722                    count++;
723                }
724            }
725            wins = new WindowNode[count];
726            count = 0;
727            for (Window wn : wns) {
728                if (propDialog.showAll || wn.isVisible()) {
729                    wins[count] = new WindowNode(wn);
730                    count++;
731                }
732            }
733        }
734    }
735
736    private static class ClassModel implements TreeModel {
737
738        ClassNode clsn;
739
740        ClassModel(ClassNode clsn) {
741            this.clsn = clsn;
742        }
743
744        @Override
745        public void addTreeModelListener(TreeModelListener l) {
746        }
747
748        @Override
749        public Object getChild(Object parent, int index) {
750            if (parent == clsn) {
751                return clsn.getSuperClasses()[index];
752            } else if (parent instanceof SuperClassNode
753                    || parent instanceof InterfaceNode) {
754                return ((ClassNode) parent).getSubNodes()[index];
755            } else if (parent instanceof MethodNode) {
756                return ((MethodNode) parent).getParameters()[index];
757            }
758            return null;
759        }
760
761        @Override
762        public int getChildCount(Object parent) {
763            if (parent == clsn) {
764                return clsn.getSuperClasses().length;
765            } else if (parent instanceof SuperClassNode
766                    || parent instanceof InterfaceNode) {
767                return ((ClassNode) parent).getSubNodes().length;
768            } else if (parent instanceof MethodNode) {
769                return ((MethodNode) parent).getParameters().length;
770            }
771            return 0;
772        }
773
774        @Override
775        public int getIndexOfChild(Object parent, Object child) {
776            if (parent == clsn
777                    || parent instanceof MethodNode
778                    || parent instanceof SuperClassNode
779                    || parent instanceof InterfaceNode) {
780                Object[] children;
781                if (parent instanceof SuperClassNode
782                        || parent instanceof InterfaceNode) {
783                    children = ((ClassNode) parent).getSuperClasses();
784                } else if (parent instanceof MethodNode) {
785                    children = ((MethodNode) parent).getParameters();
786                } else {
787                    children = clsn.getSuperClasses();
788                }
789                for (int i = 0; i < children.length; i++) {
790                    if (children.equals(child)) {
791                        return i;
792                    }
793                }
794            }
795            return 0;
796        }
797
798        @Override
799        public Object getRoot() {
800            return clsn;
801        }
802
803        @Override
804        public boolean isLeaf(Object node) {
805            return getChildCount(node) == 0;
806        }
807
808        @Override
809        public void removeTreeModelListener(TreeModelListener l) {
810        }
811
812        @Override
813        public void valueForPathChanged(TreePath path, Object newValue) {
814        }
815    }
816
817    private static class WindowModel implements TreeModel {
818
819        WindowNode win;
820
821        WindowModel(WindowNode win) {
822            this.win = win;
823        }
824
825        @Override
826        public void addTreeModelListener(TreeModelListener l) {
827        }
828
829        @Override
830        public Object getChild(Object parent, int index) {
831            return ((WindowNode) parent).getWindows()[index];
832        }
833
834        @Override
835        public int getChildCount(Object parent) {
836            return ((WindowNode) parent).getWindows().length;
837        }
838
839        @Override
840        public int getIndexOfChild(Object parent, Object child) {
841            return ((WindowNode) parent).getWindowIndex((WindowNode) child);
842        }
843
844        @Override
845        public Object getRoot() {
846            return win;
847        }
848
849        @Override
850        public boolean isLeaf(Object node) {
851            return ((WindowNode) node).getWindows().length == 0;
852        }
853
854        @Override
855        public void removeTreeModelListener(TreeModelListener l) {
856        }
857
858        @Override
859        public void valueForPathChanged(TreePath path, Object newValue) {
860        }
861    }
862
863    private static class ComponentModel implements TreeModel {
864
865        ContainerNode cont;
866
867        ComponentModel(ContainerNode cont) {
868            this.cont = cont;
869        }
870
871        @Override
872        public void addTreeModelListener(TreeModelListener l) {
873        }
874
875        @Override
876        public Object getChild(Object parent, int index) {
877            if (parent instanceof ContainerNode) {
878                return ((ContainerNode) parent).getComponents()[index];
879            } else {
880                return "";
881            }
882        }
883
884        @Override
885        public int getChildCount(Object parent) {
886            if (parent instanceof ContainerNode) {
887                return ((ContainerNode) parent).getComponents().length;
888            } else {
889                return 0;
890            }
891        }
892
893        @Override
894        public int getIndexOfChild(Object parent, Object child) {
895            if (parent instanceof ContainerNode) {
896                return ((ContainerNode) parent).getComponentIndex((ComponentNode) child);
897            } else {
898                return -1;
899            }
900        }
901
902        @Override
903        public Object getRoot() {
904            return cont;
905        }
906
907        @Override
908        public boolean isLeaf(Object node) {
909            if (node instanceof ContainerNode) {
910                return ((ContainerNode) node).getComponents().length == 0;
911            } else {
912                return true;
913            }
914        }
915
916        @Override
917        public void removeTreeModelListener(TreeModelListener l) {
918        }
919
920        @Override
921        public void valueForPathChanged(TreePath path, Object newValue) {
922        }
923    }
924
925    private static class WindowRenderer implements TreeCellRenderer, ListCellRenderer<ClassNode> {
926
927        public WindowRenderer() {
928        }
929
930        @Override
931        public Component getTreeCellRendererComponent(JTree tree,
932                Object value,
933                boolean selected,
934                boolean expanded,
935                boolean leaf,
936                int row,
937                boolean hasFocus) {
938            return get((ClassNode) value, selected);
939        }
940
941        @Override
942        public Component getListCellRendererComponent(JList<? extends ClassNode> list,
943                ClassNode value,
944                int index,
945                boolean isSelected,
946                boolean cellHasFocus) {
947            return get(value, isSelected);
948        }
949
950        private Component get(ClassNode value, boolean selected) {
951            Component result = new JLabel(value.toString());
952            if (selected) {
953                result.setBackground(Color.blue);
954                result.setForeground(Color.white);
955                JPanel resPane = new JPanel();
956                resPane.setLayout(new BorderLayout());
957                resPane.add(result, BorderLayout.CENTER);
958                return resPane;
959            } else {
960                return result;
961            }
962        }
963    }
964
965    private static class PropertyDialog extends JDialog {
966
967        public boolean showAll = false;
968        JComboBox<String> visibleCombo;
969        JCheckBox showToString;
970        JCheckBox showReflection;
971        JCheckBox showEvents;
972        DefaultListModel<String> viewTabs;
973        JList<String> orderList;
974        JButton up;
975        JButton down;
976        Properties props;
977        File propFile;
978
979        public PropertyDialog(JFrame owner) {
980            super(owner, "Properties", true);
981
982            propFile = new File(System.getProperty("user.home")
983                    + System.getProperty("file.separator")
984                    + ".guibrowser");
985
986            props = new Properties();
987
988            String[] cpItems = {"Showing", "All"};
989            visibleCombo = new JComboBox<>(cpItems);
990            visibleCombo.addActionListener(new ActionListener() {
991                @Override
992                public void actionPerformed(ActionEvent e) {
993                    showAll = (visibleCombo.getSelectedIndex() == 1);
994                }
995            });
996
997            JPanel visiblePane = new JPanel();
998            visiblePane.add(new JLabel("Show "));
999            visiblePane.add(visibleCombo);
1000
1001            showToString = new JCheckBox("Show toString() method result");
1002            showToString.setSelected(true);
1003
1004            JPanel compTreePane = new JPanel();
1005            compTreePane.add(visiblePane);
1006
1007            viewTabs = new DefaultListModel<>();
1008            viewTabs.addElement(WINDOWS_TAB);
1009            viewTabs.addElement(COMPONENTS_TAB);
1010            viewTabs.addElement(PROPERTIES_TAB);
1011            viewTabs.addElement(REFLECTION_TAB);
1012            viewTabs.addElement(IMAGE_TAB);
1013
1014            orderList = new JList<>(viewTabs);
1015            orderList.addListSelectionListener(new ListSelectionListener() {
1016                @Override
1017                public void valueChanged(ListSelectionEvent e) {
1018                    up.setEnabled(orderList.getSelectedIndex() > -1);
1019                    down.setEnabled(orderList.getSelectedIndex() > -1);
1020                }
1021            });
1022
1023            showReflection = new JCheckBox("Show reflection tab");
1024            showReflection.setSelected(true);
1025            showReflection.addActionListener(new ActionListener() {
1026                @Override
1027                public void actionPerformed(ActionEvent e) {
1028                    if (showReflection.isSelected()) {
1029                        viewTabs.addElement(REFLECTION_TAB);
1030                    } else {
1031                        viewTabs.remove(viewTabs.indexOf(REFLECTION_TAB));
1032                    }
1033                    up.setEnabled(orderList.getSelectedIndex() > -1);
1034                    down.setEnabled(orderList.getSelectedIndex() > -1);
1035                }
1036            });
1037
1038            showEvents = new JCheckBox("Show event tab");
1039            showEvents.setSelected(true);
1040            showEvents.addActionListener(new ActionListener() {
1041                @Override
1042                public void actionPerformed(ActionEvent e) {
1043                    if (showEvents.isSelected()) {
1044                        viewTabs.addElement(EVENT_TAB);
1045                    } else {
1046                        viewTabs.remove(viewTabs.indexOf(EVENT_TAB));
1047                    }
1048                    up.setEnabled(orderList.getSelectedIndex() > -1);
1049                    down.setEnabled(orderList.getSelectedIndex() > -1);
1050                }
1051            });
1052
1053            up = new JButton("Move Up");
1054            up.setEnabled(false);
1055            up.addActionListener(new ActionListener() {
1056                @Override
1057                public void actionPerformed(ActionEvent e) {
1058                    int index = orderList.getSelectedIndex();
1059                    if (index > 0) {
1060                        viewTabs.add(index - 1,
1061                                viewTabs.remove(index));
1062                        orderList.setSelectedIndex(index - 1);
1063                    }
1064                }
1065            });
1066
1067            down = new JButton("Move Down");
1068            down.setEnabled(false);
1069            down.addActionListener(new ActionListener() {
1070                @Override
1071                public void actionPerformed(ActionEvent e) {
1072                    int index = orderList.getSelectedIndex();
1073                    if (index < viewTabs.size() - 1) {
1074                        viewTabs.add(index + 1,
1075                                viewTabs.remove(index));
1076                        orderList.setSelectedIndex(index + 1);
1077                    }
1078                }
1079            });
1080
1081            JPanel movePane = new JPanel();
1082            movePane.add(showEvents);
1083            movePane.add(showReflection);
1084            movePane.add(up);
1085            movePane.add(down);
1086
1087            JPanel compViewPane = new JPanel();
1088            compViewPane.setLayout(new BorderLayout());
1089            compViewPane.add(new JLabel("Tab order:"), BorderLayout.NORTH);
1090            compViewPane.add(movePane, BorderLayout.SOUTH);
1091            compViewPane.add(orderList, BorderLayout.CENTER);
1092
1093            JTabbedPane tbpn = new JTabbedPane();
1094            tbpn.add("Component Tree", compTreePane);
1095            tbpn.add("Component View", compViewPane);
1096
1097            JButton okBUtton = new JButton("OK");
1098            okBUtton.addActionListener(new ActionListener() {
1099                @Override
1100                public void actionPerformed(ActionEvent e) {
1101                    save();
1102                    setVisible(false);
1103                }
1104            });
1105
1106            JButton cnBUtton = new JButton("Cancel");
1107            cnBUtton.addActionListener(new ActionListener() {
1108                @Override
1109                public void actionPerformed(ActionEvent e) {
1110                    setVisible(false);
1111                    load();
1112                }
1113            });
1114
1115            JPanel pn = new JPanel();
1116            pn.add(okBUtton);
1117            pn.add(cnBUtton);
1118
1119            getContentPane().setLayout(new BorderLayout());
1120            getContentPane().add(pn, BorderLayout.SOUTH);
1121            getContentPane().add(tbpn, BorderLayout.CENTER);
1122
1123            load();
1124
1125            setSize(400, 400);
1126        }
1127
1128        private void save() {
1129            try {
1130                props.setProperty("guibrowser.showall",
1131                        showAll ? "on" : "off");
1132                for (int i = 0; i < viewTabs.size(); i++) {
1133                    props.setProperty("guibrowser.viewpage_" + Integer.toString(i),
1134                            viewTabs.elementAt(i));
1135                }
1136                try (FileOutputStream fileOutputStream = new FileOutputStream(propFile)) {
1137                    props.store(fileOutputStream,
1138                            "Jemmy GUIBrowser");
1139                }
1140            } catch (IOException e) {
1141                e.printStackTrace();
1142            }
1143        }
1144
1145        private void load() {
1146            if (propFile.exists()) {
1147                try {
1148                    try (FileInputStream fileInputStream = new FileInputStream(propFile)) {
1149                        props.load(fileInputStream);
1150                    }
1151                    showAll
1152                            = props.getProperty("guibrowser.showall") == null
1153                            || props.getProperty("guibrowser.showall").equals("")
1154                            || props.getProperty("guibrowser.showall").equals("on");
1155                    visibleCombo.setSelectedIndex(showAll ? 1 : 0);
1156                    if (props.getProperty("guibrowser.viewpage_0") != null
1157                            && !props.getProperty("guibrowser.viewpage_0").equals("")) {
1158                        viewTabs.removeAllElements();
1159                        viewTabs.addElement(props.getProperty("guibrowser.viewpage_0"));
1160                        viewTabs.addElement(props.getProperty("guibrowser.viewpage_1"));
1161                        viewTabs.addElement(props.getProperty("guibrowser.viewpage_2"));
1162                        if (props.getProperty("guibrowser.viewpage_3") != null
1163                                && !props.getProperty("guibrowser.viewpage_3").equals("")) {
1164                            viewTabs.addElement(props.getProperty("guibrowser.viewpage_3"));
1165                        }
1166                        if (props.getProperty("guibrowser.viewpage_4") != null
1167                                && !props.getProperty("guibrowser.viewpage_4").equals("")) {
1168                            viewTabs.addElement(props.getProperty("guibrowser.viewpage_4"));
1169                        }
1170                    }
1171                } catch (IOException e) {
1172                    e.printStackTrace();
1173                }
1174                showReflection.setSelected(viewTabs.indexOf(REFLECTION_TAB) > -1);
1175                showEvents.setSelected(viewTabs.indexOf(EVENT_TAB) > -1);
1176            }
1177        }
1178    }
1179
1180    private class ComponentBrowser extends JFrame {
1181
1182        JTree winTree;
1183        JTree componentTree;
1184        JTree methodTree;
1185        ClassNode compNode;
1186        JTabbedPane tbd;
1187        JButton viewButton;
1188        JButton expandButton;
1189        JSplitPane winSplit = null;
1190        JSplitPane componentSplit = null;
1191        WindowRenderer renderer;
1192        SelectionManager selManager;
1193        JList<AWTEvent> eventList;
1194        ListListener listListener;
1195        DefaultListModel<AWTEvent> eventModel;
1196        JCheckBox mouseEvents;
1197        JCheckBox mouseMotionEvents;
1198        JCheckBox keyEvents;
1199
1200        public ComponentBrowser(JFrame owner, ClassNode componentNode) {
1201            super("Component " + componentNode.toString());
1202            fill(componentNode);
1203        }
1204
1205        private void fill(ClassNode componentNode) {
1206            compNode = componentNode;
1207
1208            viewButton = new JButton("View");
1209            viewButton.setEnabled(false);
1210            viewButton.addActionListener(new ActionListener() {
1211                @Override
1212                public void actionPerformed(ActionEvent e) {
1213                    new ComponentBrowser(getOwnr(),
1214                            getSelectedNode()).
1215                            setVisible(true);
1216                }
1217            });
1218
1219            expandButton = new JButton("Expand All");
1220            expandButton.setEnabled(false);
1221            expandButton.addActionListener(new ActionListener() {
1222                @Override
1223                public void actionPerformed(ActionEvent e) {
1224                    expandAll(getSelectedTree(),
1225                            getSelectionPath());
1226                }
1227            });
1228
1229            JPanel buttonPane = new JPanel();
1230            buttonPane.add(viewButton);
1231            buttonPane.add(expandButton);
1232
1233            Component[] cpss = {viewButton, expandButton};
1234            selManager = new SelectionManager(cpss);
1235            renderer = new WindowRenderer();
1236
1237            tbd = new JTabbedPane();
1238
1239            for (int i = 0; i < propDialog.viewTabs.size(); i++) {
1240                String next = propDialog.viewTabs.elementAt(i);
1241                if (next.equals(PROPERTIES_TAB)) {
1242                    addPropertiesTab();
1243                } else if (next.equals(WINDOWS_TAB)) {
1244                    addWindowTab();
1245                } else if (next.equals(COMPONENTS_TAB)) {
1246                    addComponentTab();
1247                } else if (next.equals(REFLECTION_TAB)) {
1248                    addReflectionTab();
1249                } else if (next.equals(EVENT_TAB)) {
1250                    addEventTab();
1251                } else if (next.equals(IMAGE_TAB)) {
1252                    addImageTab();
1253                }
1254            }
1255
1256            tbd.addChangeListener(new ChangeListener() {
1257                @Override
1258                public void stateChanged(ChangeEvent e) {
1259                    viewButton.setEnabled(getSelectedNode() != null);
1260                }
1261            });
1262
1263            getContentPane().setLayout(new BorderLayout());
1264            getContentPane().add(buttonPane, BorderLayout.SOUTH);
1265            getContentPane().add(tbd, BorderLayout.CENTER);
1266
1267            addComponentListener(new ComponentListener() {
1268                @Override
1269                public void componentHidden(ComponentEvent e) {
1270                }
1271
1272                @Override
1273                public void componentMoved(ComponentEvent e) {
1274                }
1275
1276                @Override
1277                public void componentResized(ComponentEvent e) {
1278                    if (winSplit != null) {
1279                        winSplit.setDividerLocation(0.8);
1280                    }
1281                    if (componentSplit != null) {
1282                        componentSplit.setDividerLocation(0.8);
1283                    }
1284                }
1285
1286                @Override
1287                public void componentShown(ComponentEvent e) {
1288                }
1289            });
1290
1291            setSize(800, 400);
1292        }
1293
1294        private void addImageTab() {
1295            BufferedImage image = null;
1296            if (compNode instanceof ComponentNode) {
1297                image = ((ComponentNode) compNode).getImage();
1298            }
1299            if (image != null) {
1300                JPanel imagePane = new ImagePane(image);
1301                imagePane.prepareImage(image, imagePane);
1302                JScrollPane pane = new JScrollPane(imagePane);
1303                tbd.add(IMAGE_TAB, pane);
1304            }
1305        }
1306
1307        private void addWindowTab() {
1308            if (compNode instanceof WindowNode
1309                    && ((WindowNode) compNode).getWindows().length > 0) {
1310                winTree = new JTree(((WindowNode) compNode).getWindowModel());
1311                winTree.setCellRenderer(renderer);
1312                winTree.setEditable(false);
1313                winTree.addTreeSelectionListener(selManager);
1314                winSplit = createUnderPane(winTree);
1315                tbd.add(WINDOWS_TAB, winSplit);
1316            }
1317
1318        }
1319
1320        private void addComponentTab() {
1321            if (compNode instanceof ContainerNode
1322                    && ((ContainerNode) compNode).getComponents().length > 0) {
1323                componentTree = new JTree(((ContainerNode) compNode).getComponentModel());
1324                componentTree.setCellRenderer(renderer);
1325                componentTree.setEditable(false);
1326                componentTree.addTreeSelectionListener(selManager);
1327                componentSplit = createUnderPane(componentTree);
1328                tbd.add(COMPONENTS_TAB, componentSplit);
1329            }
1330
1331        }
1332
1333        private void addReflectionTab() {
1334            methodTree = new JTree(compNode.getMethodsModel());
1335            methodTree.setCellRenderer(renderer);
1336            methodTree.setEditable(false);
1337            methodTree.addTreeSelectionListener(selManager);
1338            tbd.add(REFLECTION_TAB, new JScrollPane(methodTree));
1339        }
1340
1341        private void addPropertiesTab() {
1342            if (compNode instanceof ComponentNode) {
1343                Hashtable<String, Object> props = ((ContainerNode) compNode).getProperties();
1344                if (props.size() > 0) {
1345                    JTable propTable = new JTable(new MyModel(props));
1346                    propTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
1347                    tbd.add(PROPERTIES_TAB, new JScrollPane(propTable));
1348                    /*
1349                    propTable.addMouseListener(new MouseListener() {
1350                        public void mouseClicked(MouseEvent e) {
1351                            new ComponentBrowser(getOwnr(),
1352                                    getSelectedNode()).
1353                                    show();
1354                        }
1355                        public void mouseExited(MouseEvent e) {
1356                        }
1357                        public void mouseEntered(MouseEvent e) {
1358                        }
1359                        public void mousePressed(MouseEvent e) {
1360                        }
1361                        public void mouseReleased(MouseEvent e) {
1362                        }
1363                    });
1364                     */
1365                }
1366            }
1367        }
1368
1369        private void addEventTab() {
1370            if (compNode instanceof ComponentNode) {
1371                eventModel = new DefaultListModel<>();
1372                eventList = new JList<>(eventModel);
1373                listListener = new ListListener(eventModel, ((ComponentNode) compNode).comp);
1374                mouseEvents = new JCheckBox("Mouse events");
1375                mouseEvents.addActionListener(new ActionListener() {
1376                    @Override
1377                    public void actionPerformed(ActionEvent e) {
1378                        if (mouseEvents.isSelected()) {
1379                            listListener.addMouseListener();
1380                        } else {
1381                            listListener.removeMouseListener();
1382                        }
1383                    }
1384                });
1385                mouseMotionEvents = new JCheckBox("Mouse motion events");
1386                mouseMotionEvents.addActionListener(new ActionListener() {
1387                    @Override
1388                    public void actionPerformed(ActionEvent e) {
1389                        if (mouseMotionEvents.isSelected()) {
1390                            listListener.addMouseMotionListener();
1391                        } else {
1392                            listListener.removeMouseMotionListener();
1393                        }
1394                    }
1395                });
1396                keyEvents = new JCheckBox("Key events");
1397                keyEvents.addActionListener(new ActionListener() {
1398                    @Override
1399                    public void actionPerformed(ActionEvent e) {
1400                        if (keyEvents.isSelected()) {
1401                            listListener.addKeyListener();
1402                        } else {
1403                            listListener.removeKeyListener();
1404                        }
1405                    }
1406                });
1407                JButton clear = new JButton("Clear list");
1408                clear.addActionListener(new ActionListener() {
1409                    @Override
1410                    public void actionPerformed(ActionEvent e) {
1411                        eventModel.removeAllElements();
1412                    }
1413                });
1414                JPanel checkPane = new JPanel();
1415                checkPane.add(mouseEvents);
1416                checkPane.add(mouseMotionEvents);
1417                checkPane.add(keyEvents);
1418                checkPane.add(clear);
1419                JPanel subPane = new JPanel();
1420                subPane.setLayout(new BorderLayout());
1421                subPane.add(checkPane, BorderLayout.SOUTH);
1422                subPane.add(new JScrollPane(eventList), BorderLayout.CENTER);
1423                tbd.add(EVENT_TAB, subPane);
1424            }
1425        }
1426
1427        private JFrame getOwnr() {
1428            return this;
1429        }
1430
1431        private JTree getSelectedTree() {
1432            String title = tbd.getTitleAt(tbd.getSelectedIndex());
1433            if (title.equals(WINDOWS_TAB)) {
1434                return winTree;
1435            } else if (title.equals(COMPONENTS_TAB)) {
1436                return componentTree;
1437            } else if (title.equals(REFLECTION_TAB)) {
1438                return methodTree;
1439            }
1440            return null;
1441        }
1442
1443        private TreePath getSelectionPath() {
1444            JTree tree = getSelectedTree();
1445            if (tree != null) {
1446                return tree.getSelectionPath();
1447            } else {
1448                return null;
1449            }
1450        }
1451
1452        private ClassNode getSelectedNode() {
1453            TreePath path = getSelectionPath();
1454            if (path != null) {
1455                return (ClassNode) path.getLastPathComponent();
1456            } else {
1457                return null;
1458            }
1459        }
1460
1461    }
1462
1463    private static class SelectionManager implements TreeSelectionListener, ListSelectionListener {
1464
1465        Component[] comps;
1466
1467        public SelectionManager(Component[] comps) {
1468            this.comps = comps;
1469        }
1470
1471        @Override
1472        public void valueChanged(TreeSelectionEvent e) {
1473            for (Component comp : comps) {
1474                comp.setEnabled(e.getPath() != null);
1475            }
1476        }
1477
1478        @Override
1479        public void valueChanged(ListSelectionEvent e) {
1480            for (Component comp : comps) {
1481                comp.setEnabled(e.getFirstIndex() != -1);
1482            }
1483        }
1484    }
1485
1486    private void expandAll(JTree tree, TreePath path) {
1487        tree.expandPath(path);
1488        TreeModel model = tree.getModel();
1489        Object lastComponent = path.getLastPathComponent();
1490        for (int i = 0; i < model.getChildCount(lastComponent); i++) {
1491            expandAll(tree,
1492                    path.pathByAddingChild(model.getChild(lastComponent, i)));
1493        }
1494    }
1495
1496    private static class ToStringListener implements TreeSelectionListener {
1497
1498        JTextArea area;
1499
1500        public ToStringListener(JTextArea area) {
1501            this.area = area;
1502        }
1503
1504        @Override
1505        public void valueChanged(TreeSelectionEvent e) {
1506            if (e.getPath() != null
1507                    && e.getPath().getLastPathComponent() instanceof ComponentNode) {
1508                area.setText("toString(): "
1509                        + ((ComponentNode) e.getPath().getLastPathComponent()).
1510                        getToString());
1511            } else {
1512                area.setText("");
1513            }
1514        }
1515    }
1516
1517    private JSplitPane createUnderPane(JTree tree) {
1518        JTextArea toStringArea = new JTextArea();
1519        toStringArea.setLineWrap(true);
1520        toStringArea.setEditable(false);
1521        tree.addTreeSelectionListener(new ToStringListener(toStringArea));
1522        JSplitPane result = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
1523                new JScrollPane(tree),
1524                new JScrollPane(toStringArea));
1525        result.setOneTouchExpandable(true);
1526        result.setDividerSize(8);
1527        result.setDividerLocation(0.8);
1528        return result;
1529    }
1530
1531    private static class MyModel extends DefaultTableModel {
1532
1533        private static final long serialVersionUID = 42L;
1534
1535        @SuppressWarnings(value = "unchecked")
1536        public MyModel(Hashtable<String, Object> props) {
1537            super();
1538            Object[] keys = props.keySet().toArray();
1539            if (keys.length > 0) {
1540                addColumn("Name");
1541                addColumn("Value");
1542                setNumRows(keys.length);
1543                for (int i = 0; i < keys.length; i++) {
1544                    setValueAt(keys[i].toString(), i, 0);
1545                    setValueAt(props.get(keys[i]).toString(), i, 1);
1546                }
1547                Collections.sort((Vector<Vector<?>>) (Vector<?>) getDataVector(), new Comparator<Vector<?>>() {
1548                    @Override
1549                    public int compare(Vector<?> v1, Vector<?> v2) {
1550                        return v1.get(0).toString().compareTo(v2.get(0).toString());
1551                    }
1552                });
1553            }
1554        }
1555
1556        @Override
1557        public boolean isCellEditable(int x, int y) {
1558            return false;
1559        }
1560    }
1561
1562    private static class ListListener extends TrialListenerManager {
1563
1564        DefaultListModel<AWTEvent> model;
1565
1566        public ListListener(DefaultListModel<AWTEvent> m, Component comp) {
1567            super(comp);
1568            model = m;
1569        }
1570
1571        @Override
1572        void printEvent(AWTEvent e) {
1573            model.addElement(e);
1574        }
1575    }
1576
1577    private static class ImagePane extends JPanel {
1578
1579        private static final long serialVersionUID = 42L;
1580        BufferedImage image;
1581
1582        public ImagePane(BufferedImage image) {
1583            super();
1584            this.image = image;
1585            setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
1586        }
1587
1588        @Override
1589        public void paint(Graphics g) {
1590            g.drawImage(image, 0, 0, null);
1591        }
1592    }
1593
1594    private static class ComponentImageProvider {
1595
1596        BufferedImage image;
1597        int x;
1598        int y;
1599
1600        public ComponentImageProvider(BufferedImage image, int x, int y) {
1601            this.image = image;
1602            this.x = x;
1603            this.y = y;
1604        }
1605
1606        public BufferedImage getImage(int x, int y, int w, int h) {
1607            /*
1608            BufferedImage newImage = image.getSubimage(0, 0, image.getWidth(), image.getHeight());
1609            Graphics g = newImage.getGraphics();
1610            g.setColor(Color.RED);
1611            g.drawRect(x - this.x, y - this.y, w, h);
1612            return newImage;
1613             */
1614            int realW = Math.min(image.getWidth() - x, w);
1615            int realH = Math.min(image.getHeight() - y, h);
1616            return image.getSubimage(x - this.x, y - this.y, realW, realH);
1617        }
1618    }
1619}
1620