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