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