1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.hierarchyviewer.ui;
18 
19 import com.android.ddmlib.AndroidDebugBridge;
20 import com.android.ddmlib.IDevice;
21 import com.android.hierarchyviewer.device.DeviceBridge;
22 import com.android.hierarchyviewer.device.Window;
23 import com.android.hierarchyviewer.laf.UnifiedContentBorder;
24 import com.android.hierarchyviewer.scene.CaptureLoader;
25 import com.android.hierarchyviewer.scene.ProfilesLoader;
26 import com.android.hierarchyviewer.scene.VersionLoader;
27 import com.android.hierarchyviewer.scene.ViewHierarchyLoader;
28 import com.android.hierarchyviewer.scene.ViewHierarchyScene;
29 import com.android.hierarchyviewer.scene.ViewManager;
30 import com.android.hierarchyviewer.scene.ViewNode;
31 import com.android.hierarchyviewer.scene.WindowsLoader;
32 import com.android.hierarchyviewer.ui.action.CaptureLayersAction;
33 import com.android.hierarchyviewer.ui.action.CaptureNodeAction;
34 import com.android.hierarchyviewer.ui.action.DumpDisplayListAction;
35 import com.android.hierarchyviewer.ui.action.ExitAction;
36 import com.android.hierarchyviewer.ui.action.InvalidateAction;
37 import com.android.hierarchyviewer.ui.action.LoadGraphAction;
38 import com.android.hierarchyviewer.ui.action.RefreshWindowsAction;
39 import com.android.hierarchyviewer.ui.action.RequestLayoutAction;
40 import com.android.hierarchyviewer.ui.action.SaveSceneAction;
41 import com.android.hierarchyviewer.ui.action.ShowDevicesAction;
42 import com.android.hierarchyviewer.ui.action.StartServerAction;
43 import com.android.hierarchyviewer.ui.action.StopServerAction;
44 import com.android.hierarchyviewer.ui.model.ProfilesTableModel;
45 import com.android.hierarchyviewer.ui.model.PropertiesTableModel;
46 import com.android.hierarchyviewer.ui.model.ViewsTreeModel;
47 import com.android.hierarchyviewer.ui.util.IconLoader;
48 import com.android.hierarchyviewer.ui.util.PngFileFilter;
49 import com.android.hierarchyviewer.ui.util.PsdFileFilter;
50 import com.android.hierarchyviewer.util.OS;
51 import com.android.hierarchyviewer.util.WorkerThread;
52 
53 import org.netbeans.api.visual.graph.layout.TreeGraphLayout;
54 import org.netbeans.api.visual.model.ObjectSceneEvent;
55 import org.netbeans.api.visual.model.ObjectSceneEventType;
56 import org.netbeans.api.visual.model.ObjectSceneListener;
57 import org.netbeans.api.visual.model.ObjectState;
58 
59 import java.awt.BorderLayout;
60 import java.awt.Color;
61 import java.awt.Component;
62 import java.awt.Dimension;
63 import java.awt.FlowLayout;
64 import java.awt.Graphics2D;
65 import java.awt.GridBagConstraints;
66 import java.awt.GridBagLayout;
67 import java.awt.Image;
68 import java.awt.Insets;
69 import java.awt.event.ActionEvent;
70 import java.awt.event.ActionListener;
71 import java.awt.event.MouseAdapter;
72 import java.awt.event.MouseEvent;
73 import java.awt.event.MouseWheelEvent;
74 import java.awt.event.MouseWheelListener;
75 import java.awt.image.BufferedImage;
76 import java.io.File;
77 import java.io.IOException;
78 import java.util.ArrayList;
79 import java.util.HashSet;
80 import java.util.Set;
81 import java.util.concurrent.ExecutionException;
82 import java.util.regex.Pattern;
83 import java.util.regex.PatternSyntaxException;
84 
85 import javax.imageio.ImageIO;
86 import javax.swing.ActionMap;
87 import javax.swing.BorderFactory;
88 import javax.swing.Box;
89 import javax.swing.ButtonGroup;
90 import javax.swing.ImageIcon;
91 import javax.swing.JButton;
92 import javax.swing.JCheckBox;
93 import javax.swing.JComponent;
94 import javax.swing.JFileChooser;
95 import javax.swing.JFrame;
96 import javax.swing.JLabel;
97 import javax.swing.JMenu;
98 import javax.swing.JMenuBar;
99 import javax.swing.JMenuItem;
100 import javax.swing.JPanel;
101 import javax.swing.JProgressBar;
102 import javax.swing.JScrollBar;
103 import javax.swing.JScrollPane;
104 import javax.swing.JSlider;
105 import javax.swing.JSplitPane;
106 import javax.swing.JTable;
107 import javax.swing.JTextField;
108 import javax.swing.JToggleButton;
109 import javax.swing.JToolBar;
110 import javax.swing.JTree;
111 import javax.swing.ListSelectionModel;
112 import javax.swing.SwingUtilities;
113 import javax.swing.SwingWorker;
114 import javax.swing.event.ChangeEvent;
115 import javax.swing.event.ChangeListener;
116 import javax.swing.event.DocumentEvent;
117 import javax.swing.event.DocumentListener;
118 import javax.swing.event.ListSelectionEvent;
119 import javax.swing.event.ListSelectionListener;
120 import javax.swing.event.TreeSelectionEvent;
121 import javax.swing.event.TreeSelectionListener;
122 import javax.swing.table.DefaultTableModel;
123 import javax.swing.text.BadLocationException;
124 import javax.swing.text.Document;
125 import javax.swing.tree.DefaultTreeCellRenderer;
126 import javax.swing.tree.TreePath;
127 
128 public class Workspace extends JFrame {
129     private JLabel viewCountLabel;
130     private JSlider zoomSlider;
131     private JSplitPane sideSplitter;
132     private JSplitPane mainSplitter;
133     private JTable propertiesTable;
134     private JTable profilingTable;
135     private JComponent pixelPerfectPanel;
136     private JTree pixelPerfectTree;
137     private ScreenViewer screenViewer;
138 
139     private JPanel extrasPanel;
140     private LayoutRenderer layoutView;
141 
142     private JScrollPane sceneScroller;
143     private JComponent sceneView;
144 
145     private ViewHierarchyScene scene;
146 
147     private ActionMap actionsMap;
148     private JPanel mainPanel;
149     private JProgressBar progress;
150     private JToolBar buttonsPanel;
151     private JToolBar commandButtonsPanel;
152 
153     private JComponent deviceSelector;
154     private DevicesTableModel devicesTableModel;
155     private WindowsTableModel windowsTableModel;
156 
157     private IDevice currentDevice;
158     private Window currentWindow = Window.FOCUSED_WINDOW;
159 
160     private JButton displayNodeButton;
161     private JButton dumpDisplayListButton;
162     private JButton captureLayersButton;
163     private JButton invalidateButton;
164     private JButton requestLayoutButton;
165     private JButton loadButton;
166     private JButton startButton;
167     private JButton stopButton;
168     private JButton showDevicesButton;
169     private JButton refreshButton;
170     private JToggleButton graphViewButton;
171     private JToggleButton pixelPerfectViewButton;
172     private JMenuItem saveMenuItem;
173     private JMenuItem showDevicesMenuItem;
174     private JMenuItem loadMenuItem;
175     private JMenuItem startMenuItem;
176     private JMenuItem stopMenuItem;
177     private JTable devices;
178     private JTable windows;
179     private JLabel minZoomLabel;
180     private JLabel maxZoomLabel;
181     private JTextField filterText;
182     private JLabel filterLabel;
183 
184     private int protocolVersion;
185     private int serverVersion;
186 
Workspace()187     public Workspace() {
188         super("Hierarchy Viewer");
189 
190         buildActions();
191         add(buildMainPanel());
192         setJMenuBar(buildMenuBar());
193 
194         devices.changeSelection(0, 0, false, false);
195         currentDeviceChanged();
196 
197         pack();
198     }
199 
buildActions()200     private void buildActions() {
201         actionsMap = new ActionMap();
202         actionsMap.put(ExitAction.ACTION_NAME, new ExitAction(this));
203         actionsMap.put(ShowDevicesAction.ACTION_NAME, new ShowDevicesAction(this));
204         actionsMap.put(LoadGraphAction.ACTION_NAME, new LoadGraphAction(this));
205         actionsMap.put(SaveSceneAction.ACTION_NAME, new SaveSceneAction(this));
206         actionsMap.put(StartServerAction.ACTION_NAME, new StartServerAction(this));
207         actionsMap.put(StopServerAction.ACTION_NAME, new StopServerAction(this));
208         actionsMap.put(InvalidateAction.ACTION_NAME, new InvalidateAction(this));
209         actionsMap.put(RequestLayoutAction.ACTION_NAME, new RequestLayoutAction(this));
210         actionsMap.put(DumpDisplayListAction.ACTION_NAME, new DumpDisplayListAction(this));
211         actionsMap.put(CaptureNodeAction.ACTION_NAME, new CaptureNodeAction(this));
212         actionsMap.put(CaptureLayersAction.ACTION_NAME, new CaptureLayersAction(this));
213         actionsMap.put(RefreshWindowsAction.ACTION_NAME, new RefreshWindowsAction(this));
214     }
215 
buildMainPanel()216     private JComponent buildMainPanel() {
217         mainPanel = new JPanel();
218         mainPanel.setLayout(new BorderLayout());
219         commandButtonsPanel = buildToolBar();
220         mainPanel.add(commandButtonsPanel, BorderLayout.PAGE_START);
221         mainPanel.add(deviceSelector = buildDeviceSelector(), BorderLayout.CENTER);
222         mainPanel.add(buildStatusPanel(), BorderLayout.SOUTH);
223 
224         mainPanel.setPreferredSize(new Dimension(1200, 800));
225 
226         return mainPanel;
227     }
228 
buildGraphPanel()229     private JComponent buildGraphPanel() {
230         sceneScroller = new JScrollPane();
231         sceneScroller.setBorder(null);
232 
233         mainSplitter = new JSplitPane();
234         mainSplitter.setResizeWeight(1.0);
235         mainSplitter.setContinuousLayout(true);
236         if (OS.isMacOsX() && OS.isLeopardOrLater()) {
237             mainSplitter.setBorder(new UnifiedContentBorder());
238         }
239 
240         mainSplitter.setLeftComponent(sceneScroller);
241         mainSplitter.setRightComponent(buildSideSplitter());
242 
243         return mainSplitter;
244     }
245 
buildDeviceSelector()246     private JComponent buildDeviceSelector() {
247         JPanel panel = new JPanel(new GridBagLayout());
248         if (OS.isMacOsX() && OS.isLeopardOrLater()) {
249             panel.setBorder(new UnifiedContentBorder());
250         }
251 
252         devicesTableModel = new DevicesTableModel();
253         for (IDevice device : DeviceBridge.getDevices()) {
254             DeviceBridge.setupDeviceForward(device);
255             devicesTableModel.addDevice(device);
256         }
257         DeviceBridge.startListenForDevices(devicesTableModel);
258 
259         devices = new JTable(devicesTableModel);
260         devices.getSelectionModel().addListSelectionListener(new DeviceSelectedListener());
261         devices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
262         devices.setBorder(null);
263         JScrollPane devicesScroller = new JScrollPane(devices);
264         devicesScroller.setBorder(null);
265         panel.add(devicesScroller, new GridBagConstraints(0, 0, 1, 1, 0.5, 1.0,
266                 GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),
267                 0, 0));
268 
269         windowsTableModel = new WindowsTableModel();
270         windowsTableModel.setVisible(false);
271 
272         windows = new JTable(windowsTableModel);
273         windows.getSelectionModel().addListSelectionListener(new WindowSelectedListener());
274         windows.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
275         windows.setBorder(null);
276         JScrollPane windowsScroller = new JScrollPane(windows);
277         windowsScroller.setBorder(null);
278         panel.add(windowsScroller, new GridBagConstraints(2, 0, 1, 1, 0.5, 1.0,
279                 GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),
280                 0, 0));
281 
282         return panel;
283     }
284 
buildSideSplitter()285     private JComponent buildSideSplitter() {
286         propertiesTable = new JTable();
287         propertiesTable.setModel(new DefaultTableModel(new Object[][] { },
288                 new String[] { "Property", "Value" }));
289         propertiesTable.setBorder(null);
290         propertiesTable.getTableHeader().setBorder(null);
291 
292         JScrollPane tableScroller = new JScrollPane(propertiesTable);
293         tableScroller.setBorder(null);
294 
295         profilingTable = new JTable();
296         profilingTable.setModel(new DefaultTableModel(new Object[][] {
297                 { " " , " " }, { " " , " " }, { " " , " " } },
298                 new String[] { "Operation", "Duration (ms)" }));
299         profilingTable.setBorder(null);
300         profilingTable.getTableHeader().setBorder(null);
301 
302         JScrollPane firstTableScroller = new JScrollPane(profilingTable);
303         firstTableScroller.setBorder(null);
304 
305         setVisibleRowCount(profilingTable, 5);
306         firstTableScroller.setMinimumSize(profilingTable.getPreferredScrollableViewportSize());
307 
308         JSplitPane tablesSplitter = new JSplitPane();
309         tablesSplitter.setBorder(null);
310         tablesSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT);
311         tablesSplitter.setResizeWeight(0);
312         tablesSplitter.setLeftComponent(firstTableScroller);
313         tablesSplitter.setBottomComponent(tableScroller);
314         tablesSplitter.setContinuousLayout(true);
315 
316         sideSplitter = new JSplitPane();
317         sideSplitter.setBorder(null);
318         sideSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT);
319         sideSplitter.setResizeWeight(0.5);
320         sideSplitter.setLeftComponent(tablesSplitter);
321         sideSplitter.setBottomComponent(null);
322         sideSplitter.setContinuousLayout(true);
323 
324         return sideSplitter;
325     }
326 
buildStatusPanel()327     private JPanel buildStatusPanel() {
328         JPanel statusPanel = new JPanel();
329         statusPanel.setLayout(new BorderLayout());
330 
331         JPanel leftSide = new JPanel();
332         leftSide.setOpaque(false);
333         leftSide.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 5));
334         leftSide.add(Box.createHorizontalStrut(6));
335 
336         ButtonGroup group = new ButtonGroup();
337 
338         graphViewButton = new JToggleButton(IconLoader.load(getClass(),
339                 "/images/icon-graph-view.png"));
340         graphViewButton.setSelectedIcon(IconLoader.load(getClass(),
341                 "/images/icon-graph-view-selected.png"));
342         graphViewButton.putClientProperty("JButton.buttonType", "segmentedTextured");
343         graphViewButton.putClientProperty("JButton.segmentPosition", "first");
344         graphViewButton.addActionListener(new ActionListener() {
345             public void actionPerformed(ActionEvent e) {
346                 toggleGraphView();
347             }
348         });
349         group.add(graphViewButton);
350         leftSide.add(graphViewButton);
351 
352         pixelPerfectViewButton = new JToggleButton(IconLoader.load(getClass(),
353                 "/images/icon-pixel-perfect-view.png"));
354         pixelPerfectViewButton.setSelectedIcon(IconLoader.load(getClass(),
355                 "/images/icon-pixel-perfect-view-selected.png"));
356         pixelPerfectViewButton.putClientProperty("JButton.buttonType", "segmentedTextured");
357         pixelPerfectViewButton.putClientProperty("JButton.segmentPosition", "last");
358         pixelPerfectViewButton.addActionListener(new ActionListener() {
359             public void actionPerformed(ActionEvent e) {
360                 togglePixelPerfectView();
361             }
362         });
363         group.add(pixelPerfectViewButton);
364         leftSide.add(pixelPerfectViewButton);
365 
366         graphViewButton.setSelected(true);
367 
368         filterText = new JTextField(20);
369         filterText.putClientProperty("JComponent.sizeVariant", "small");
370         filterText.getDocument().addDocumentListener(new DocumentListener() {
371             public void insertUpdate(DocumentEvent e) {
372                 updateFilter(e);
373             }
374 
375             public void removeUpdate(DocumentEvent e) {
376                 updateFilter(e);
377             }
378 
379             public void changedUpdate(DocumentEvent e) {
380                 updateFilter(e);
381             }
382         });
383 
384         filterLabel = new JLabel("Filter by class or id:");
385         filterLabel.putClientProperty("JComponent.sizeVariant", "small");
386         filterLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 6));
387 
388         leftSide.add(filterLabel);
389         leftSide.add(filterText);
390 
391         minZoomLabel = new JLabel();
392         minZoomLabel.setText("20%");
393         minZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
394         minZoomLabel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 0));
395         leftSide.add(minZoomLabel);
396 
397         zoomSlider = new JSlider();
398         zoomSlider.putClientProperty("JComponent.sizeVariant", "small");
399         zoomSlider.setMaximum(200);
400         zoomSlider.setMinimum(20);
401         zoomSlider.setValue(100);
402         zoomSlider.addChangeListener(new ChangeListener() {
403             public void stateChanged(ChangeEvent evt) {
404                 zoomSliderStateChanged(evt);
405             }
406         });
407         leftSide.add(zoomSlider);
408 
409         maxZoomLabel = new JLabel();
410         maxZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
411         maxZoomLabel.setText("200%");
412         leftSide.add(maxZoomLabel);
413 
414         viewCountLabel = new JLabel();
415         viewCountLabel.setText("0 views");
416         viewCountLabel.putClientProperty("JComponent.sizeVariant", "small");
417         viewCountLabel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 0));
418         leftSide.add(viewCountLabel);
419 
420         statusPanel.add(leftSide, BorderLayout.LINE_START);
421 
422         JPanel rightSide = new JPanel();
423         rightSide.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 12));
424         rightSide.setLayout(new FlowLayout(FlowLayout.RIGHT));
425 
426         progress = new JProgressBar();
427         progress.setVisible(false);
428         progress.setIndeterminate(true);
429         progress.putClientProperty("JComponent.sizeVariant", "mini");
430         progress.putClientProperty("JProgressBar.style", "circular");
431         rightSide.add(progress);
432 
433         statusPanel.add(rightSide, BorderLayout.LINE_END);
434 
435         hideStatusBarComponents();
436 
437         return statusPanel;
438     }
439 
hideStatusBarComponents()440     private void hideStatusBarComponents() {
441         viewCountLabel.setVisible(false);
442         zoomSlider.setVisible(false);
443         minZoomLabel.setVisible(false);
444         maxZoomLabel.setVisible(false);
445         filterLabel.setVisible(false);
446         filterText.setVisible(false);
447     }
448 
buildToolBar()449     private JToolBar buildToolBar() {
450         JToolBar toolBar = new JToolBar();
451         toolBar.setFloatable(false);
452         toolBar.setRollover(true);
453 
454         startButton = new JButton();
455         startButton.setAction(actionsMap.get(StartServerAction.ACTION_NAME));
456         startButton.putClientProperty("JButton.buttonType", "segmentedTextured");
457         startButton.putClientProperty("JButton.segmentPosition", "first");
458         toolBar.add(startButton);
459 
460         stopButton = new JButton();
461         stopButton.setAction(actionsMap.get(StopServerAction.ACTION_NAME));
462         stopButton.putClientProperty("JButton.buttonType", "segmentedTextured");
463         stopButton.putClientProperty("JButton.segmentPosition", "middle");
464         toolBar.add(stopButton);
465 
466         refreshButton = new JButton();
467         refreshButton.setAction(actionsMap.get(RefreshWindowsAction.ACTION_NAME));
468         refreshButton.putClientProperty("JButton.buttonType", "segmentedTextured");
469         refreshButton.putClientProperty("JButton.segmentPosition", "last");
470         toolBar.add(refreshButton);
471 
472         showDevicesButton = new JButton();
473         showDevicesButton.setAction(actionsMap.get(ShowDevicesAction.ACTION_NAME));
474         showDevicesButton.putClientProperty("JButton.buttonType", "segmentedTextured");
475         showDevicesButton.putClientProperty("JButton.segmentPosition", "first");
476         toolBar.add(showDevicesButton);
477         showDevicesButton.setEnabled(false);
478 
479         loadButton = new JButton();
480         loadButton.setAction(actionsMap.get(LoadGraphAction.ACTION_NAME));
481         loadButton.putClientProperty("JButton.buttonType", "segmentedTextured");
482         loadButton.putClientProperty("JButton.segmentPosition", "last");
483         toolBar.add(loadButton);
484 
485         displayNodeButton = new JButton();
486         displayNodeButton.setAction(actionsMap.get(CaptureNodeAction.ACTION_NAME));
487         displayNodeButton.putClientProperty("JButton.buttonType", "segmentedTextured");
488         displayNodeButton.putClientProperty("JButton.segmentPosition", "first");
489         toolBar.add(displayNodeButton);
490 
491         dumpDisplayListButton = new JButton();
492         dumpDisplayListButton.setAction(actionsMap.get(DumpDisplayListAction.ACTION_NAME));
493         dumpDisplayListButton.putClientProperty("JButton.buttonType", "segmentedTextured");
494         dumpDisplayListButton.putClientProperty("JButton.segmentPosition", "middle");
495 
496         captureLayersButton = new JButton();
497         captureLayersButton.setAction(actionsMap.get(CaptureLayersAction.ACTION_NAME));
498         captureLayersButton.putClientProperty("JButton.buttonType", "segmentedTextured");
499         captureLayersButton.putClientProperty("JButton.segmentPosition", "middle");
500         toolBar.add(captureLayersButton);
501 
502         invalidateButton = new JButton();
503         invalidateButton.setAction(actionsMap.get(InvalidateAction.ACTION_NAME));
504         invalidateButton.putClientProperty("JButton.buttonType", "segmentedTextured");
505         invalidateButton.putClientProperty("JButton.segmentPosition", "middle");
506         toolBar.add(invalidateButton);
507 
508         requestLayoutButton = new JButton();
509         requestLayoutButton.setAction(actionsMap.get(RequestLayoutAction.ACTION_NAME));
510         requestLayoutButton.putClientProperty("JButton.buttonType", "segmentedTextured");
511         requestLayoutButton.putClientProperty("JButton.segmentPosition", "last");
512         toolBar.add(requestLayoutButton);
513 
514         return toolBar;
515     }
516 
setupProtocolDependentToolbar()517     private void setupProtocolDependentToolbar() {
518         // Some functionality is only enabled in certain versions of the protocol.
519         // Add/remove those buttons here
520         if (protocolVersion < 4) {
521             commandButtonsPanel.remove(dumpDisplayListButton);
522         } else if (dumpDisplayListButton.getParent() == null) {
523             commandButtonsPanel.add(dumpDisplayListButton,
524                     commandButtonsPanel.getComponentCount() - 1);
525         }
526     }
527 
buildMenuBar()528     private JMenuBar buildMenuBar() {
529         JMenuBar menuBar = new JMenuBar();
530 
531         JMenu fileMenu = new JMenu();
532         JMenu viewMenu = new JMenu();
533         JMenu viewHierarchyMenu = new JMenu();
534         JMenu serverMenu = new JMenu();
535 
536         saveMenuItem = new JMenuItem();
537         JMenuItem exitMenuItem = new JMenuItem();
538 
539         showDevicesMenuItem = new JMenuItem();
540 
541         loadMenuItem = new JMenuItem();
542 
543         startMenuItem = new JMenuItem();
544         stopMenuItem = new JMenuItem();
545 
546         fileMenu.setText("File");
547 
548         saveMenuItem.setAction(actionsMap.get(SaveSceneAction.ACTION_NAME));
549         fileMenu.add(saveMenuItem);
550 
551         exitMenuItem.setAction(actionsMap.get(ExitAction.ACTION_NAME));
552         fileMenu.add(exitMenuItem);
553 
554         menuBar.add(fileMenu);
555 
556         viewMenu.setText("View");
557 
558         showDevicesMenuItem.setAction(actionsMap.get(ShowDevicesAction.ACTION_NAME));
559         showDevicesMenuItem.setEnabled(false);
560         viewMenu.add(showDevicesMenuItem);
561 
562         menuBar.add(viewMenu);
563 
564         viewHierarchyMenu.setText("Hierarchy");
565 
566         loadMenuItem.setAction(actionsMap.get(LoadGraphAction.ACTION_NAME));
567         viewHierarchyMenu.add(loadMenuItem);
568 
569         menuBar.add(viewHierarchyMenu);
570 
571         serverMenu.setText("Server");
572 
573         startMenuItem.setAction(actionsMap.get(StartServerAction.ACTION_NAME));
574         serverMenu.add(startMenuItem);
575 
576         stopMenuItem.setAction(actionsMap.get(StopServerAction.ACTION_NAME));
577         serverMenu.add(stopMenuItem);
578 
579         menuBar.add(serverMenu);
580 
581         return menuBar;
582     }
583 
buildPixelPerfectPanel()584     private JComponent buildPixelPerfectPanel() {
585         JSplitPane splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
586 
587         pixelPerfectTree = new JTree(new Object[0]);
588         pixelPerfectTree.setBorder(null);
589         pixelPerfectTree.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
590         pixelPerfectTree.addTreeSelectionListener(new TreeSelectionListener() {
591             public void valueChanged(TreeSelectionEvent event) {
592                 ViewNode node = (ViewNode) event.getPath().getLastPathComponent();
593                 screenViewer.select(node);
594             }
595         });
596 
597         JScrollPane scroller = new JScrollPane(pixelPerfectTree);
598         scroller.setBorder(null);
599         scroller.getViewport().setBorder(null);
600 
601         splitter.setContinuousLayout(true);
602         splitter.setLeftComponent(scroller);
603         splitter.setRightComponent(buildPixelPerfectViewer(splitter));
604         splitter.setBorder(null);
605 
606         if (OS.isMacOsX() && OS.isLeopardOrLater()) {
607             splitter.setBorder(new UnifiedContentBorder());
608         }
609 
610         return splitter;
611     }
612 
buildPixelPerfectViewer(JSplitPane splitter)613     private JComponent buildPixelPerfectViewer(JSplitPane splitter) {
614         screenViewer = new ScreenViewer(this, currentDevice, splitter.getDividerSize());
615         return screenViewer;
616     }
617 
toggleGraphView()618     private void toggleGraphView() {
619         showStatusBarComponents();
620 
621         screenViewer.stop();
622         mainPanel.remove(pixelPerfectPanel);
623         mainPanel.add(mainSplitter, BorderLayout.CENTER);
624 
625         validate();
626         repaint();
627     }
628 
showStatusBarComponents()629     private void showStatusBarComponents() {
630         viewCountLabel.setVisible(true);
631         zoomSlider.setVisible(true);
632         minZoomLabel.setVisible(true);
633         maxZoomLabel.setVisible(true);
634         filterLabel.setVisible(true);
635         filterText.setVisible(true);
636     }
637 
togglePixelPerfectView()638     private void togglePixelPerfectView() {
639         if (pixelPerfectPanel == null) {
640             pixelPerfectPanel = buildPixelPerfectPanel();
641             showPixelPerfectTree();
642         } else {
643             screenViewer.start();
644         }
645 
646         hideStatusBarComponents();
647 
648         mainPanel.remove(mainSplitter);
649         mainPanel.add(pixelPerfectPanel, BorderLayout.CENTER);
650 
651         validate();
652         repaint();
653     }
654 
zoomSliderStateChanged(ChangeEvent evt)655     private void zoomSliderStateChanged(ChangeEvent evt) {
656         JSlider slider = (JSlider) evt.getSource();
657         if (sceneView != null) {
658             scene.setZoomFactor(slider.getValue() / 100.0d);
659             sceneView.repaint();
660         }
661     }
662 
showProperties(ViewNode node)663     private void showProperties(ViewNode node) {
664         propertiesTable.setModel(new PropertiesTableModel(node));
665     }
666 
updateProfiles(double[] profiles)667     private void updateProfiles(double[] profiles) {
668         profilingTable.setModel(new ProfilesTableModel(profiles));
669         setVisibleRowCount(profilingTable, profiles.length + 1);
670     }
671 
setVisibleRowCount(JTable table, int rows)672     public static void setVisibleRowCount(JTable table, int rows) {
673         int height = 0;
674         for (int row = 0; row < rows; row++) {
675             height += table.getRowHeight(row);
676         }
677 
678         Dimension size = new Dimension(table.getPreferredScrollableViewportSize().width, height);
679         table.setPreferredScrollableViewportSize(size);
680         table.revalidate();
681     }
682 
showPixelPerfectTree()683     private void showPixelPerfectTree() {
684         if (pixelPerfectTree == null) {
685             return;
686         }
687         pixelPerfectTree.setModel(new ViewsTreeModel(scene.getRoot()));
688         pixelPerfectTree.setCellRenderer(new ViewsTreeCellRenderer());
689         expandAll(pixelPerfectTree, true);
690 
691     }
692 
expandAll(JTree tree, boolean expand)693     private static void expandAll(JTree tree, boolean expand) {
694         ViewNode root = (ViewNode) tree.getModel().getRoot();
695         expandAll(tree, new TreePath(root), expand);
696     }
697 
expandAll(JTree tree, TreePath parent, boolean expand)698     private static void expandAll(JTree tree, TreePath parent, boolean expand) {
699         // Traverse children
700         ViewNode node = (ViewNode)parent.getLastPathComponent();
701         if (node.children != null) {
702             for (ViewNode n : node.children) {
703                 TreePath path = parent.pathByAddingChild(n);
704                 expandAll(tree, path, expand);
705             }
706         }
707 
708         if (expand) {
709             tree.expandPath(parent);
710         } else {
711             tree.collapsePath(parent);
712         }
713     }
714 
createGraph(ViewHierarchyScene scene)715     private void createGraph(ViewHierarchyScene scene) {
716         scene.addObjectSceneListener(new SceneFocusListener(),
717                 ObjectSceneEventType.OBJECT_FOCUS_CHANGED);
718 
719         if (mainSplitter == null) {
720             mainPanel.remove(deviceSelector);
721             mainPanel.add(buildGraphPanel(), BorderLayout.CENTER);
722             showDevicesButton.setEnabled(true);
723             showDevicesMenuItem.setEnabled(true);
724             graphViewButton.setEnabled(true);
725             pixelPerfectViewButton.setEnabled(true);
726 
727             showStatusBarComponents();
728         }
729 
730         sceneView = scene.createView();
731         sceneView.addMouseListener(new NodeClickListener());
732         sceneView.addMouseWheelListener(new WheelZoomListener());
733         sceneScroller.setViewportView(sceneView);
734 
735         if (extrasPanel != null) {
736             sideSplitter.remove(extrasPanel);
737         }
738         sideSplitter.setBottomComponent(buildExtrasPanel());
739 
740         mainSplitter.setDividerLocation(getWidth() - mainSplitter.getDividerSize() -
741                 buttonsPanel.getPreferredSize().width);
742 
743         captureLayersButton.setEnabled(true);
744         saveMenuItem.setEnabled(true);
745         showPixelPerfectTree();
746 
747         updateStatus();
748         layoutScene();
749     }
750 
layoutScene()751     private void layoutScene() {
752         TreeGraphLayout<ViewNode, String> layout =
753                 new TreeGraphLayout<ViewNode, String>(scene, 50, 50, 70, 30, true);
754         layout.layout(scene.getRoot());
755     }
756 
updateStatus()757     private void updateStatus() {
758         viewCountLabel.setText("" + scene.getNodes().size() + " views");
759         zoomSlider.setEnabled(scene.getNodes().size() > 0);
760     }
761 
buildExtrasPanel()762     private JPanel buildExtrasPanel() {
763         extrasPanel = new JPanel(new BorderLayout());
764         JScrollPane p = new JScrollPane(layoutView = new LayoutRenderer(scene, sceneView));
765         JScrollBar b = p.getVerticalScrollBar();
766         b.setUnitIncrement(10);
767         extrasPanel.add(p);
768         extrasPanel.add(scene.createSatelliteView(), BorderLayout.SOUTH);
769         extrasPanel.add(buildLayoutViewControlButtons(), BorderLayout.NORTH);
770         return extrasPanel;
771     }
772 
buildLayoutViewControlButtons()773     private JComponent buildLayoutViewControlButtons() {
774         buttonsPanel = new JToolBar();
775         buttonsPanel.setFloatable(false);
776 
777         ButtonGroup group = new ButtonGroup();
778 
779         JToggleButton white = new JToggleButton("On White");
780         toggleColorOnSelect(white);
781         white.putClientProperty("JButton.buttonType", "segmentedTextured");
782         white.putClientProperty("JButton.segmentPosition", "first");
783         white.addActionListener(new ActionListener() {
784             public void actionPerformed(ActionEvent e) {
785                 layoutView.setBackground(Color.WHITE);
786                 layoutView.setForeground(Color.BLACK);
787             }
788         });
789         group.add(white);
790         buttonsPanel.add(white);
791 
792         JToggleButton black = new JToggleButton("On Black");
793         toggleColorOnSelect(black);
794         black.putClientProperty("JButton.buttonType", "segmentedTextured");
795         black.putClientProperty("JButton.segmentPosition", "last");
796         black.addActionListener(new ActionListener() {
797             public void actionPerformed(ActionEvent e) {
798                 layoutView.setBackground(Color.BLACK);
799                 layoutView.setForeground(Color.WHITE);
800             }
801         });
802         group.add(black);
803         buttonsPanel.add(black);
804 
805         black.setSelected(true);
806 
807         JCheckBox showExtras = new JCheckBox("Show Extras");
808         showExtras.putClientProperty("JComponent.sizeVariant", "small");
809         showExtras.addChangeListener(new ChangeListener() {
810             public void stateChanged(ChangeEvent e) {
811                 layoutView.setShowExtras(((JCheckBox) e.getSource()).isSelected());
812             }
813         });
814         buttonsPanel.add(showExtras);
815 
816         return buttonsPanel;
817     }
818 
showCaptureWindow(ViewNode node, String captureParams, Image image)819     private void showCaptureWindow(ViewNode node, String captureParams, Image image) {
820         if (image != null) {
821             layoutView.repaint();
822 
823             JFrame frame = new JFrame(captureParams);
824             JPanel panel = new JPanel(new BorderLayout());
825 
826             final CaptureRenderer label = new CaptureRenderer(new ImageIcon(image), node);
827             label.setBorder(BorderFactory.createEmptyBorder(24, 24, 24, 24));
828 
829             final JPanel solidColor = new JPanel(new BorderLayout());
830             solidColor.setBackground(Color.BLACK);
831             solidColor.add(label);
832 
833             JToolBar toolBar = new JToolBar();
834             toolBar.setFloatable(false);
835 
836             ButtonGroup group = new ButtonGroup();
837 
838             JToggleButton white = new JToggleButton("On White");
839             toggleColorOnSelect(white);
840             white.putClientProperty("JButton.buttonType", "segmentedTextured");
841             white.putClientProperty("JButton.segmentPosition", "first");
842             white.addActionListener(new ActionListener() {
843                 public void actionPerformed(ActionEvent e) {
844                     solidColor.setBackground(Color.WHITE);
845                 }
846             });
847             group.add(white);
848             toolBar.add(white);
849 
850             JToggleButton black = new JToggleButton("On Black");
851             toggleColorOnSelect(black);
852             black.putClientProperty("JButton.buttonType", "segmentedTextured");
853             black.putClientProperty("JButton.segmentPosition", "last");
854             black.addActionListener(new ActionListener() {
855                 public void actionPerformed(ActionEvent e) {
856                     solidColor.setBackground(Color.BLACK);
857                 }
858             });
859             group.add(black);
860             toolBar.add(black);
861 
862             black.setSelected(true);
863 
864             JCheckBox showExtras = new JCheckBox("Show Extras");
865             showExtras.addChangeListener(new ChangeListener() {
866                 public void stateChanged(ChangeEvent e) {
867                     label.setShowExtras(((JCheckBox) e.getSource()).isSelected());
868                 }
869             });
870             toolBar.add(showExtras);
871 
872             panel.add(toolBar, BorderLayout.NORTH);
873             panel.add(solidColor);
874             frame.add(panel);
875 
876             frame.pack();
877             frame.setResizable(false);
878             frame.setLocationRelativeTo(Workspace.this);
879             frame.setVisible(true);
880         }
881     }
882 
reset()883     private void reset() {
884         currentDevice = null;
885         currentWindow = null;
886         currentDeviceChanged();
887         windowsTableModel.setVisible(false);
888         windowsTableModel.clear();
889 
890         showDevicesSelector();
891     }
892 
showDevicesSelector()893     public void showDevicesSelector() {
894         if (mainSplitter != null) {
895             if (pixelPerfectPanel != null) {
896                 screenViewer.start();
897             }
898             mainPanel.remove(graphViewButton.isSelected() ? mainSplitter : pixelPerfectPanel);
899             mainPanel.add(deviceSelector, BorderLayout.CENTER);
900             pixelPerfectPanel = mainSplitter = null;
901             graphViewButton.setSelected(true);
902 
903             hideStatusBarComponents();
904 
905             saveMenuItem.setEnabled(false);
906             showDevicesMenuItem.setEnabled(false);
907             showDevicesButton.setEnabled(false);
908             displayNodeButton.setEnabled(false);
909             captureLayersButton.setEnabled(false);
910             invalidateButton.setEnabled(false);
911             dumpDisplayListButton.setEnabled(false);
912             requestLayoutButton.setEnabled(false);
913             graphViewButton.setEnabled(false);
914             pixelPerfectViewButton.setEnabled(false);
915 
916             if (currentDevice != null) {
917                 if (!DeviceBridge.isViewServerRunning(currentDevice)) {
918                     DeviceBridge.startViewServer(currentDevice);
919                 }
920                 loadWindows().execute();
921                 windowsTableModel.setVisible(true);
922             }
923 
924             validate();
925             repaint();
926         }
927     }
928 
currentDeviceChanged()929     private void currentDeviceChanged() {
930         if (currentDevice == null) {
931             startButton.setEnabled(false);
932             startMenuItem.setEnabled(false);
933             stopButton.setEnabled(false);
934             stopMenuItem.setEnabled(false);
935             refreshButton.setEnabled(false);
936             saveMenuItem.setEnabled(false);
937             loadButton.setEnabled(false);
938             displayNodeButton.setEnabled(false);
939             captureLayersButton.setEnabled(false);
940             invalidateButton.setEnabled(false);
941             dumpDisplayListButton.setEnabled(false);
942             graphViewButton.setEnabled(false);
943             pixelPerfectViewButton.setEnabled(false);
944             requestLayoutButton.setEnabled(false);
945             loadMenuItem.setEnabled(false);
946         } else {
947             loadMenuItem.setEnabled(true);
948             checkForServerOnCurrentDevice();
949         }
950     }
951 
checkForServerOnCurrentDevice()952     private void checkForServerOnCurrentDevice() {
953         if (DeviceBridge.isViewServerRunning(currentDevice)) {
954             startButton.setEnabled(false);
955             startMenuItem.setEnabled(false);
956             stopButton.setEnabled(true);
957             stopMenuItem.setEnabled(true);
958             loadButton.setEnabled(true);
959             refreshButton.setEnabled(true);
960         } else {
961             startButton.setEnabled(true);
962             startMenuItem.setEnabled(true);
963             stopButton.setEnabled(false);
964             stopMenuItem.setEnabled(false);
965             loadButton.setEnabled(false);
966             refreshButton.setEnabled(false);
967         }
968     }
969 
cleanupDevices()970     public void cleanupDevices() {
971         for (IDevice device : devicesTableModel.getDevices()) {
972             DeviceBridge.removeDeviceForward(device);
973         }
974     }
975 
toggleColorOnSelect(JToggleButton button)976     private static void toggleColorOnSelect(JToggleButton button) {
977         if (!OS.isMacOsX() || !OS.isLeopardOrLater()) {
978             return;
979         }
980 
981         button.addChangeListener(new ChangeListener() {
982             public void stateChanged(ChangeEvent event) {
983                 JToggleButton button = (JToggleButton) event.getSource();
984                 if (button.isSelected()) {
985                     button.setForeground(Color.WHITE);
986                 } else {
987                     button.setForeground(Color.BLACK);
988                 }
989             }
990         });
991     }
992 
updateFilter(DocumentEvent e)993     private void updateFilter(DocumentEvent e) {
994         final Document document = e.getDocument();
995         try {
996             updateFilteredNodes(document.getText(0, document.getLength()));
997         } catch (BadLocationException e1) {
998             e1.printStackTrace();
999         }
1000     }
1001 
updateFilteredNodes(String filterText)1002     private void updateFilteredNodes(String filterText) {
1003         final ViewNode root = scene.getRoot();
1004         try {
1005             final Pattern pattern = Pattern.compile(filterText, Pattern.CASE_INSENSITIVE);
1006             filterNodes(pattern, root);
1007         } catch (PatternSyntaxException e) {
1008             filterNodes(null, root);
1009         }
1010         repaint();
1011     }
1012 
filterNodes(Pattern pattern, ViewNode root)1013     private void filterNodes(Pattern pattern, ViewNode root) {
1014         root.filter(pattern);
1015 
1016         for (ViewNode node : root.children) {
1017             filterNodes(pattern, node);
1018         }
1019     }
1020 
beginTask()1021     public void beginTask() {
1022         progress.setVisible(true);
1023     }
1024 
endTask()1025     public void endTask() {
1026         progress.setVisible(false);
1027     }
1028 
showNodeCapture()1029     public SwingWorker<?, ?> showNodeCapture() {
1030         if (scene.getFocusedObject() == null) {
1031             return null;
1032         }
1033         return new CaptureNodeTask();
1034     }
1035 
outputDisplayList()1036     public SwingWorker<?, ?> outputDisplayList() {
1037         if (scene.getFocusedObject() == null) {
1038             return null;
1039         }
1040         return new DumpDisplayListTask();
1041     }
1042 
captureLayers()1043     public SwingWorker<?, ?> captureLayers() {
1044         JFileChooser chooser = new JFileChooser();
1045         chooser.setFileFilter(new PsdFileFilter());
1046         int choice = chooser.showSaveDialog(sceneView);
1047         if (choice == JFileChooser.APPROVE_OPTION) {
1048             return new CaptureLayersTask(chooser.getSelectedFile());
1049         } else {
1050             return null;
1051         }
1052     }
1053 
startServer()1054     public SwingWorker<?, ?> startServer() {
1055         return new StartServerTask();
1056     }
1057 
stopServer()1058     public SwingWorker<?, ?> stopServer() {
1059         return new StopServerTask();
1060     }
1061 
loadWindows()1062     public SwingWorker<?, ?> loadWindows() {
1063         return new LoadWindowsTask();
1064     }
1065 
loadGraph()1066     public SwingWorker<?, ?> loadGraph() {
1067         return new LoadGraphTask();
1068     }
1069 
invalidateView()1070     public SwingWorker<?, ?> invalidateView() {
1071         if (scene.getFocusedObject() == null) {
1072             return null;
1073         }
1074         return new InvalidateTask();
1075     }
1076 
requestLayout()1077     public SwingWorker<?, ?> requestLayout() {
1078         if (scene.getFocusedObject() == null) {
1079             return null;
1080         }
1081         return new RequestLayoutTask();
1082     }
1083 
saveSceneAsImage()1084     public SwingWorker<?, ?> saveSceneAsImage() {
1085         JFileChooser chooser = new JFileChooser();
1086         chooser.setFileFilter(new PngFileFilter());
1087         int choice = chooser.showSaveDialog(sceneView);
1088         if (choice == JFileChooser.APPROVE_OPTION) {
1089             return new SaveSceneTask(chooser.getSelectedFile());
1090         } else {
1091             return null;
1092         }
1093     }
1094 
1095     private class InvalidateTask extends SwingWorker<Object, Void> {
1096         private String captureParams;
1097 
InvalidateTask()1098         private InvalidateTask() {
1099             captureParams = scene.getFocusedObject().toString();
1100             beginTask();
1101         }
1102 
1103         @Override
1104         @WorkerThread
doInBackground()1105         protected Object doInBackground() throws Exception {
1106             ViewManager.invalidate(currentDevice, currentWindow, captureParams);
1107             return null;
1108         }
1109 
1110         @Override
done()1111         protected void done() {
1112             endTask();
1113         }
1114     }
1115 
1116     private class DumpDisplayListTask extends SwingWorker<Object, Void> {
1117         private String captureParams;
1118 
DumpDisplayListTask()1119         private DumpDisplayListTask() {
1120             captureParams = scene.getFocusedObject().toString();
1121             beginTask();
1122         }
1123 
1124         @Override
1125         @WorkerThread
doInBackground()1126         protected Object doInBackground() throws Exception {
1127             ViewManager.outputDisplayList(currentDevice, currentWindow, captureParams);
1128             return null;
1129         }
1130 
1131         @Override
done()1132         protected void done() {
1133             endTask();
1134         }
1135     }
1136 
1137     private class RequestLayoutTask extends SwingWorker<Object, Void> {
1138         private String captureParams;
1139 
RequestLayoutTask()1140         private RequestLayoutTask() {
1141             captureParams = scene.getFocusedObject().toString();
1142             beginTask();
1143         }
1144 
1145         @Override
1146         @WorkerThread
doInBackground()1147         protected Object doInBackground() throws Exception {
1148             ViewManager.requestLayout(currentDevice, currentWindow, captureParams);
1149             return null;
1150         }
1151 
1152         @Override
done()1153         protected void done() {
1154             endTask();
1155         }
1156     }
1157 
1158     private class CaptureLayersTask extends SwingWorker<Boolean, Void> {
1159         private File file;
1160 
CaptureLayersTask(File file)1161         private CaptureLayersTask(File file) {
1162             this.file = file;
1163             beginTask();
1164         }
1165 
1166         @Override
1167         @WorkerThread
doInBackground()1168         protected Boolean doInBackground() throws Exception {
1169             return CaptureLoader.saveLayers(currentDevice, currentWindow, file);
1170         }
1171 
1172         @Override
done()1173         protected void done() {
1174             endTask();
1175         }
1176     }
1177 
1178     private class CaptureNodeTask extends SwingWorker<Image, Void> {
1179         private String captureParams;
1180         private ViewNode node;
1181 
CaptureNodeTask()1182         private CaptureNodeTask() {
1183             node = (ViewNode) scene.getFocusedObject();
1184             captureParams = node.toString();
1185             beginTask();
1186         }
1187 
1188         @Override
1189         @WorkerThread
doInBackground()1190         protected Image doInBackground() throws Exception {
1191             node.image = CaptureLoader.loadCapture(currentDevice, currentWindow, captureParams);
1192             return node.image;
1193         }
1194 
1195         @Override
done()1196         protected void done() {
1197             try {
1198                 Image image = get();
1199                 showCaptureWindow(node, captureParams, image);
1200             } catch (InterruptedException e) {
1201                 e.printStackTrace();
1202             } catch (ExecutionException e) {
1203                 e.printStackTrace();
1204             } finally {
1205                 endTask();
1206             }
1207         }
1208     }
1209 
1210     static class WindowsResult {
1211         Window[] windows;
1212         int serverVersion;
1213         int protocolVersion;
1214     }
1215 
1216     private class LoadWindowsTask extends SwingWorker<WindowsResult, Void> {
LoadWindowsTask()1217         private LoadWindowsTask() {
1218             beginTask();
1219         }
1220 
1221         @Override
1222         @WorkerThread
doInBackground()1223         protected WindowsResult doInBackground() throws Exception {
1224             WindowsResult r = new WindowsResult();
1225             r.protocolVersion = VersionLoader.loadProtocolVersion(currentDevice);
1226             r.serverVersion = VersionLoader.loadServerVersion(currentDevice);
1227             r.windows = WindowsLoader.loadWindows(currentDevice,
1228                     r.protocolVersion, r.serverVersion);
1229             return r;
1230         }
1231 
1232         @Override
done()1233         protected void done() {
1234             try {
1235                 WindowsResult result = get();
1236                 protocolVersion = result.protocolVersion;
1237                 serverVersion = result.serverVersion;
1238                 setupProtocolDependentToolbar();
1239                 windowsTableModel.clear();
1240                 windowsTableModel.addWindows(result.windows);
1241             } catch (ExecutionException e) {
1242                 e.printStackTrace();
1243             } catch (InterruptedException e) {
1244                 e.printStackTrace();
1245             } finally {
1246                 endTask();
1247             }
1248         }
1249     }
1250 
1251     private class StartServerTask extends SwingWorker<Object, Void> {
StartServerTask()1252         public StartServerTask() {
1253             beginTask();
1254         }
1255 
1256         @Override
1257         @WorkerThread
doInBackground()1258         protected Object doInBackground() {
1259             DeviceBridge.startViewServer(currentDevice);
1260             return null;
1261         }
1262 
1263         @Override
done()1264         protected void done() {
1265             new LoadWindowsTask().execute();
1266             windowsTableModel.setVisible(true);
1267             checkForServerOnCurrentDevice();
1268             endTask();
1269         }
1270     }
1271 
1272     private class StopServerTask extends SwingWorker<Object, Void> {
StopServerTask()1273         public StopServerTask() {
1274             beginTask();
1275         }
1276 
1277         @Override
1278         @WorkerThread
doInBackground()1279         protected Object doInBackground() {
1280             DeviceBridge.stopViewServer(currentDevice);
1281             return null;
1282         }
1283 
1284         @Override
done()1285         protected void done() {
1286             windowsTableModel.setVisible(false);
1287             windowsTableModel.clear();
1288             checkForServerOnCurrentDevice();
1289             endTask();
1290         }
1291     }
1292 
1293     private class LoadGraphTask extends SwingWorker<double[], Void> {
LoadGraphTask()1294         public LoadGraphTask() {
1295             beginTask();
1296         }
1297 
1298         @Override
1299         @WorkerThread
doInBackground()1300         protected double[] doInBackground() {
1301             scene = ViewHierarchyLoader.loadScene(currentDevice, currentWindow);
1302             return ProfilesLoader.loadProfiles(currentDevice, currentWindow,
1303                     scene.getRoot().toString());
1304         }
1305 
1306         @Override
done()1307         protected void done() {
1308             try {
1309                 createGraph(scene);
1310                 updateProfiles(get());
1311             } catch (InterruptedException e) {
1312                 e.printStackTrace();
1313             } catch (ExecutionException e) {
1314                 e.printStackTrace();
1315             } finally {
1316                 endTask();
1317             }
1318         }
1319     }
1320 
1321     private class SaveSceneTask extends SwingWorker<Object, Void> {
1322         private File file;
1323 
SaveSceneTask(File file)1324         private SaveSceneTask(File file) {
1325             this.file = file;
1326             beginTask();
1327         }
1328 
1329         @Override
1330         @WorkerThread
doInBackground()1331         protected Object doInBackground() {
1332             if (sceneView == null) {
1333                 return null;
1334             }
1335 
1336             try {
1337                 BufferedImage image = new BufferedImage(sceneView.getWidth(),
1338                         sceneView.getHeight(), BufferedImage.TYPE_INT_RGB);
1339                 Graphics2D g2 = image.createGraphics();
1340                 sceneView.paint(g2);
1341                 g2.dispose();
1342                 ImageIO.write(image, "PNG", file);
1343             } catch (IOException ex) {
1344                 ex.printStackTrace();
1345             }
1346             return null;
1347         }
1348 
1349         @Override
done()1350         protected void done() {
1351             endTask();
1352         }
1353     }
1354 
1355     private class SceneFocusListener implements ObjectSceneListener {
1356 
objectAdded(ObjectSceneEvent arg0, Object arg1)1357         public void objectAdded(ObjectSceneEvent arg0, Object arg1) {
1358         }
1359 
objectRemoved(ObjectSceneEvent arg0, Object arg1)1360         public void objectRemoved(ObjectSceneEvent arg0, Object arg1) {
1361         }
1362 
objectStateChanged(ObjectSceneEvent arg0, Object arg1, ObjectState arg2, ObjectState arg3)1363         public void objectStateChanged(ObjectSceneEvent arg0, Object arg1,
1364                 ObjectState arg2, ObjectState arg3) {
1365         }
1366 
selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection, Set<Object> newSelection)1367         public void selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection,
1368                 Set<Object> newSelection) {
1369         }
1370 
highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2)1371         public void highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2) {
1372         }
1373 
hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2)1374         public void hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) {
1375         }
1376 
focusChanged(ObjectSceneEvent e, Object oldFocus, Object newFocus)1377         public void focusChanged(ObjectSceneEvent e, Object oldFocus, Object newFocus) {
1378             displayNodeButton.setEnabled(true);
1379             invalidateButton.setEnabled(true);
1380             dumpDisplayListButton.setEnabled(true);
1381             requestLayoutButton.setEnabled(true);
1382 
1383             Set<Object> selection = new HashSet<Object>();
1384             selection.add(newFocus);
1385             scene.setSelectedObjects(selection);
1386 
1387             showProperties((ViewNode) newFocus);
1388             layoutView.repaint();
1389         }
1390     }
1391 
1392     private class NodeClickListener extends MouseAdapter {
1393         @Override
mouseClicked(MouseEvent e)1394         public void mouseClicked(MouseEvent e) {
1395             if (e.getClickCount() == 2) {
1396                 showNodeCapture().execute();
1397             }
1398         }
1399     }
1400 
1401     private class WheelZoomListener implements MouseWheelListener {
mouseWheelMoved(MouseWheelEvent e)1402         public void mouseWheelMoved(MouseWheelEvent e) {
1403             if (zoomSlider != null) {
1404                 int val = zoomSlider.getValue();
1405                 val -= e.getWheelRotation() * 10;
1406                 zoomSlider.setValue(val);
1407             }
1408         }
1409     }
1410     private class DevicesTableModel extends DefaultTableModel implements
1411             AndroidDebugBridge.IDeviceChangeListener {
1412 
1413         private ArrayList<IDevice> devices;
1414 
DevicesTableModel()1415         private DevicesTableModel() {
1416             devices = new ArrayList<IDevice>();
1417         }
1418 
1419         @Override
getColumnCount()1420         public int getColumnCount() {
1421             return 1;
1422         }
1423 
1424         @Override
isCellEditable(int row, int column)1425         public boolean isCellEditable(int row, int column) {
1426             return false;
1427         }
1428 
1429         @Override
getValueAt(int row, int column)1430         public Object getValueAt(int row, int column) {
1431             return devices.get(row);
1432         }
1433 
1434         @Override
getColumnName(int column)1435         public String getColumnName(int column) {
1436             return "Devices";
1437         }
1438 
1439         @WorkerThread
deviceConnected(final IDevice device)1440         public void deviceConnected(final IDevice device) {
1441             DeviceBridge.setupDeviceForward(device);
1442 
1443             SwingUtilities.invokeLater(new Runnable() {
1444                 public void run() {
1445                     addDevice(device);
1446                 }
1447             });
1448         }
1449 
1450         @WorkerThread
deviceDisconnected(final IDevice device)1451         public void deviceDisconnected(final IDevice device) {
1452             DeviceBridge.removeDeviceForward(device);
1453 
1454             SwingUtilities.invokeLater(new Runnable() {
1455                 public void run() {
1456                     removeDevice(device);
1457                 }
1458             });
1459         }
1460 
addDevice(IDevice device)1461         public void addDevice(IDevice device) {
1462             if (!devices.contains(device)) {
1463                 devices.add(device);
1464                 fireTableDataChanged();
1465             }
1466         }
1467 
removeDevice(IDevice device)1468         public void removeDevice(IDevice device) {
1469             if (device.equals(currentDevice)) {
1470                 reset();
1471             }
1472 
1473             if (devices.contains(device)) {
1474                 devices.remove(device);
1475                 fireTableDataChanged();
1476             }
1477         }
1478 
1479         @WorkerThread
deviceChanged(IDevice device, int changeMask)1480         public void deviceChanged(IDevice device, int changeMask) {
1481             if ((changeMask & IDevice.CHANGE_STATE) != 0 &&
1482                     device.isOnline()) {
1483                 // if the device state changed and it's now online, we set up its port forwarding.
1484                 DeviceBridge.setupDeviceForward(device);
1485             } else if (device == currentDevice && (changeMask & IDevice.CHANGE_CLIENT_LIST) != 0) {
1486                 // if the changed device is the current one and the client list changed, we update
1487                 // the UI.
1488                 loadWindows().execute();
1489                 windowsTableModel.setVisible(true);
1490             }
1491         }
1492 
1493         @Override
getRowCount()1494         public int getRowCount() {
1495             return devices == null ? 0 : devices.size();
1496         }
1497 
getDevice(int index)1498         public IDevice getDevice(int index) {
1499             return index < devices.size() ? devices.get(index) : null;
1500         }
1501 
getDevices()1502         public IDevice[] getDevices() {
1503             return devices.toArray(new IDevice[devices.size()]);
1504         }
1505     }
1506 
1507     private static class WindowsTableModel extends DefaultTableModel {
1508         private ArrayList<Window> windows;
1509         private boolean visible;
1510 
WindowsTableModel()1511         private WindowsTableModel() {
1512             windows = new ArrayList<Window>();
1513             windows.add(Window.FOCUSED_WINDOW);
1514         }
1515 
1516         @Override
getColumnCount()1517         public int getColumnCount() {
1518             return 1;
1519         }
1520 
1521         @Override
isCellEditable(int row, int column)1522         public boolean isCellEditable(int row, int column) {
1523             return false;
1524         }
1525 
1526         @Override
getColumnName(int column)1527         public String getColumnName(int column) {
1528             return "Windows";
1529         }
1530 
1531         @Override
getValueAt(int row, int column)1532         public Object getValueAt(int row, int column) {
1533             return windows.get(row);
1534         }
1535 
1536         @Override
getRowCount()1537         public int getRowCount() {
1538             return !visible || windows == null ? 0 : windows.size();
1539         }
1540 
setVisible(boolean visible)1541         public void setVisible(boolean visible) {
1542             this.visible = visible;
1543             fireTableDataChanged();
1544         }
1545 
addWindow(Window window)1546         public void addWindow(Window window) {
1547             windows.add(window);
1548             fireTableDataChanged();
1549         }
1550 
addWindows(Window[] windowsList)1551         public void addWindows(Window[] windowsList) {
1552             //noinspection ManualArrayToCollectionCopy
1553             for (Window window : windowsList) {
1554                 windows.add(window);
1555             }
1556             fireTableDataChanged();
1557         }
1558 
clear()1559         public void clear() {
1560             windows.clear();
1561             windows.add(Window.FOCUSED_WINDOW);
1562         }
1563 
getWindow(int index)1564         public Window getWindow(int index) {
1565             return windows.get(index);
1566         }
1567     }
1568 
1569     private class DeviceSelectedListener implements ListSelectionListener {
valueChanged(ListSelectionEvent event)1570         public void valueChanged(ListSelectionEvent event) {
1571             if (event.getValueIsAdjusting()) {
1572                 return;
1573             }
1574 
1575             int row = devices.getSelectedRow();
1576             if (row >= 0) {
1577                 currentDevice = devicesTableModel.getDevice(row);
1578                 currentDeviceChanged();
1579                 if (currentDevice != null) {
1580                     if (!DeviceBridge.isViewServerRunning(currentDevice)) {
1581                         DeviceBridge.startViewServer(currentDevice);
1582                         checkForServerOnCurrentDevice();
1583                     }
1584                     loadWindows().execute();
1585                     windowsTableModel.setVisible(true);
1586                 }
1587             } else {
1588                 currentDevice = null;
1589                 currentDeviceChanged();
1590                 windowsTableModel.setVisible(false);
1591                 windowsTableModel.clear();
1592             }
1593         }
1594     }
1595 
1596     private class WindowSelectedListener implements ListSelectionListener {
valueChanged(ListSelectionEvent event)1597         public void valueChanged(ListSelectionEvent event) {
1598             if (event.getValueIsAdjusting()) {
1599                 return;
1600             }
1601 
1602             int row = windows.getSelectedRow();
1603             if (row >= 0) {
1604                 currentWindow = windowsTableModel.getWindow(row);
1605             } else {
1606                 currentWindow = Window.FOCUSED_WINDOW;
1607             }
1608         }
1609     }
1610 
1611     private static class ViewsTreeCellRenderer extends DefaultTreeCellRenderer {
getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)1612         public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
1613                 boolean expanded, boolean leaf, int row, boolean hasFocus) {
1614 
1615             final String name = ((ViewNode) value).name;
1616             value = name.substring(name.lastIndexOf('.') + 1, name.lastIndexOf('@'));
1617             return super.getTreeCellRendererComponent(tree, value, selected, expanded,
1618                     leaf, row, hasFocus);
1619         }
1620     }
1621 }
1622