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