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