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