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 Workspace()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 buildActions()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 buildMainPanel()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 buildGraphPanel()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 buildDeviceSelector()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 buildSideSplitter()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 buildStatusPanel()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 hideStatusBarComponents()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 buildToolBar()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 setupProtocolDependentToolbar()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 buildMenuBar()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 buildPixelPerfectPanel()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 buildPixelPerfectViewer(JSplitPane splitter)613 private JComponent buildPixelPerfectViewer(JSplitPane splitter) { 614 screenViewer = new ScreenViewer(this, currentDevice, splitter.getDividerSize()); 615 return screenViewer; 616 } 617 toggleGraphView()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 showStatusBarComponents()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 togglePixelPerfectView()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 zoomSliderStateChanged(ChangeEvent evt)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 showProperties(ViewNode node)663 private void showProperties(ViewNode node) { 664 propertiesTable.setModel(new PropertiesTableModel(node)); 665 } 666 updateProfiles(double[] profiles)667 private void updateProfiles(double[] profiles) { 668 profilingTable.setModel(new ProfilesTableModel(profiles)); 669 setVisibleRowCount(profilingTable, profiles.length + 1); 670 } 671 setVisibleRowCount(JTable table, int rows)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 showPixelPerfectTree()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 expandAll(JTree tree, boolean expand)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 expandAll(JTree tree, TreePath parent, boolean expand)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 createGraph(ViewHierarchyScene scene)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 layoutScene()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 updateStatus()757 private void updateStatus() { 758 viewCountLabel.setText("" + scene.getNodes().size() + " views"); 759 zoomSlider.setEnabled(scene.getNodes().size() > 0); 760 } 761 buildExtrasPanel()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 buildLayoutViewControlButtons()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 showCaptureWindow(ViewNode node, String captureParams, Image image)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 reset()883 private void reset() { 884 currentDevice = null; 885 currentWindow = null; 886 currentDeviceChanged(); 887 windowsTableModel.setVisible(false); 888 windowsTableModel.clear(); 889 890 showDevicesSelector(); 891 } 892 showDevicesSelector()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 currentDeviceChanged()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 checkForServerOnCurrentDevice()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 cleanupDevices()970 public void cleanupDevices() { 971 for (IDevice device : devicesTableModel.getDevices()) { 972 DeviceBridge.removeDeviceForward(device); 973 } 974 } 975 toggleColorOnSelect(JToggleButton button)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 updateFilter(DocumentEvent e)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 updateFilteredNodes(String filterText)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 filterNodes(Pattern pattern, ViewNode root)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 beginTask()1021 public void beginTask() { 1022 progress.setVisible(true); 1023 } 1024 endTask()1025 public void endTask() { 1026 progress.setVisible(false); 1027 } 1028 showNodeCapture()1029 public SwingWorker<?, ?> showNodeCapture() { 1030 if (scene.getFocusedObject() == null) { 1031 return null; 1032 } 1033 return new CaptureNodeTask(); 1034 } 1035 outputDisplayList()1036 public SwingWorker<?, ?> outputDisplayList() { 1037 if (scene.getFocusedObject() == null) { 1038 return null; 1039 } 1040 return new DumpDisplayListTask(); 1041 } 1042 captureLayers()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 startServer()1054 public SwingWorker<?, ?> startServer() { 1055 return new StartServerTask(); 1056 } 1057 stopServer()1058 public SwingWorker<?, ?> stopServer() { 1059 return new StopServerTask(); 1060 } 1061 loadWindows()1062 public SwingWorker<?, ?> loadWindows() { 1063 return new LoadWindowsTask(); 1064 } 1065 loadGraph()1066 public SwingWorker<?, ?> loadGraph() { 1067 return new LoadGraphTask(); 1068 } 1069 invalidateView()1070 public SwingWorker<?, ?> invalidateView() { 1071 if (scene.getFocusedObject() == null) { 1072 return null; 1073 } 1074 return new InvalidateTask(); 1075 } 1076 requestLayout()1077 public SwingWorker<?, ?> requestLayout() { 1078 if (scene.getFocusedObject() == null) { 1079 return null; 1080 } 1081 return new RequestLayoutTask(); 1082 } 1083 saveSceneAsImage()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 InvalidateTask()1098 private InvalidateTask() { 1099 captureParams = scene.getFocusedObject().toString(); 1100 beginTask(); 1101 } 1102 1103 @Override 1104 @WorkerThread doInBackground()1105 protected Object doInBackground() throws Exception { 1106 ViewManager.invalidate(currentDevice, currentWindow, captureParams); 1107 return null; 1108 } 1109 1110 @Override done()1111 protected void done() { 1112 endTask(); 1113 } 1114 } 1115 1116 private class DumpDisplayListTask extends SwingWorker<Object, Void> { 1117 private String captureParams; 1118 DumpDisplayListTask()1119 private DumpDisplayListTask() { 1120 captureParams = scene.getFocusedObject().toString(); 1121 beginTask(); 1122 } 1123 1124 @Override 1125 @WorkerThread doInBackground()1126 protected Object doInBackground() throws Exception { 1127 ViewManager.outputDisplayList(currentDevice, currentWindow, captureParams); 1128 return null; 1129 } 1130 1131 @Override done()1132 protected void done() { 1133 endTask(); 1134 } 1135 } 1136 1137 private class RequestLayoutTask extends SwingWorker<Object, Void> { 1138 private String captureParams; 1139 RequestLayoutTask()1140 private RequestLayoutTask() { 1141 captureParams = scene.getFocusedObject().toString(); 1142 beginTask(); 1143 } 1144 1145 @Override 1146 @WorkerThread doInBackground()1147 protected Object doInBackground() throws Exception { 1148 ViewManager.requestLayout(currentDevice, currentWindow, captureParams); 1149 return null; 1150 } 1151 1152 @Override done()1153 protected void done() { 1154 endTask(); 1155 } 1156 } 1157 1158 private class CaptureLayersTask extends SwingWorker<Boolean, Void> { 1159 private File file; 1160 CaptureLayersTask(File file)1161 private CaptureLayersTask(File file) { 1162 this.file = file; 1163 beginTask(); 1164 } 1165 1166 @Override 1167 @WorkerThread doInBackground()1168 protected Boolean doInBackground() throws Exception { 1169 return CaptureLoader.saveLayers(currentDevice, currentWindow, file); 1170 } 1171 1172 @Override done()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 CaptureNodeTask()1182 private CaptureNodeTask() { 1183 node = (ViewNode) scene.getFocusedObject(); 1184 captureParams = node.toString(); 1185 beginTask(); 1186 } 1187 1188 @Override 1189 @WorkerThread doInBackground()1190 protected Image doInBackground() throws Exception { 1191 node.image = CaptureLoader.loadCapture(currentDevice, currentWindow, captureParams); 1192 return node.image; 1193 } 1194 1195 @Override done()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> { LoadWindowsTask()1217 private LoadWindowsTask() { 1218 beginTask(); 1219 } 1220 1221 @Override 1222 @WorkerThread doInBackground()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 done()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> { StartServerTask()1252 public StartServerTask() { 1253 beginTask(); 1254 } 1255 1256 @Override 1257 @WorkerThread doInBackground()1258 protected Object doInBackground() { 1259 DeviceBridge.startViewServer(currentDevice); 1260 return null; 1261 } 1262 1263 @Override done()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> { StopServerTask()1273 public StopServerTask() { 1274 beginTask(); 1275 } 1276 1277 @Override 1278 @WorkerThread doInBackground()1279 protected Object doInBackground() { 1280 DeviceBridge.stopViewServer(currentDevice); 1281 return null; 1282 } 1283 1284 @Override done()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> { LoadGraphTask()1294 public LoadGraphTask() { 1295 beginTask(); 1296 } 1297 1298 @Override 1299 @WorkerThread doInBackground()1300 protected double[] doInBackground() { 1301 scene = ViewHierarchyLoader.loadScene(currentDevice, currentWindow); 1302 return ProfilesLoader.loadProfiles(currentDevice, currentWindow, 1303 scene.getRoot().toString()); 1304 } 1305 1306 @Override done()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 SaveSceneTask(File file)1324 private SaveSceneTask(File file) { 1325 this.file = file; 1326 beginTask(); 1327 } 1328 1329 @Override 1330 @WorkerThread doInBackground()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 done()1350 protected void done() { 1351 endTask(); 1352 } 1353 } 1354 1355 private class SceneFocusListener implements ObjectSceneListener { 1356 objectAdded(ObjectSceneEvent arg0, Object arg1)1357 public void objectAdded(ObjectSceneEvent arg0, Object arg1) { 1358 } 1359 objectRemoved(ObjectSceneEvent arg0, Object arg1)1360 public void objectRemoved(ObjectSceneEvent arg0, Object arg1) { 1361 } 1362 objectStateChanged(ObjectSceneEvent arg0, Object arg1, ObjectState arg2, ObjectState arg3)1363 public void objectStateChanged(ObjectSceneEvent arg0, Object arg1, 1364 ObjectState arg2, ObjectState arg3) { 1365 } 1366 selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection, Set<Object> newSelection)1367 public void selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection, 1368 Set<Object> newSelection) { 1369 } 1370 highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2)1371 public void highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2) { 1372 } 1373 hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2)1374 public void hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) { 1375 } 1376 focusChanged(ObjectSceneEvent e, Object oldFocus, Object newFocus)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 mouseClicked(MouseEvent e)1394 public void mouseClicked(MouseEvent e) { 1395 if (e.getClickCount() == 2) { 1396 showNodeCapture().execute(); 1397 } 1398 } 1399 } 1400 1401 private class WheelZoomListener implements MouseWheelListener { mouseWheelMoved(MouseWheelEvent e)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 DevicesTableModel()1415 private DevicesTableModel() { 1416 devices = new ArrayList<IDevice>(); 1417 } 1418 1419 @Override getColumnCount()1420 public int getColumnCount() { 1421 return 1; 1422 } 1423 1424 @Override isCellEditable(int row, int column)1425 public boolean isCellEditable(int row, int column) { 1426 return false; 1427 } 1428 1429 @Override getValueAt(int row, int column)1430 public Object getValueAt(int row, int column) { 1431 return devices.get(row); 1432 } 1433 1434 @Override getColumnName(int column)1435 public String getColumnName(int column) { 1436 return "Devices"; 1437 } 1438 1439 @WorkerThread deviceConnected(final IDevice device)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 deviceDisconnected(final IDevice device)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 addDevice(IDevice device)1461 public void addDevice(IDevice device) { 1462 if (!devices.contains(device)) { 1463 devices.add(device); 1464 fireTableDataChanged(); 1465 } 1466 } 1467 removeDevice(IDevice device)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 deviceChanged(IDevice device, int changeMask)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 getRowCount()1494 public int getRowCount() { 1495 return devices == null ? 0 : devices.size(); 1496 } 1497 getDevice(int index)1498 public IDevice getDevice(int index) { 1499 return index < devices.size() ? devices.get(index) : null; 1500 } 1501 getDevices()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 WindowsTableModel()1511 private WindowsTableModel() { 1512 windows = new ArrayList<Window>(); 1513 windows.add(Window.FOCUSED_WINDOW); 1514 } 1515 1516 @Override getColumnCount()1517 public int getColumnCount() { 1518 return 1; 1519 } 1520 1521 @Override isCellEditable(int row, int column)1522 public boolean isCellEditable(int row, int column) { 1523 return false; 1524 } 1525 1526 @Override getColumnName(int column)1527 public String getColumnName(int column) { 1528 return "Windows"; 1529 } 1530 1531 @Override getValueAt(int row, int column)1532 public Object getValueAt(int row, int column) { 1533 return windows.get(row); 1534 } 1535 1536 @Override getRowCount()1537 public int getRowCount() { 1538 return !visible || windows == null ? 0 : windows.size(); 1539 } 1540 setVisible(boolean visible)1541 public void setVisible(boolean visible) { 1542 this.visible = visible; 1543 fireTableDataChanged(); 1544 } 1545 addWindow(Window window)1546 public void addWindow(Window window) { 1547 windows.add(window); 1548 fireTableDataChanged(); 1549 } 1550 addWindows(Window[] windowsList)1551 public void addWindows(Window[] windowsList) { 1552 //noinspection ManualArrayToCollectionCopy 1553 for (Window window : windowsList) { 1554 windows.add(window); 1555 } 1556 fireTableDataChanged(); 1557 } 1558 clear()1559 public void clear() { 1560 windows.clear(); 1561 windows.add(Window.FOCUSED_WINDOW); 1562 } 1563 getWindow(int index)1564 public Window getWindow(int index) { 1565 return windows.get(index); 1566 } 1567 } 1568 1569 private class DeviceSelectedListener implements ListSelectionListener { valueChanged(ListSelectionEvent event)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 { valueChanged(ListSelectionEvent event)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 { getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)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