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 Workspace()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 buildActions()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 buildMainPanel()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 buildGraphPanel()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 buildDeviceSelector()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 buildSideSplitter()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 buildStatusPanel()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 hideStatusBarComponents()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 buildToolBar()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 buildMenuBar()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 buildPixelPerfectPanel()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 buildPixelPerfectViewer(JSplitPane splitter)590 private JComponent buildPixelPerfectViewer(JSplitPane splitter) { 591 screenViewer = new ScreenViewer(this, currentDevice, splitter.getDividerSize()); 592 return screenViewer; 593 } 594 toggleGraphView()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 showStatusBarComponents()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 togglePixelPerfectView()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 zoomSliderStateChanged(ChangeEvent evt)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 showProperties(ViewNode node)640 private void showProperties(ViewNode node) { 641 propertiesTable.setModel(new PropertiesTableModel(node)); 642 } 643 updateProfiles(double[] profiles)644 private void updateProfiles(double[] profiles) { 645 profilingTable.setModel(new ProfilesTableModel(profiles)); 646 setVisibleRowCount(profilingTable, profiles.length + 1); 647 } 648 setVisibleRowCount(JTable table, int rows)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 showPixelPerfectTree()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 expandAll(JTree tree, boolean expand)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 expandAll(JTree tree, TreePath parent, boolean expand)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 createGraph(ViewHierarchyScene scene)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 layoutScene()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 updateStatus()734 private void updateStatus() { 735 viewCountLabel.setText("" + scene.getNodes().size() + " views"); 736 zoomSlider.setEnabled(scene.getNodes().size() > 0); 737 } 738 buildExtrasPanel()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 buildLayoutViewControlButtons()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 showCaptureWindow(ViewNode node, String captureParams, Image image)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 reset()860 private void reset() { 861 currentDevice = null; 862 currentWindow = null; 863 currentDeviceChanged(); 864 windowsTableModel.setVisible(false); 865 windowsTableModel.clear(); 866 867 showDevicesSelector(); 868 } 869 showDevicesSelector()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 currentDeviceChanged()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 checkForServerOnCurrentDevice()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 cleanupDevices()945 public void cleanupDevices() { 946 for (IDevice device : devicesTableModel.getDevices()) { 947 DeviceBridge.removeDeviceForward(device); 948 } 949 } 950 toggleColorOnSelect(JToggleButton button)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 updateFilter(DocumentEvent e)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 updateFilteredNodes(String filterText)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 filterNodes(Pattern pattern, ViewNode root)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 beginTask()996 public void beginTask() { 997 progress.setVisible(true); 998 } 999 endTask()1000 public void endTask() { 1001 progress.setVisible(false); 1002 } 1003 showNodeCapture()1004 public SwingWorker<?, ?> showNodeCapture() { 1005 if (scene.getFocusedObject() == null) { 1006 return null; 1007 } 1008 return new CaptureNodeTask(); 1009 } 1010 captureLayers()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 startServer()1022 public SwingWorker<?, ?> startServer() { 1023 return new StartServerTask(); 1024 } 1025 stopServer()1026 public SwingWorker<?, ?> stopServer() { 1027 return new StopServerTask(); 1028 } 1029 loadWindows()1030 public SwingWorker<?, ?> loadWindows() { 1031 return new LoadWindowsTask(); 1032 } 1033 loadGraph()1034 public SwingWorker<?, ?> loadGraph() { 1035 return new LoadGraphTask(); 1036 } 1037 invalidateView()1038 public SwingWorker<?, ?> invalidateView() { 1039 if (scene.getFocusedObject() == null) { 1040 return null; 1041 } 1042 return new InvalidateTask(); 1043 } 1044 requestLayout()1045 public SwingWorker<?, ?> requestLayout() { 1046 if (scene.getFocusedObject() == null) { 1047 return null; 1048 } 1049 return new RequestLayoutTask(); 1050 } 1051 saveSceneAsImage()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 InvalidateTask()1066 private InvalidateTask() { 1067 captureParams = scene.getFocusedObject().toString(); 1068 beginTask(); 1069 } 1070 1071 @Override 1072 @WorkerThread doInBackground()1073 protected Object doInBackground() throws Exception { 1074 ViewManager.invalidate(currentDevice, currentWindow, captureParams); 1075 return null; 1076 } 1077 1078 @Override done()1079 protected void done() { 1080 endTask(); 1081 } 1082 } 1083 1084 private class RequestLayoutTask extends SwingWorker<Object, Void> { 1085 private String captureParams; 1086 RequestLayoutTask()1087 private RequestLayoutTask() { 1088 captureParams = scene.getFocusedObject().toString(); 1089 beginTask(); 1090 } 1091 1092 @Override 1093 @WorkerThread doInBackground()1094 protected Object doInBackground() throws Exception { 1095 ViewManager.requestLayout(currentDevice, currentWindow, captureParams); 1096 return null; 1097 } 1098 1099 @Override done()1100 protected void done() { 1101 endTask(); 1102 } 1103 } 1104 1105 private class CaptureLayersTask extends SwingWorker<Boolean, Void> { 1106 private File file; 1107 CaptureLayersTask(File file)1108 private CaptureLayersTask(File file) { 1109 this.file = file; 1110 beginTask(); 1111 } 1112 1113 @Override 1114 @WorkerThread doInBackground()1115 protected Boolean doInBackground() throws Exception { 1116 return CaptureLoader.saveLayers(currentDevice, currentWindow, file); 1117 } 1118 1119 @Override done()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 CaptureNodeTask()1129 private CaptureNodeTask() { 1130 node = (ViewNode) scene.getFocusedObject(); 1131 captureParams = node.toString(); 1132 beginTask(); 1133 } 1134 1135 @Override 1136 @WorkerThread doInBackground()1137 protected Image doInBackground() throws Exception { 1138 node.image = CaptureLoader.loadCapture(currentDevice, currentWindow, captureParams); 1139 return node.image; 1140 } 1141 1142 @Override done()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> { LoadWindowsTask()1164 private LoadWindowsTask() { 1165 beginTask(); 1166 } 1167 1168 @Override 1169 @WorkerThread doInBackground()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 done()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> { StartServerTask()1199 public StartServerTask() { 1200 beginTask(); 1201 } 1202 1203 @Override 1204 @WorkerThread doInBackground()1205 protected Object doInBackground() { 1206 DeviceBridge.startViewServer(currentDevice); 1207 return null; 1208 } 1209 1210 @Override done()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> { StopServerTask()1220 public StopServerTask() { 1221 beginTask(); 1222 } 1223 1224 @Override 1225 @WorkerThread doInBackground()1226 protected Object doInBackground() { 1227 DeviceBridge.stopViewServer(currentDevice); 1228 return null; 1229 } 1230 1231 @Override done()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> { LoadGraphTask()1241 public LoadGraphTask() { 1242 beginTask(); 1243 } 1244 1245 @Override 1246 @WorkerThread doInBackground()1247 protected double[] doInBackground() { 1248 scene = ViewHierarchyLoader.loadScene(currentDevice, currentWindow); 1249 return ProfilesLoader.loadProfiles(currentDevice, currentWindow, 1250 scene.getRoot().toString()); 1251 } 1252 1253 @Override done()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 SaveSceneTask(File file)1271 private SaveSceneTask(File file) { 1272 this.file = file; 1273 beginTask(); 1274 } 1275 1276 @Override 1277 @WorkerThread doInBackground()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 done()1297 protected void done() { 1298 endTask(); 1299 } 1300 } 1301 1302 private class SceneFocusListener implements ObjectSceneListener { 1303 objectAdded(ObjectSceneEvent arg0, Object arg1)1304 public void objectAdded(ObjectSceneEvent arg0, Object arg1) { 1305 } 1306 objectRemoved(ObjectSceneEvent arg0, Object arg1)1307 public void objectRemoved(ObjectSceneEvent arg0, Object arg1) { 1308 } 1309 objectStateChanged(ObjectSceneEvent arg0, Object arg1, ObjectState arg2, ObjectState arg3)1310 public void objectStateChanged(ObjectSceneEvent arg0, Object arg1, 1311 ObjectState arg2, ObjectState arg3) { 1312 } 1313 selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection, Set<Object> newSelection)1314 public void selectionChanged(ObjectSceneEvent e, Set<Object> previousSelection, 1315 Set<Object> newSelection) { 1316 } 1317 highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2)1318 public void highlightingChanged(ObjectSceneEvent arg0, Set<Object> arg1, Set<Object> arg2) { 1319 } 1320 hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2)1321 public void hoverChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) { 1322 } 1323 focusChanged(ObjectSceneEvent e, Object oldFocus, Object newFocus)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 mouseClicked(MouseEvent e)1340 public void mouseClicked(MouseEvent e) { 1341 if (e.getClickCount() == 2) { 1342 showNodeCapture().execute(); 1343 } 1344 } 1345 } 1346 1347 private class WheelZoomListener implements MouseWheelListener { mouseWheelMoved(MouseWheelEvent e)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 DevicesTableModel()1361 private DevicesTableModel() { 1362 devices = new ArrayList<IDevice>(); 1363 } 1364 1365 @Override getColumnCount()1366 public int getColumnCount() { 1367 return 1; 1368 } 1369 1370 @Override isCellEditable(int row, int column)1371 public boolean isCellEditable(int row, int column) { 1372 return false; 1373 } 1374 1375 @Override getValueAt(int row, int column)1376 public Object getValueAt(int row, int column) { 1377 return devices.get(row); 1378 } 1379 1380 @Override getColumnName(int column)1381 public String getColumnName(int column) { 1382 return "Devices"; 1383 } 1384 1385 @WorkerThread deviceConnected(final IDevice device)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 deviceDisconnected(final IDevice device)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 addDevice(IDevice device)1407 public void addDevice(IDevice device) { 1408 if (!devices.contains(device)) { 1409 devices.add(device); 1410 fireTableDataChanged(); 1411 } 1412 } 1413 removeDevice(IDevice device)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 deviceChanged(IDevice device, int changeMask)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 getRowCount()1440 public int getRowCount() { 1441 return devices == null ? 0 : devices.size(); 1442 } 1443 getDevice(int index)1444 public IDevice getDevice(int index) { 1445 return index < devices.size() ? devices.get(index) : null; 1446 } 1447 getDevices()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 WindowsTableModel()1457 private WindowsTableModel() { 1458 windows = new ArrayList<Window>(); 1459 windows.add(Window.FOCUSED_WINDOW); 1460 } 1461 1462 @Override getColumnCount()1463 public int getColumnCount() { 1464 return 1; 1465 } 1466 1467 @Override isCellEditable(int row, int column)1468 public boolean isCellEditable(int row, int column) { 1469 return false; 1470 } 1471 1472 @Override getColumnName(int column)1473 public String getColumnName(int column) { 1474 return "Windows"; 1475 } 1476 1477 @Override getValueAt(int row, int column)1478 public Object getValueAt(int row, int column) { 1479 return windows.get(row); 1480 } 1481 1482 @Override getRowCount()1483 public int getRowCount() { 1484 return !visible || windows == null ? 0 : windows.size(); 1485 } 1486 setVisible(boolean visible)1487 public void setVisible(boolean visible) { 1488 this.visible = visible; 1489 fireTableDataChanged(); 1490 } 1491 addWindow(Window window)1492 public void addWindow(Window window) { 1493 windows.add(window); 1494 fireTableDataChanged(); 1495 } 1496 addWindows(Window[] windowsList)1497 public void addWindows(Window[] windowsList) { 1498 //noinspection ManualArrayToCollectionCopy 1499 for (Window window : windowsList) { 1500 windows.add(window); 1501 } 1502 fireTableDataChanged(); 1503 } 1504 clear()1505 public void clear() { 1506 windows.clear(); 1507 windows.add(Window.FOCUSED_WINDOW); 1508 } 1509 getWindow(int index)1510 public Window getWindow(int index) { 1511 return windows.get(index); 1512 } 1513 } 1514 1515 private class DeviceSelectedListener implements ListSelectionListener { valueChanged(ListSelectionEvent event)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 { valueChanged(ListSelectionEvent event)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 { getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)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