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