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