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