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