aboutsummaryrefslogtreecommitdiff
path: root/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
diff options
context:
space:
mode:
Diffstat (limited to 'hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java')
-rw-r--r--hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java1621
1 files changed, 1621 insertions, 0 deletions
diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
new file mode 100644
index 000000000..bfa15b3d4
--- /dev/null
+++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
@@ -0,0 +1,1621 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.hierarchyviewer.ui;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.android.hierarchyviewer.device.DeviceBridge;
+import com.android.hierarchyviewer.device.Window;
+import com.android.hierarchyviewer.laf.UnifiedContentBorder;
+import com.android.hierarchyviewer.scene.CaptureLoader;
+import com.android.hierarchyviewer.scene.ProfilesLoader;
+import com.android.hierarchyviewer.scene.VersionLoader;
+import com.android.hierarchyviewer.scene.ViewHierarchyLoader;
+import com.android.hierarchyviewer.scene.ViewHierarchyScene;
+import com.android.hierarchyviewer.scene.ViewManager;
+import com.android.hierarchyviewer.scene.ViewNode;
+import com.android.hierarchyviewer.scene.WindowsLoader;
+import com.android.hierarchyviewer.ui.action.CaptureLayersAction;
+import com.android.hierarchyviewer.ui.action.CaptureNodeAction;
+import com.android.hierarchyviewer.ui.action.DumpDisplayListAction;
+import com.android.hierarchyviewer.ui.action.ExitAction;
+import com.android.hierarchyviewer.ui.action.InvalidateAction;
+import com.android.hierarchyviewer.ui.action.LoadGraphAction;
+import com.android.hierarchyviewer.ui.action.RefreshWindowsAction;
+import com.android.hierarchyviewer.ui.action.RequestLayoutAction;
+import com.android.hierarchyviewer.ui.action.SaveSceneAction;
+import com.android.hierarchyviewer.ui.action.ShowDevicesAction;
+import com.android.hierarchyviewer.ui.action.StartServerAction;
+import com.android.hierarchyviewer.ui.action.StopServerAction;
+import com.android.hierarchyviewer.ui.model.ProfilesTableModel;
+import com.android.hierarchyviewer.ui.model.PropertiesTableModel;
+import com.android.hierarchyviewer.ui.model.ViewsTreeModel;
+import com.android.hierarchyviewer.ui.util.IconLoader;
+import com.android.hierarchyviewer.ui.util.PngFileFilter;
+import com.android.hierarchyviewer.ui.util.PsdFileFilter;
+import com.android.hierarchyviewer.util.OS;
+import com.android.hierarchyviewer.util.WorkerThread;
+
+import org.netbeans.api.visual.graph.layout.TreeGraphLayout;
+import org.netbeans.api.visual.model.ObjectSceneEvent;
+import org.netbeans.api.visual.model.ObjectSceneEventType;
+import org.netbeans.api.visual.model.ObjectSceneListener;
+import org.netbeans.api.visual.model.ObjectState;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.imageio.ImageIO;
+import javax.swing.ActionMap;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.JTree;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreePath;
+
+public class Workspace extends JFrame {
+ private JLabel viewCountLabel;
+ private JSlider zoomSlider;
+ private JSplitPane sideSplitter;
+ private JSplitPane mainSplitter;
+ private JTable propertiesTable;
+ private JTable profilingTable;
+ private JComponent pixelPerfectPanel;
+ private JTree pixelPerfectTree;
+ private ScreenViewer screenViewer;
+
+ private JPanel extrasPanel;
+ private LayoutRenderer layoutView;
+
+ private JScrollPane sceneScroller;
+ private JComponent sceneView;
+
+ private ViewHierarchyScene scene;
+
+ private ActionMap actionsMap;
+ private JPanel mainPanel;
+ private JProgressBar progress;
+ private JToolBar buttonsPanel;
+ private JToolBar commandButtonsPanel;
+
+ private JComponent deviceSelector;
+ private DevicesTableModel devicesTableModel;
+ private WindowsTableModel windowsTableModel;
+
+ private IDevice currentDevice;
+ private Window currentWindow = Window.FOCUSED_WINDOW;
+
+ private JButton displayNodeButton;
+ private JButton dumpDisplayListButton;
+ private JButton captureLayersButton;
+ private JButton invalidateButton;
+ private JButton requestLayoutButton;
+ private JButton loadButton;
+ private JButton startButton;
+ private JButton stopButton;
+ private JButton showDevicesButton;
+ private JButton refreshButton;
+ private JToggleButton graphViewButton;
+ private JToggleButton pixelPerfectViewButton;
+ private JMenuItem saveMenuItem;
+ private JMenuItem showDevicesMenuItem;
+ private JMenuItem loadMenuItem;
+ private JMenuItem startMenuItem;
+ private JMenuItem stopMenuItem;
+ private JTable devices;
+ private JTable windows;
+ private JLabel minZoomLabel;
+ private JLabel maxZoomLabel;
+ private JTextField filterText;
+ private JLabel filterLabel;
+
+ private int protocolVersion;
+ private int serverVersion;
+
+ public Workspace() {
+ super("Hierarchy Viewer");
+
+ buildActions();
+ add(buildMainPanel());
+ setJMenuBar(buildMenuBar());
+
+ devices.changeSelection(0, 0, false, false);
+ currentDeviceChanged();
+
+ pack();
+ }
+
+ private void buildActions() {
+ actionsMap = new ActionMap();
+ actionsMap.put(ExitAction.ACTION_NAME, new ExitAction(this));
+ actionsMap.put(ShowDevicesAction.ACTION_NAME, new ShowDevicesAction(this));
+ actionsMap.put(LoadGraphAction.ACTION_NAME, new LoadGraphAction(this));
+ actionsMap.put(SaveSceneAction.ACTION_NAME, new SaveSceneAction(this));
+ actionsMap.put(StartServerAction.ACTION_NAME, new StartServerAction(this));
+ actionsMap.put(StopServerAction.ACTION_NAME, new StopServerAction(this));
+ actionsMap.put(InvalidateAction.ACTION_NAME, new InvalidateAction(this));
+ actionsMap.put(RequestLayoutAction.ACTION_NAME, new RequestLayoutAction(this));
+ actionsMap.put(DumpDisplayListAction.ACTION_NAME, new DumpDisplayListAction(this));
+ actionsMap.put(CaptureNodeAction.ACTION_NAME, new CaptureNodeAction(this));
+ actionsMap.put(CaptureLayersAction.ACTION_NAME, new CaptureLayersAction(this));
+ actionsMap.put(RefreshWindowsAction.ACTION_NAME, new RefreshWindowsAction(this));
+ }
+
+ private JComponent buildMainPanel() {
+ mainPanel = new JPanel();
+ mainPanel.setLayout(new BorderLayout());
+ commandButtonsPanel = buildToolBar();
+ mainPanel.add(commandButtonsPanel, BorderLayout.PAGE_START);
+ mainPanel.add(deviceSelector = buildDeviceSelector(), BorderLayout.CENTER);
+ mainPanel.add(buildStatusPanel(), BorderLayout.SOUTH);
+
+ mainPanel.setPreferredSize(new Dimension(1200, 800));
+
+ return mainPanel;
+ }
+
+ private JComponent buildGraphPanel() {
+ sceneScroller = new JScrollPane();
+ sceneScroller.setBorder(null);
+
+ mainSplitter = new JSplitPane();
+ mainSplitter.setResizeWeight(1.0);
+ mainSplitter.setContinuousLayout(true);
+ if (OS.isMacOsX() && OS.isLeopardOrLater()) {
+ mainSplitter.setBorder(new UnifiedContentBorder());
+ }
+
+ mainSplitter.setLeftComponent(sceneScroller);
+ mainSplitter.setRightComponent(buildSideSplitter());
+
+ return mainSplitter;
+ }
+
+ private JComponent buildDeviceSelector() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ if (OS.isMacOsX() && OS.isLeopardOrLater()) {
+ panel.setBorder(new UnifiedContentBorder());
+ }
+
+ devicesTableModel = new DevicesTableModel();
+ for (IDevice device : DeviceBridge.getDevices()) {
+ DeviceBridge.setupDeviceForward(device);
+ devicesTableModel.addDevice(device);
+ }
+ DeviceBridge.startListenForDevices(devicesTableModel);
+
+ devices = new JTable(devicesTableModel);
+ devices.getSelectionModel().addListSelectionListener(new DeviceSelectedListener());
+ devices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ devices.setBorder(null);
+ JScrollPane devicesScroller = new JScrollPane(devices);
+ devicesScroller.setBorder(null);
+ panel.add(devicesScroller, new GridBagConstraints(0, 0, 1, 1, 0.5, 1.0,
+ GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),
+ 0, 0));
+
+ windowsTableModel = new WindowsTableModel();
+ windowsTableModel.setVisible(false);
+
+ windows = new JTable(windowsTableModel);
+ windows.getSelectionModel().addListSelectionListener(new WindowSelectedListener());
+ windows.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ windows.setBorder(null);
+ JScrollPane windowsScroller = new JScrollPane(windows);
+ windowsScroller.setBorder(null);
+ panel.add(windowsScroller, new GridBagConstraints(2, 0, 1, 1, 0.5, 1.0,
+ GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),
+ 0, 0));
+
+ return panel;
+ }
+
+ private JComponent buildSideSplitter() {
+ propertiesTable = new JTable();
+ propertiesTable.setModel(new DefaultTableModel(new Object[][] { },
+ new String[] { "Property", "Value" }));
+ propertiesTable.setBorder(null);
+ propertiesTable.getTableHeader().setBorder(null);
+
+ JScrollPane tableScroller = new JScrollPane(propertiesTable);
+ tableScroller.setBorder(null);
+
+ profilingTable = new JTable();
+ profilingTable.setModel(new DefaultTableModel(new Object[][] {
+ { " " , " " }, { " " , " " }, { " " , " " } },
+ new String[] { "Operation", "Duration (ms)" }));
+ profilingTable.setBorder(null);
+ profilingTable.getTableHeader().setBorder(null);
+
+ JScrollPane firstTableScroller = new JScrollPane(profilingTable);
+ firstTableScroller.setBorder(null);
+
+ setVisibleRowCount(profilingTable, 5);
+ firstTableScroller.setMinimumSize(profilingTable.getPreferredScrollableViewportSize());
+
+ JSplitPane tablesSplitter = new JSplitPane();
+ tablesSplitter.setBorder(null);
+ tablesSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT);
+ tablesSplitter.setResizeWeight(0);
+ tablesSplitter.setLeftComponent(firstTableScroller);
+ tablesSplitter.setBottomComponent(tableScroller);
+ tablesSplitter.setContinuousLayout(true);
+
+ sideSplitter = new JSplitPane();
+ sideSplitter.setBorder(null);
+ sideSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT);
+ sideSplitter.setResizeWeight(0.5);
+ sideSplitter.setLeftComponent(tablesSplitter);
+ sideSplitter.setBottomComponent(null);
+ sideSplitter.setContinuousLayout(true);
+
+ return sideSplitter;
+ }
+
+ private JPanel buildStatusPanel() {
+ JPanel statusPanel = new JPanel();
+ statusPanel.setLayout(new BorderLayout());
+
+ JPanel leftSide = new JPanel();
+ leftSide.setOpaque(false);
+ leftSide.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 5));
+ leftSide.add(Box.createHorizontalStrut(6));
+
+ ButtonGroup group = new ButtonGroup();
+
+ graphViewButton = new JToggleButton(IconLoader.load(getClass(),
+ "/images/icon-graph-view.png"));
+ graphViewButton.setSelectedIcon(IconLoader.load(getClass(),
+ "/images/icon-graph-view-selected.png"));
+ graphViewButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ graphViewButton.putClientProperty("JButton.segmentPosition", "first");
+ graphViewButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ toggleGraphView();
+ }
+ });
+ group.add(graphViewButton);
+ leftSide.add(graphViewButton);
+
+ pixelPerfectViewButton = new JToggleButton(IconLoader.load(getClass(),
+ "/images/icon-pixel-perfect-view.png"));
+ pixelPerfectViewButton.setSelectedIcon(IconLoader.load(getClass(),
+ "/images/icon-pixel-perfect-view-selected.png"));
+ pixelPerfectViewButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ pixelPerfectViewButton.putClientProperty("JButton.segmentPosition", "last");
+ pixelPerfectViewButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ togglePixelPerfectView();
+ }
+ });
+ group.add(pixelPerfectViewButton);
+ leftSide.add(pixelPerfectViewButton);
+
+ graphViewButton.setSelected(true);
+
+ filterText = new JTextField(20);
+ filterText.putClientProperty("JComponent.sizeVariant", "small");
+ filterText.getDocument().addDocumentListener(new DocumentListener() {
+ public void insertUpdate(DocumentEvent e) {
+ updateFilter(e);
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ updateFilter(e);
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ updateFilter(e);
+ }
+ });
+
+ filterLabel = new JLabel("Filter by class or id:");
+ filterLabel.putClientProperty("JComponent.sizeVariant", "small");
+ filterLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 6));
+
+ leftSide.add(filterLabel);
+ leftSide.add(filterText);
+
+ minZoomLabel = new JLabel();
+ minZoomLabel.setText("20%");
+ minZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
+ minZoomLabel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 0));
+ leftSide.add(minZoomLabel);
+
+ zoomSlider = new JSlider();
+ zoomSlider.putClientProperty("JComponent.sizeVariant", "small");
+ zoomSlider.setMaximum(200);
+ zoomSlider.setMinimum(20);
+ zoomSlider.setValue(100);
+ zoomSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent evt) {
+ zoomSliderStateChanged(evt);
+ }
+ });
+ leftSide.add(zoomSlider);
+
+ maxZoomLabel = new JLabel();
+ maxZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
+ maxZoomLabel.setText("200%");
+ leftSide.add(maxZoomLabel);
+
+ viewCountLabel = new JLabel();
+ viewCountLabel.setText("0 views");
+ viewCountLabel.putClientProperty("JComponent.sizeVariant", "small");
+ viewCountLabel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 0));
+ leftSide.add(viewCountLabel);
+
+ statusPanel.add(leftSide, BorderLayout.LINE_START);
+
+ JPanel rightSide = new JPanel();
+ rightSide.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 12));
+ rightSide.setLayout(new FlowLayout(FlowLayout.RIGHT));
+
+ progress = new JProgressBar();
+ progress.setVisible(false);
+ progress.setIndeterminate(true);
+ progress.putClientProperty("JComponent.sizeVariant", "mini");
+ progress.putClientProperty("JProgressBar.style", "circular");
+ rightSide.add(progress);
+
+ statusPanel.add(rightSide, BorderLayout.LINE_END);
+
+ hideStatusBarComponents();
+
+ return statusPanel;
+ }
+
+ private void hideStatusBarComponents() {
+ viewCountLabel.setVisible(false);
+ zoomSlider.setVisible(false);
+ minZoomLabel.setVisible(false);
+ maxZoomLabel.setVisible(false);
+ filterLabel.setVisible(false);
+ filterText.setVisible(false);
+ }
+
+ private JToolBar buildToolBar() {
+ JToolBar toolBar = new JToolBar();
+ toolBar.setFloatable(false);
+ toolBar.setRollover(true);
+
+ startButton = new JButton();
+ startButton.setAction(actionsMap.get(StartServerAction.ACTION_NAME));
+ startButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ startButton.putClientProperty("JButton.segmentPosition", "first");
+ toolBar.add(startButton);
+
+ stopButton = new JButton();
+ stopButton.setAction(actionsMap.get(StopServerAction.ACTION_NAME));
+ stopButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ stopButton.putClientProperty("JButton.segmentPosition", "middle");
+ toolBar.add(stopButton);
+
+ refreshButton = new JButton();
+ refreshButton.setAction(actionsMap.get(RefreshWindowsAction.ACTION_NAME));
+ refreshButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ refreshButton.putClientProperty("JButton.segmentPosition", "last");
+ toolBar.add(refreshButton);
+
+ showDevicesButton = new JButton();
+ showDevicesButton.setAction(actionsMap.get(ShowDevicesAction.ACTION_NAME));
+ showDevicesButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ showDevicesButton.putClientProperty("JButton.segmentPosition", "first");
+ toolBar.add(showDevicesButton);
+ showDevicesButton.setEnabled(false);
+
+ loadButton = new JButton();
+ loadButton.setAction(actionsMap.get(LoadGraphAction.ACTION_NAME));
+ loadButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ loadButton.putClientProperty("JButton.segmentPosition", "last");
+ toolBar.add(loadButton);
+
+ displayNodeButton = new JButton();
+ displayNodeButton.setAction(actionsMap.get(CaptureNodeAction.ACTION_NAME));
+ displayNodeButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ displayNodeButton.putClientProperty("JButton.segmentPosition", "first");
+ toolBar.add(displayNodeButton);
+
+ dumpDisplayListButton = new JButton();
+ dumpDisplayListButton.setAction(actionsMap.get(DumpDisplayListAction.ACTION_NAME));
+ dumpDisplayListButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ dumpDisplayListButton.putClientProperty("JButton.segmentPosition", "middle");
+
+ captureLayersButton = new JButton();
+ captureLayersButton.setAction(actionsMap.get(CaptureLayersAction.ACTION_NAME));
+ captureLayersButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ captureLayersButton.putClientProperty("JButton.segmentPosition", "middle");
+ toolBar.add(captureLayersButton);
+
+ invalidateButton = new JButton();
+ invalidateButton.setAction(actionsMap.get(InvalidateAction.ACTION_NAME));
+ invalidateButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ invalidateButton.putClientProperty("JButton.segmentPosition", "middle");
+ toolBar.add(invalidateButton);
+
+ requestLayoutButton = new JButton();
+ requestLayoutButton.setAction(actionsMap.get(RequestLayoutAction.ACTION_NAME));
+ requestLayoutButton.putClientProperty("JButton.buttonType", "segmentedTextured");
+ requestLayoutButton.putClientProperty("JButton.segmentPosition", "last");
+ toolBar.add(requestLayoutButton);
+
+ return toolBar;
+ }
+
+ private void setupProtocolDependentToolbar() {
+ // Some functionality is only enabled in certain versions of the protocol.
+ // Add/remove those buttons here
+ if (protocolVersion < 4) {
+ commandButtonsPanel.remove(dumpDisplayListButton);
+ } else if (dumpDisplayListButton.getParent() == null) {
+ commandButtonsPanel.add(dumpDisplayListButton,
+ commandButtonsPanel.getComponentCount() - 1);
+ }
+ }
+
+ private JMenuBar buildMenuBar() {
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu();
+ JMenu viewMenu = new JMenu();
+ JMenu viewHierarchyMenu = new JMenu();
+ JMenu serverMenu = new JMenu();
+
+ saveMenuItem = new JMenuItem();
+ JMenuItem exitMenuItem = new JMenuItem();
+
+ showDevicesMenuItem = new JMenuItem();
+
+ loadMenuItem = new JMenuItem();
+
+ startMenuItem = new JMenuItem();
+ stopMenuItem = new JMenuItem();
+
+ fileMenu.setText("File");
+
+ saveMenuItem.setAction(actionsMap.get(SaveSceneAction.ACTION_NAME));
+ fileMenu.add(saveMenuItem);
+
+ exitMenuItem.setAction(actionsMap.get(ExitAction.ACTION_NAME));
+ fileMenu.add(exitMenuItem);
+
+ menuBar.add(fileMenu);
+
+ viewMenu.setText("View");
+
+ showDevicesMenuItem.setAction(actionsMap.get(ShowDevicesAction.ACTION_NAME));
+ showDevicesMenuItem.setEnabled(false);
+ viewMenu.add(showDevicesMenuItem);
+
+ menuBar.add(viewMenu);
+
+ viewHierarchyMenu.setText("Hierarchy");
+
+ loadMenuItem.setAction(actionsMap.get(LoadGraphAction.ACTION_NAME));
+ viewHierarchyMenu.add(loadMenuItem);
+
+ menuBar.add(viewHierarchyMenu);
+
+ serverMenu.setText("Server");
+
+ startMenuItem.setAction(actionsMap.get(StartServerAction.ACTION_NAME));
+ serverMenu.add(startMenuItem);
+
+ stopMenuItem.setAction(actionsMap.get(StopServerAction.ACTION_NAME));
+ serverMenu.add(stopMenuItem);
+
+ menuBar.add(serverMenu);
+
+ return menuBar;
+ }
+
+ private JComponent buildPixelPerfectPanel() {
+ JSplitPane splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+
+ pixelPerfectTree = new JTree(new Object[0]);
+ pixelPerfectTree.setBorder(null);
+ pixelPerfectTree.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
+ pixelPerfectTree.addTreeSelectionListener(new TreeSelectionListener() {
+ public void valueChanged(TreeSelectionEvent event) {
+ ViewNode node = (ViewNode) event.getPath().getLastPathComponent();
+ screenViewer.select(node);
+ }
+ });
+
+ JScrollPane scroller = new JScrollPane(pixelPerfectTree);
+ scroller.setBorder(null);
+ scroller.getViewport().setBorder(null);
+
+ splitter.setContinuousLayout(true);
+ splitter.setLeftComponent(scroller);
+ splitter.setRightComponent(buildPixelPerfectViewer(splitter));
+ splitter.setBorder(null);
+
+ if (OS.isMacOsX() && OS.isLeopardOrLater()) {
+ splitter.setBorder(new UnifiedContentBorder());
+ }
+
+ return splitter;
+ }
+
+ private JComponent buildPixelPerfectViewer(JSplitPane splitter) {
+ screenViewer = new ScreenViewer(this, currentDevice, splitter.getDividerSize());
+ return screenViewer;
+ }
+
+ private void toggleGraphView() {
+ showStatusBarComponents();
+
+ screenViewer.stop();
+ mainPanel.remove(pixelPerfectPanel);
+ mainPanel.add(mainSplitter, BorderLayout.CENTER);
+
+ validate();
+ repaint();
+ }
+
+ private void showStatusBarComponents() {
+ viewCountLabel.setVisible(true);
+ zoomSlider.setVisible(true);
+ minZoomLabel.setVisible(true);
+ maxZoomLabel.setVisible(true);
+ filterLabel.setVisible(true);
+ filterText.setVisible(true);
+ }
+
+ private void togglePixelPerfectView() {
+ if (pixelPerfectPanel == null) {
+ pixelPerfectPanel = buildPixelPerfectPanel();
+ showPixelPerfectTree();
+ } else {
+ screenViewer.start();
+ }
+
+ hideStatusBarComponents();
+
+ mainPanel.remove(mainSplitter);
+ mainPanel.add(pixelPerfectPanel, BorderLayout.CENTER);
+
+ validate();
+ repaint();
+ }
+
+ private void zoomSliderStateChanged(ChangeEvent evt) {
+ JSlider slider = (JSlider) evt.getSource();
+ if (sceneView != null) {
+ scene.setZoomFactor(slider.getValue() / 100.0d);
+ sceneView.repaint();
+ }
+ }
+
+ private void showProperties(ViewNode node) {
+ propertiesTable.setModel(new PropertiesTableModel(node));
+ }
+
+ private void updateProfiles(double[] profiles) {
+ profilingTable.setModel(new ProfilesTableModel(profiles));
+ setVisibleRowCount(profilingTable, profiles.length + 1);
+ }
+
+ public static void setVisibleRowCount(JTable table, int rows) {
+ int height = 0;
+ for (int row = 0; row < rows; row++) {
+ height += table.getRowHeight(row);
+ }
+
+ Dimension size = new Dimension(table.getPreferredScrollableViewportSize().width, height);
+ table.setPreferredScrollableViewportSize(size);
+ table.revalidate();
+ }
+
+ private void showPixelPerfectTree() {
+ if (pixelPerfectTree == null) {
+ return;
+ }
+ pixelPerfectTree.setModel(new ViewsTreeModel(scene.getRoot()));
+ pixelPerfectTree.setCellRenderer(new ViewsTreeCellRenderer());
+ expandAll(pixelPerfectTree, true);
+
+ }
+
+ private static void expandAll(JTree tree, boolean expand) {
+ ViewNode root = (ViewNode) tree.getModel().getRoot();
+ expandAll(tree, new TreePath(root), expand);
+ }
+
+ private static void expandAll(JTree tree, TreePath parent, boolean expand) {
+ // Traverse children
+ ViewNode node = (ViewNode)parent.getLastPathComponent();
+ if (node.children != null) {
+ for (ViewNode n : node.children) {
+ TreePath path = parent.pathByAddingChild(n);
+ expandAll(tree, path, expand);
+ }
+ }
+
+ if (expand) {
+ tree.expandPath(parent);
+ } else {
+ tree.collapsePath(parent);
+ }
+ }
+
+ private void createGraph(ViewHierarchyScene scene) {
+ scene.addObjectSceneListener(new SceneFocusListener(),
+ ObjectSceneEventType.OBJECT_FOCUS_CHANGED);
+
+ if (mainSplitter == null) {
+ mainPanel.remove(deviceSelector);
+ mainPanel.add(buildGraphPanel(), BorderLayout.CENTER);
+ showDevicesButton.setEnabled(true);
+ showDevicesMenuItem.setEnabled(true);
+ graphViewButton.setEnabled(true);
+ pixelPerfectViewButton.setEnabled(true);
+
+ showStatusBarComponents();
+ }
+
+ sceneView = scene.createView();
+ sceneView.addMouseListener(new NodeClickListener());
+ sceneView.addMouseWheelListener(new WheelZoomListener());
+ sceneScroller.setViewportView(sceneView);
+
+ if (extrasPanel != null) {
+ sideSplitter.remove(extrasPanel);
+ }
+ sideSplitter.setBottomComponent(buildExtrasPanel());
+
+ mainSplitter.setDividerLocation(getWidth() - mainSplitter.getDividerSize() -
+ buttonsPanel.getPreferredSize().width);
+
+ captureLayersButton.setEnabled(true);
+ saveMenuItem.setEnabled(true);
+ showPixelPerfectTree();
+
+ updateStatus();
+ layoutScene();
+ }
+
+ private void layoutScene() {
+ TreeGraphLayout<ViewNode, String> layout =
+ new TreeGraphLayout<ViewNode, String>(scene, 50, 50, 70, 30, true);
+ layout.layout(scene.getRoot());
+ }
+
+ private void updateStatus() {
+ viewCountLabel.setText("" + scene.getNodes().size() + " views");
+ zoomSlider.setEnabled(scene.getNodes().size() > 0);
+ }
+
+ private JPanel buildExtrasPanel() {
+ extrasPanel = new JPanel(new BorderLayout());
+ JScrollPane p = new JScrollPane(layoutView = new LayoutRenderer(scene, sceneView));
+ JScrollBar b = p.getVerticalScrollBar();
+ b.setUnitIncrement(10);
+ extrasPanel.add(p);
+ extrasPanel.add(scene.createSatelliteView(), BorderLayout.SOUTH);
+ extrasPanel.add(buildLayoutViewControlButtons(), BorderLayout.NORTH);
+ return extrasPanel;
+ }
+
+ private JComponent buildLayoutViewControlButtons() {
+ buttonsPanel = new JToolBar();
+ buttonsPanel.setFloatable(false);
+
+ ButtonGroup group = new ButtonGroup();
+
+ JToggleButton white = new JToggleButton("On White");
+ toggleColorOnSelect(white);
+ white.putClientProperty("JButton.buttonType", "segmentedTextured");
+ white.putClientProperty("JButton.segmentPosition", "first");
+ white.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ layoutView.setBackground(Color.WHITE);
+ layoutView.setForeground(Color.BLACK);
+ }
+ });
+ group.add(white);
+ buttonsPanel.add(white);
+
+ JToggleButton black = new JToggleButton("On Black");
+ toggleColorOnSelect(black);
+ black.putClientProperty("JButton.buttonType", "segmentedTextured");
+ black.putClientProperty("JButton.segmentPosition", "last");
+ black.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ layoutView.setBackground(Color.BLACK);
+ layoutView.setForeground(Color.WHITE);
+ }
+ });
+ group.add(black);
+ buttonsPanel.add(black);
+
+ black.setSelected(true);
+
+ JCheckBox showExtras = new JCheckBox("Show Extras");
+ showExtras.putClientProperty("JComponent.sizeVariant", "small");
+ showExtras.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ layoutView.setShowExtras(((JCheckBox) e.getSource()).isSelected());
+ }
+ });
+ buttonsPanel.add(showExtras);
+
+ return buttonsPanel;
+ }
+
+ private void showCaptureWindow(ViewNode node, String captureParams, Image image) {
+ if (image != null) {
+ layoutView.repaint();
+
+ JFrame frame = new JFrame(captureParams);
+ JPanel panel = new JPanel(new BorderLayout());
+
+ final CaptureRenderer label = new CaptureRenderer(new ImageIcon(image), node);
+ label.setBorder(BorderFactory.createEmptyBorder(24, 24, 24, 24));
+
+ final JPanel solidColor = new JPanel(new BorderLayout());
+ solidColor.setBackground(Color.BLACK);
+ solidColor.add(label);
+
+ JToolBar toolBar = new JToolBar();
+ toolBar.setFloatable(false);
+
+ ButtonGroup group = new ButtonGroup();
+
+ JToggleButton white = new JToggleButton("On White");
+ toggleColorOnSelect(white);
+ white.putClientProperty("JButton.buttonType", "segmentedTextured");
+ white.putClientProperty("JButton.segmentPosition", "first");
+ white.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ solidColor.setBackground(Color.WHITE);
+ }
+ });
+ group.add(white);
+ toolBar.add(white);
+
+ JToggleButton black = new JToggleButton("On Black");
+ toggleColorOnSelect(black);
+ black.putClientProperty("JButton.buttonType", "segmentedTextured");
+ black.putClientProperty("JButton.segmentPosition", "last");
+ black.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ solidColor.setBackground(Color.BLACK);
+ }
+ });
+ group.add(black);
+ toolBar.add(black);
+
+ black.setSelected(true);
+
+ JCheckBox showExtras = new JCheckBox("Show Extras");
+ showExtras.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ label.setShowExtras(((JCheckBox) e.getSource()).isSelected());
+ }
+ });
+ toolBar.add(showExtras);
+
+ panel.add(toolBar, BorderLayout.NORTH);
+ panel.add(solidColor);
+ frame.add(panel);
+
+ frame.pack();
+ frame.setResizable(false);
+ frame.setLocationRelativeTo(Workspace.this);
+ frame.setVisible(true);
+ }
+ }
+
+ private void reset() {
+ currentDevice = null;
+ currentWindow = null;
+ currentDeviceChanged();
+ windowsTableModel.setVisible(false);
+ windowsTableModel.clear();
+
+ showDevicesSelector();
+ }
+
+ public void showDevicesSelector() {
+ if (mainSplitter != null) {
+ if (pixelPerfectPanel != null) {
+ screenViewer.start();
+ }
+ mainPanel.remove(graphViewButton.isSelected() ? mainSplitter : pixelPerfectPanel);
+ mainPanel.add(deviceSelector, BorderLayout.CENTER);
+ pixelPerfectPanel = mainSplitter = null;
+ graphViewButton.setSelected(true);
+
+ hideStatusBarComponents();
+
+ saveMenuItem.setEnabled(false);
+ showDevicesMenuItem.setEnabled(false);
+ showDevicesButton.setEnabled(false);
+ displayNodeButton.setEnabled(false);
+ captureLayersButton.setEnabled(false);
+ invalidateButton.setEnabled(false);
+ dumpDisplayListButton.setEnabled(false);
+ requestLayoutButton.setEnabled(false);
+ graphViewButton.setEnabled(false);
+ pixelPerfectViewButton.setEnabled(false);
+
+ if (currentDevice != null) {
+ if (!DeviceBridge.isViewServerRunning(currentDevice)) {
+ DeviceBridge.startViewServer(currentDevice);
+ }
+ loadWindows().execute();
+ windowsTableModel.setVisible(true);
+ }
+
+ validate();
+ repaint();
+ }
+ }
+
+ private void currentDeviceChanged() {
+ if (currentDevice == null) {
+ startButton.setEnabled(false);
+ startMenuItem.setEnabled(false);
+ stopButton.setEnabled(false);
+ stopMenuItem.setEnabled(false);
+ refreshButton.setEnabled(false);
+ saveMenuItem.setEnabled(false);
+ loadButton.setEnabled(false);
+ displayNodeButton.setEnabled(false);
+ captureLayersButton.setEnabled(false);
+ invalidateButton.setEnabled(false);
+ dumpDisplayListButton.setEnabled(false);
+ graphViewButton.setEnabled(false);
+ pixelPerfectViewButton.setEnabled(false);
+ requestLayoutButton.setEnabled(false);
+ loadMenuItem.setEnabled(false);
+ } else {
+ loadMenuItem.setEnabled(true);
+ checkForServerOnCurrentDevice();
+ }
+ }
+
+ private void checkForServerOnCurrentDevice() {
+ if (DeviceBridge.isViewServerRunning(currentDevice)) {
+ startButton.setEnabled(false);
+ startMenuItem.setEnabled(false);
+ stopButton.setEnabled(true);
+ stopMenuItem.setEnabled(true);
+ loadButton.setEnabled(true);
+ refreshButton.setEnabled(true);
+ } else {
+ startButton.setEnabled(true);
+ startMenuItem.setEnabled(true);
+ stopButton.setEnabled(false);
+ stopMenuItem.setEnabled(false);
+ loadButton.setEnabled(false);
+ refreshButton.setEnabled(false);
+ }
+ }
+
+ public void cleanupDevices() {
+ for (IDevice device : devicesTableModel.getDevices()) {
+ DeviceBridge.removeDeviceForward(device);
+ }
+ }
+
+ private static void toggleColorOnSelect(JToggleButton button) {
+ if (!OS.isMacOsX() || !OS.isLeopardOrLater()) {
+ return;
+ }
+
+ button.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent event) {
+ JToggleButton button = (JToggleButton) event.getSource();
+ if (button.isSelected()) {
+ button.setForeground(Color.WHITE);
+ } else {
+ button.setForeground(Color.BLACK);
+ }
+ }
+ });
+ }
+
+ private void updateFilter(DocumentEvent e) {
+ final Document document = e.getDocument();
+ try {
+ updateFilteredNodes(document.getText(0, document.getLength()));
+ } catch (BadLocationException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ private void updateFilteredNodes(String filterText) {
+ final ViewNode root = scene.getRoot();
+ try {
+ final Pattern pattern = Pattern.compile(filterText, Pattern.CASE_INSENSITIVE);
+ filterNodes(pattern, root);
+ } catch (PatternSyntaxException e) {
+ filterNodes(null, root);
+ }
+ repaint();
+ }
+
+ private void filterNodes(Pattern pattern, ViewNode root) {
+ root.filter(pattern);
+
+ for (ViewNode node : root.children) {
+ filterNodes(pattern, node);
+ }
+ }
+
+ public void beginTask() {
+ progress.setVisible(true);
+ }
+
+ public void endTask() {
+ progress.setVisible(false);
+ }
+
+ public SwingWorker<?, ?> showNodeCapture() {
+ if (scene.getFocusedObject() == null) {
+ return null;
+ }
+ return new CaptureNodeTask();
+ }
+
+ public SwingWorker<?, ?> outputDisplayList() {
+ if (scene.getFocusedObject() == null) {
+ return null;
+ }
+ return new DumpDisplayListTask();
+ }
+
+ public SwingWorker<?, ?> captureLayers() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setFileFilter(new PsdFileFilter());
+ int choice = chooser.showSaveDialog(sceneView);
+ if (choice == JFileChooser.APPROVE_OPTION) {
+ return new CaptureLayersTask(chooser.getSelectedFile());
+ } else {
+ return null;
+ }
+ }
+
+ public SwingWorker<?, ?> startServer() {
+ return new StartServerTask();
+ }
+
+ public SwingWorker<?, ?> stopServer() {
+ return new StopServerTask();
+ }
+
+ public SwingWorker<?, ?> loadWindows() {
+ return new LoadWindowsTask();
+ }
+
+ public SwingWorker<?, ?> loadGraph() {
+ return new LoadGraphTask();
+ }
+
+ public SwingWorker<?, ?> invalidateView() {
+ if (scene.getFocusedObject() == null) {
+ return null;
+ }
+ return new InvalidateTask();
+ }
+
+ public SwingWorker<?, ?> requestLayout() {
+ if (scene.getFocusedObject() == null) {
+ return null;
+ }
+ return new RequestLayoutTask();
+ }
+
+ public SwingWorker<?, ?> saveSceneAsImage() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setFileFilter(new PngFileFilter());
+ int choice = chooser.showSaveDialog(sceneView);
+ if (choice == JFileChooser.APPROVE_OPTION) {
+ return new SaveSceneTask(chooser.getSelectedFile());
+ } else {
+ return null;
+ }
+ }
+
+ private class InvalidateTask extends SwingWorker<Object, Void> {
+ private String captureParams;
+
+ private InvalidateTask() {
+ captureParams = scene.getFocusedObject().toString();
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Object doInBackground() throws Exception {
+ ViewManager.invalidate(currentDevice, currentWindow, captureParams);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ endTask();
+ }
+ }
+
+ private class DumpDisplayListTask extends SwingWorker<Object, Void> {
+ private String captureParams;
+
+ private DumpDisplayListTask() {
+ captureParams = scene.getFocusedObject().toString();
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Object doInBackground() throws Exception {
+ ViewManager.outputDisplayList(currentDevice, currentWindow, captureParams);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ endTask();
+ }
+ }
+
+ private class RequestLayoutTask extends SwingWorker<Object, Void> {
+ private String captureParams;
+
+ private RequestLayoutTask() {
+ captureParams = scene.getFocusedObject().toString();
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Object doInBackground() throws Exception {
+ ViewManager.requestLayout(currentDevice, currentWindow, captureParams);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ endTask();
+ }
+ }
+
+ private class CaptureLayersTask extends SwingWorker<Boolean, Void> {
+ private File file;
+
+ private CaptureLayersTask(File file) {
+ this.file = file;
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Boolean doInBackground() throws Exception {
+ return CaptureLoader.saveLayers(currentDevice, currentWindow, file);
+ }
+
+ @Override
+ protected void done() {
+ endTask();
+ }
+ }
+
+ private class CaptureNodeTask extends SwingWorker<Image, Void> {
+ private String captureParams;
+ private ViewNode node;
+
+ private CaptureNodeTask() {
+ node = (ViewNode) scene.getFocusedObject();
+ captureParams = node.toString();
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Image doInBackground() throws Exception {
+ node.image = CaptureLoader.loadCapture(currentDevice, currentWindow, captureParams);
+ return node.image;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ Image image = get();
+ showCaptureWindow(node, captureParams, image);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } finally {
+ endTask();
+ }
+ }
+ }
+
+ static class WindowsResult {
+ Window[] windows;
+ int serverVersion;
+ int protocolVersion;
+ }
+
+ private class LoadWindowsTask extends SwingWorker<WindowsResult, Void> {
+ private LoadWindowsTask() {
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected WindowsResult doInBackground() throws Exception {
+ WindowsResult r = new WindowsResult();
+ r.protocolVersion = VersionLoader.loadProtocolVersion(currentDevice);
+ r.serverVersion = VersionLoader.loadServerVersion(currentDevice);
+ r.windows = WindowsLoader.loadWindows(currentDevice,
+ r.protocolVersion, r.serverVersion);
+ return r;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ WindowsResult result = get();
+ protocolVersion = result.protocolVersion;
+ serverVersion = result.serverVersion;
+ setupProtocolDependentToolbar();
+ windowsTableModel.clear();
+ windowsTableModel.addWindows(result.windows);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ endTask();
+ }
+ }
+ }
+
+ private class StartServerTask extends SwingWorker<Object, Void> {
+ public StartServerTask() {
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Object doInBackground() {
+ DeviceBridge.startViewServer(currentDevice);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ new LoadWindowsTask().execute();
+ windowsTableModel.setVisible(true);
+ checkForServerOnCurrentDevice();
+ endTask();
+ }
+ }
+
+ private class StopServerTask extends SwingWorker<Object, Void> {
+ public StopServerTask() {
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Object doInBackground() {
+ DeviceBridge.stopViewServer(currentDevice);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ windowsTableModel.setVisible(false);
+ windowsTableModel.clear();
+ checkForServerOnCurrentDevice();
+ endTask();
+ }
+ }
+
+ private class LoadGraphTask extends SwingWorker<double[], Void> {
+ public LoadGraphTask() {
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected double[] doInBackground() {
+ scene = ViewHierarchyLoader.loadScene(currentDevice, currentWindow);
+ return ProfilesLoader.loadProfiles(currentDevice, currentWindow,
+ scene.getRoot().toString());
+ }
+
+ @Override
+ protected void done() {
+ try {
+ createGraph(scene);
+ updateProfiles(get());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } finally {
+ endTask();
+ }
+ }
+ }
+
+ private class SaveSceneTask extends SwingWorker<Object, Void> {
+ private File file;
+
+ private SaveSceneTask(File file) {
+ this.file = file;
+ beginTask();
+ }
+
+ @Override
+ @WorkerThread
+ protected Object doInBackground() {
+ if (sceneView == null) {
+ return null;
+ }
+
+ try {
+ BufferedImage image = new BufferedImage(sceneView.getWidth(),
+ sceneView.getHeight(), BufferedImage.TYPE_INT_RGB);
+ Graphics2D g2 = image.createGraphics();
+ sceneView.paint(g2);
+ g2.dispose();
+ ImageIO.write(image, "PNG", file);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ endTask();
+ }
+ }
+
+ private class SceneFocusListener implements ObjectSceneListener {
+
+ public void objectAdded(ObjectSceneEvent arg0, Object arg1) {
+ }
+
+ public void objectRemoved(ObjectSceneEvent arg0, Object arg1) {
+ }
+
+ public void objectStateChanged(ObjectSceneEvent arg0, Object arg1,
+ ObjectState arg2, ObjectState arg3) {
+ }
+
+ public void selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection,
+ Set<Object> newSelection) {
+ }
+
+ public void highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2) {
+ }
+
+ public void hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) {
+ }
+
+ public void focusChanged(ObjectSceneEvent e, Object oldFocus, Object newFocus) {
+ displayNodeButton.setEnabled(true);
+ invalidateButton.setEnabled(true);
+ dumpDisplayListButton.setEnabled(true);
+ requestLayoutButton.setEnabled(true);
+
+ Set<Object> selection = new HashSet<Object>();
+ selection.add(newFocus);
+ scene.setSelectedObjects(selection);
+
+ showProperties((ViewNode) newFocus);
+ layoutView.repaint();
+ }
+ }
+
+ private class NodeClickListener extends MouseAdapter {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ showNodeCapture().execute();
+ }
+ }
+ }
+
+ private class WheelZoomListener implements MouseWheelListener {
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ if (zoomSlider != null) {
+ int val = zoomSlider.getValue();
+ val -= e.getWheelRotation() * 10;
+ zoomSlider.setValue(val);
+ }
+ }
+ }
+ private class DevicesTableModel extends DefaultTableModel implements
+ AndroidDebugBridge.IDeviceChangeListener {
+
+ private ArrayList<IDevice> devices;
+
+ private DevicesTableModel() {
+ devices = new ArrayList<IDevice>();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ return false;
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+ return devices.get(row);
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return "Devices";
+ }
+
+ @WorkerThread
+ public void deviceConnected(final IDevice device) {
+ DeviceBridge.setupDeviceForward(device);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ addDevice(device);
+ }
+ });
+ }
+
+ @WorkerThread
+ public void deviceDisconnected(final IDevice device) {
+ DeviceBridge.removeDeviceForward(device);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ removeDevice(device);
+ }
+ });
+ }
+
+ public void addDevice(IDevice device) {
+ if (!devices.contains(device)) {
+ devices.add(device);
+ fireTableDataChanged();
+ }
+ }
+
+ public void removeDevice(IDevice device) {
+ if (device.equals(currentDevice)) {
+ reset();
+ }
+
+ if (devices.contains(device)) {
+ devices.remove(device);
+ fireTableDataChanged();
+ }
+ }
+
+ @WorkerThread
+ public void deviceChanged(IDevice device, int changeMask) {
+ if ((changeMask & IDevice.CHANGE_STATE) != 0 &&
+ device.isOnline()) {
+ // if the device state changed and it's now online, we set up its port forwarding.
+ DeviceBridge.setupDeviceForward(device);
+ } else if (device == currentDevice && (changeMask & IDevice.CHANGE_CLIENT_LIST) != 0) {
+ // if the changed device is the current one and the client list changed, we update
+ // the UI.
+ loadWindows().execute();
+ windowsTableModel.setVisible(true);
+ }
+ }
+
+ @Override
+ public int getRowCount() {
+ return devices == null ? 0 : devices.size();
+ }
+
+ public IDevice getDevice(int index) {
+ return index < devices.size() ? devices.get(index) : null;
+ }
+
+ public IDevice[] getDevices() {
+ return devices.toArray(new IDevice[devices.size()]);
+ }
+ }
+
+ private static class WindowsTableModel extends DefaultTableModel {
+ private ArrayList<Window> windows;
+ private boolean visible;
+
+ private WindowsTableModel() {
+ windows = new ArrayList<Window>();
+ windows.add(Window.FOCUSED_WINDOW);
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ return false;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return "Windows";
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+ return windows.get(row);
+ }
+
+ @Override
+ public int getRowCount() {
+ return !visible || windows == null ? 0 : windows.size();
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ fireTableDataChanged();
+ }
+
+ public void addWindow(Window window) {
+ windows.add(window);
+ fireTableDataChanged();
+ }
+
+ public void addWindows(Window[] windowsList) {
+ //noinspection ManualArrayToCollectionCopy
+ for (Window window : windowsList) {
+ windows.add(window);
+ }
+ fireTableDataChanged();
+ }
+
+ public void clear() {
+ windows.clear();
+ windows.add(Window.FOCUSED_WINDOW);
+ }
+
+ public Window getWindow(int index) {
+ return windows.get(index);
+ }
+ }
+
+ private class DeviceSelectedListener implements ListSelectionListener {
+ public void valueChanged(ListSelectionEvent event) {
+ if (event.getValueIsAdjusting()) {
+ return;
+ }
+
+ int row = devices.getSelectedRow();
+ if (row >= 0) {
+ currentDevice = devicesTableModel.getDevice(row);
+ currentDeviceChanged();
+ if (currentDevice != null) {
+ if (!DeviceBridge.isViewServerRunning(currentDevice)) {
+ DeviceBridge.startViewServer(currentDevice);
+ checkForServerOnCurrentDevice();
+ }
+ loadWindows().execute();
+ windowsTableModel.setVisible(true);
+ }
+ } else {
+ currentDevice = null;
+ currentDeviceChanged();
+ windowsTableModel.setVisible(false);
+ windowsTableModel.clear();
+ }
+ }
+ }
+
+ private class WindowSelectedListener implements ListSelectionListener {
+ public void valueChanged(ListSelectionEvent event) {
+ if (event.getValueIsAdjusting()) {
+ return;
+ }
+
+ int row = windows.getSelectedRow();
+ if (row >= 0) {
+ currentWindow = windowsTableModel.getWindow(row);
+ } else {
+ currentWindow = Window.FOCUSED_WINDOW;
+ }
+ }
+ }
+
+ private static class ViewsTreeCellRenderer extends DefaultTreeCellRenderer {
+ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
+ boolean expanded, boolean leaf, int row, boolean hasFocus) {
+
+ final String name = ((ViewNode) value).name;
+ value = name.substring(name.lastIndexOf('.') + 1, name.lastIndexOf('@'));
+ return super.getTreeCellRendererComponent(tree, value, selected, expanded,
+ leaf, row, hasFocus);
+ }
+ }
+}