| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.debugger.jpda.visual.ui; |
| |
| import java.awt.BorderLayout; |
| import java.awt.Canvas; |
| import java.awt.Color; |
| import java.awt.Component; |
| import java.awt.Dimension; |
| import java.awt.Font; |
| import java.awt.FontMetrics; |
| import java.awt.Graphics; |
| import java.awt.Image; |
| import java.awt.Insets; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.ContainerEvent; |
| import java.awt.event.ContainerListener; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.KeyListener; |
| import java.awt.event.MouseEvent; |
| import java.awt.event.MouseListener; |
| import java.awt.geom.Point2D; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ImageObserver; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyVetoException; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Logger; |
| import javax.imageio.ImageIO; |
| import javax.imageio.ImageWriter; |
| import javax.imageio.stream.ImageOutputStream; |
| import javax.swing.AbstractButton; |
| import javax.swing.Action; |
| import javax.swing.JButton; |
| import javax.swing.JComboBox; |
| import javax.swing.JComponent; |
| import javax.swing.JPopupMenu; |
| import javax.swing.JScrollPane; |
| import javax.swing.JToggleButton; |
| import javax.swing.JToolBar; |
| import javax.swing.SwingUtilities; |
| import javax.swing.filechooser.FileNameExtensionFilter; |
| import org.netbeans.api.actions.Savable; |
| import org.netbeans.api.annotations.common.StaticResource; |
| import org.netbeans.api.debugger.DebuggerEngine; |
| import org.netbeans.modules.debugger.jpda.visual.JavaComponentInfo; |
| import org.netbeans.modules.debugger.jpda.visual.options.Options; |
| import org.netbeans.modules.debugger.jpda.visual.spi.ComponentInfo; |
| import org.netbeans.modules.debugger.jpda.visual.spi.RemoteScreenshot; |
| import org.netbeans.modules.debugger.jpda.visual.spi.ScreenshotUIManager; |
| import org.netbeans.spi.navigator.NavigatorLookupHint; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.actions.SaveAction; |
| import org.openide.awt.Actions; |
| import org.openide.explorer.ExplorerManager; |
| import org.openide.filesystems.FileChooserBuilder; |
| import org.openide.nodes.Node; |
| import org.openide.util.Exceptions; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| import org.openide.util.Utilities; |
| import org.openide.util.lookup.Lookups; |
| import org.openide.util.lookup.ProxyLookup; |
| import org.openide.windows.Mode; |
| import org.openide.windows.TopComponent; |
| import org.openide.windows.WindowManager; |
| |
| /** |
| * This component draws the screenshot of a remote application. |
| * |
| * @author Martin Entlicher |
| */ |
| public class ScreenshotComponent extends TopComponent { |
| |
| private static final Logger logger = Logger.getLogger(ScreenshotComponent.class.getName()); |
| |
| private static final Map<DebuggerEngine, Set<ScreenshotComponent>> openedScreenshots = |
| new HashMap<DebuggerEngine, Set<ScreenshotComponent>>(); |
| private static volatile ScreenshotComponent activeScreenshotComponent; |
| |
| private static final String[] ZOOM_PERCENTS = { "10%", "25%", "50%", "75%", "100%", "150%", "200%", "300%" }; // NOI18N |
| private static final String PROP_ZOOM = "zoom"; // NOI18N |
| @StaticResource |
| private static final String HIERARCHY_CHANGES_ICON = "org/netbeans/modules/debugger/jpda/visual/resources/hierarchy.png"; // NOI18N |
| |
| private RemoteScreenshot screenshot; |
| private ScreenshotUIManager manager; |
| private NavigatorLookupHint componentHierarchyNavigatorHint = new ComponentHierarchyNavigatorHint(); |
| private ComponentNode componentNodes; |
| private ScreenshotCanvas canvas; |
| private JScrollPane scrollPane; |
| private boolean propertiesOpened; |
| |
| public ScreenshotComponent(RemoteScreenshot screenshot, ScreenshotUIManager manager) { |
| this.screenshot = screenshot; |
| this.manager = manager; |
| setFocusable(true); |
| screenshot.getImage(); |
| ScreenshotCanvas c = new ScreenshotCanvas(screenshot.getImage()); |
| this.canvas = c; |
| scrollPane = new JScrollPane(c); |
| scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); |
| scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); |
| setLayout(new BorderLayout()); |
| add(scrollPane, BorderLayout.CENTER); |
| JToolBar toolBar = createToolBar(); |
| add(toolBar, BorderLayout.NORTH); |
| String title = screenshot.getTitle(); |
| title = (title == null) ? NbBundle.getMessage(ScreenshotComponent.class, "LBL_DebuggerSnapshot") : |
| NbBundle.getMessage(ScreenshotComponent.class, "LBL_DebuggerSnapshotOf", title); |
| setDisplayName(title); |
| componentNodes = new ComponentNode(screenshot.getComponentInfo()); |
| ComponentHierarchy.getInstance().getExplorerManager().setRootContext(componentNodes); |
| ComponentInfo firstCi = getFirstCustomComponent(componentNodes); |
| if (firstCi != null) { |
| c.listener.selectComponent(firstCi, false); |
| } else { |
| setActivatedNodes(new Node[] { componentNodes }); |
| } |
| addKeyListener(new KeyListener() { |
| @Override |
| public void keyTyped(KeyEvent e) { |
| char c = e.getKeyChar(); |
| switch (c) { |
| case '+': |
| case '=': canvas.zoomIn(); |
| break; |
| case '-': |
| case '_': canvas.zoomOut(); |
| break; |
| case '1': canvas.zoom(1.0); |
| break; |
| case '2': canvas.zoom(2.0); |
| break; |
| case '3': canvas.zoom(3.0); |
| break; |
| case '4': canvas.zoom(4.0); |
| break; |
| case '7': canvas.zoom(1.0/4.0); |
| break; |
| case '8': canvas.zoom(1.0/3.0); |
| break; |
| case '9': canvas.zoom(1.0/2.0); |
| break; |
| case 'X': |
| case 'x': canvas.zoomFit(); |
| break; |
| } |
| } |
| @Override |
| public void keyPressed(KeyEvent e) {} |
| @Override |
| public void keyReleased(KeyEvent e) {} |
| }); |
| } |
| |
| private JToolBar createToolBar() { |
| JToolBar toolBar = new JToolBar(NbBundle.getMessage(ScreenshotComponent.class, "CTL_ToolBarName")); |
| toolBar.getAccessibleContext().setAccessibleName(NbBundle.getMessage(ScreenshotComponent.class, "CTL_ToolBarA11yName")); |
| toolBar.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(ScreenshotComponent.class, "CTL_ToolBarA11yDescr")); |
| toolBar.add(createZoomInButton()); |
| toolBar.add(createZoomOutButton()); |
| toolBar.addSeparator(); |
| toolBar.add(createZoomOrigButton()); |
| toolBar.addSeparator(); |
| toolBar.add(createZoomFitButton()); |
| toolBar.addSeparator(); |
| toolBar.add(createZoomPercent()); |
| toolBar.addSeparator(); |
| toolBar.add(createSaveButton()); |
| toolBar.addSeparator(); |
| toolBar.add(createHierarchyChangesButton()); |
| toolBar.addSeparator(); |
| return toolBar; |
| } |
| |
| private JButton createZoomInButton() { |
| JButton inButton = new JButton(ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/debugger/jpda/visual/resources/zoomIn.gif"))); |
| inButton.setToolTipText(NbBundle.getMessage(ScreenshotComponent.class, "TLTP_ZoomIn")); |
| inButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomInA11yDescr")); |
| inButton.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| canvas.zoomIn(); |
| } |
| }); |
| inButton.setAlignmentX(CENTER_ALIGNMENT); |
| return inButton; |
| } |
| |
| private JButton createZoomOutButton() { |
| JButton outButton = new JButton(ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/debugger/jpda/visual/resources/zoomOut.gif"))); |
| outButton.setToolTipText(NbBundle.getMessage(ScreenshotComponent.class, "TLTP_ZoomOut")); |
| outButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomOutA11yDescr")); |
| outButton.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| canvas.zoomOut(); |
| } |
| }); |
| outButton.setAlignmentX(CENTER_ALIGNMENT); |
| return outButton; |
| } |
| |
| private JButton createZoomOrigButton() { |
| JButton origButton = new JButton(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomOrig")); |
| origButton.setToolTipText(NbBundle.getMessage(ScreenshotComponent.class, "TLTP_ZoomOrig")); |
| origButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomOrigA11yDescr")); |
| origButton.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| canvas.zoom(1); |
| } |
| }); |
| origButton.setAlignmentX(CENTER_ALIGNMENT); |
| return origButton; |
| } |
| |
| private JButton createZoomFitButton() { |
| JButton fitButton = new JButton(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomFit")); |
| fitButton.setToolTipText(NbBundle.getMessage(ScreenshotComponent.class, "TLTP_ZoomFit")); |
| fitButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomFitA11yDescr")); |
| fitButton.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| canvas.zoomFit(); |
| } |
| }); |
| fitButton.setAlignmentX(CENTER_ALIGNMENT); |
| return fitButton; |
| } |
| |
| private JComboBox createZoomPercent() { |
| final JComboBox cb = new JComboBox(ZOOM_PERCENTS); |
| final boolean[] ignoreChanges = { false }; |
| cb.setEditable(true); |
| Font font = cb.getEditor().getEditorComponent().getFont(); |
| int textWidth = 0; |
| FontMetrics fm = cb.getEditor().getEditorComponent().getFontMetrics(font); |
| for (String zp : ZOOM_PERCENTS) { |
| int sw = fm.stringWidth(zp); |
| textWidth = Math.max(textWidth, sw); |
| } |
| for (Component c : cb.getComponents()) { |
| if (c instanceof AbstractButton) { |
| AbstractButton ab = (AbstractButton) c; |
| Insets insets = ab.getInsets(); |
| int buttonWidth = insets.left + |
| Math.max(c.getMinimumSize().width, c.getPreferredSize().width) + |
| insets.right; |
| Dimension dim = new Dimension(buttonWidth + textWidth + 10, cb.getPreferredSize().height); |
| cb.setPreferredSize(dim); |
| cb.setMinimumSize(dim); |
| cb.setMaximumSize(dim); |
| break; |
| } |
| } |
| //cb.setPreferredSize(new Dimension(textWidth, cb.getPreferredSize().height)); |
| cb.setPrototypeDisplayValue(ZOOM_PERCENTS[ZOOM_PERCENTS.length - 1]); |
| cb.setMaximumSize(cb.getPreferredSize()); |
| cb.setToolTipText(NbBundle.getMessage(ScreenshotComponent.class, "TLTP_ZoomPercent")); |
| cb.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(ScreenshotComponent.class, "LBL_ZoomPercentA11yDescr")); |
| double scale = canvas.getScale(); |
| int pc = (int) (scale * 100); |
| String str = Integer.toString(pc) + '%'; |
| cb.setSelectedItem(str); |
| cb.addContainerListener(new ContainerListener() { |
| @Override |
| public void componentAdded(ContainerEvent e) { |
| Component child = e.getChild(); |
| if (child instanceof AbstractButton) { |
| int buttonWidth = child.getMinimumSize().width; |
| Font font = cb.getEditor().getEditorComponent().getFont(); |
| int textWidth = cb.getEditor().getEditorComponent().getFontMetrics(font).stringWidth(ZOOM_PERCENTS[ZOOM_PERCENTS.length - 1]); |
| int width = buttonWidth + textWidth + 10; |
| Dimension dim = new Dimension(width, cb.getPreferredSize().height); |
| cb.setPreferredSize(dim); |
| cb.setMinimumSize(dim); |
| cb.setMaximumSize(dim); |
| cb.removeContainerListener(this); |
| } |
| } |
| @Override |
| public void componentRemoved(ContainerEvent e) {} |
| }); |
| cb.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| if (ignoreChanges[0]) return; |
| String selected = (String) cb.getSelectedItem(); |
| selected = selected.trim(); |
| if (selected.endsWith("%")) { |
| selected = selected.substring(0, selected.length() - 1); |
| } |
| int pc; |
| try { |
| pc = Integer.parseInt(selected); |
| double scale = pc / 100.0; |
| canvas.setScale(scale); |
| } catch (NumberFormatException nfex) { |
| // Wrong value - set the current one. |
| pc = (int) (canvas.getScale() * 100); |
| String str = Integer.toString(pc) + '%'; |
| ignoreChanges[0] = true; |
| try { |
| cb.setSelectedItem(str); |
| } finally { |
| ignoreChanges[0] = false; |
| } |
| } |
| } |
| }); |
| canvas.addPropertyChangeListener(PROP_ZOOM, new PropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| double newScale = (Double) evt.getNewValue(); |
| int pc = (int) (newScale * 100); |
| String str = Integer.toString(pc) + '%'; |
| if (!str.equals(cb.getSelectedItem())) { |
| ignoreChanges[0] = true; |
| try { |
| cb.setSelectedItem(str); |
| } finally { |
| ignoreChanges[0] = false; |
| } |
| } |
| } |
| }); |
| cb.setAlignmentX(CENTER_ALIGNMENT); |
| return cb; |
| } |
| |
| private JButton createSaveButton() { |
| SaveAction sa = SaveAction.get(SaveAction.class); |
| Action saveAction = sa.createContextAwareInstance(Lookups.singleton(new ScreenshotSavable())); |
| JButton jb = new JButton(); |
| Actions.connect(jb, saveAction); |
| return jb; |
| } |
| |
| @NbBundle.Messages({"TT_TrackingComponentChanges=Tracking locations of component hierarchy changes (may have impact on application performance)", |
| "TTL_TrackComponentChangesConfirm=Confirm Tracking of Component Hierarchy Changes", |
| "MSG_TrackComponentChangesConfirm=Tracking of component hierarchy changes allows you to "+ |
| "navigate to the place where components were added to their containers.\n"+ |
| "The tracking may have an impact on performace of the application.\n"+ |
| "Turn on the tracking? You may need to restart the application to take an effect."}) |
| private JToggleButton createHierarchyChangesButton() { |
| final JToggleButton jtb = new JToggleButton(ImageUtilities.loadImageIcon(HIERARCHY_CHANGES_ICON, false)); |
| boolean tcc = Options.isTrackComponentChanges(); |
| jtb.setSelected(tcc); |
| jtb.setToolTipText(Bundle.TT_TrackingComponentChanges()); |
| jtb.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| boolean tcc = jtb.isSelected(); |
| if (tcc) { |
| NotifyDescriptor.Confirmation confirmation = new NotifyDescriptor.Confirmation( |
| Bundle.MSG_TrackComponentChangesConfirm(), |
| Bundle.TTL_TrackComponentChangesConfirm(), |
| NotifyDescriptor.YES_NO_OPTION |
| ); |
| Object result = DialogDisplayer.getDefault().notify(confirmation); |
| if (result != NotifyDescriptor.YES_OPTION) { |
| jtb.setSelected(false); |
| return ; |
| } |
| } |
| Options.setTrackComponentChanges(tcc); |
| } |
| }); |
| return jtb; |
| } |
| |
| private static ComponentInfo getFirstCustomComponent(Node node) { |
| ComponentInfo ci = node.getLookup().lookup(ComponentInfo.class); |
| if (ci != null && ci instanceof JavaComponentInfo && ((JavaComponentInfo) ci).isCustomType()) { |
| return ci; |
| } else { |
| Node[] nodes = node.getChildren().getNodes(); |
| for (Node n : nodes) { |
| ComponentInfo fci = getFirstCustomComponent(n); |
| if (fci != null) { |
| return fci; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static ScreenshotComponent getActive() { |
| return activeScreenshotComponent; |
| /* |
| WindowManager wm = WindowManager.getDefault(); |
| synchronized (openedScreenshots) { |
| for (Set<ScreenshotComponent> ssc : openedScreenshots.values()) { |
| for (ScreenshotComponent sc : ssc) { |
| Mode m = wm.findMode(sc); |
| if (m != null && m.getSelectedTopComponent() == sc) { |
| return sc; |
| } |
| } |
| } |
| } |
| return null; |
| */ |
| } |
| |
| public ScreenshotUIManager getManager() { |
| return manager; |
| } |
| |
| public ComponentInfo getSelectedComponent() { |
| Node[] nodes = getActivatedNodes(); |
| if (nodes.length > 0) { |
| return nodes[0].getLookup().lookup(ComponentInfo.class); |
| } |
| return null; |
| } |
| |
| @Override |
| public Lookup getLookup() { |
| Lookup lookup = super.getLookup(); |
| return new ProxyLookup(lookup, Lookups.singleton(componentHierarchyNavigatorHint)); |
| } |
| |
| @Override |
| protected void componentActivated() { |
| logger.fine("componentActivated() root = "+componentNodes+", ci = "+componentNodes.getLookup().lookup(ComponentInfo.class)); |
| activeScreenshotComponent = this; |
| ComponentHierarchy.getInstance().getExplorerManager().setRootContext(componentNodes); |
| ComponentHierarchy.getInstance().getExplorerManager().setExploredContext(componentNodes); |
| canvas.activated(); |
| } |
| |
| @Override |
| protected void componentDeactivated() { |
| super.componentDeactivated(); |
| //canvas.deactivated(); |
| } |
| |
| @Override |
| protected void componentShowing() { |
| super.componentShowing(); |
| TopComponent properties = WindowManager.getDefault().findTopComponent("properties"); // NOI18N |
| if (properties != null) { |
| if (!properties.isOpened()) { |
| propertiesOpened = true; |
| properties.open(); |
| } |
| } |
| } |
| |
| @Override |
| protected void componentHidden() { |
| super.componentHidden(); |
| if (propertiesOpened) { |
| propertiesOpened = false; |
| TopComponent properties = WindowManager.getDefault().findTopComponent("properties"); // NOI18N |
| if (properties != null) { |
| properties.close(); |
| } |
| } |
| } |
| |
| @Override |
| protected void componentOpened() { |
| synchronized (openedScreenshots) { |
| Set<ScreenshotComponent> components = openedScreenshots.get(screenshot.getDebuggerEngine()); |
| if (components == null) { |
| components = new HashSet<ScreenshotComponent>(); |
| openedScreenshots.put(screenshot.getDebuggerEngine(), components); |
| } |
| components.add(this); |
| } |
| } |
| |
| @Override |
| protected void componentClosed() { |
| if (activeScreenshotComponent == this) { |
| activeScreenshotComponent = null; |
| } |
| synchronized (openedScreenshots) { |
| Set<ScreenshotComponent> components = openedScreenshots.get(screenshot.getDebuggerEngine()); |
| if (components != null) { |
| components.remove(this); |
| if (components.isEmpty()) { |
| openedScreenshots.remove(screenshot.getDebuggerEngine()); |
| } |
| } |
| } |
| canvas.deactivated(); |
| } |
| |
| @Override |
| public int getPersistenceType() { |
| return PERSISTENCE_NEVER; |
| } |
| |
| public static void closeScreenshots(DebuggerEngine engine) { |
| synchronized (openedScreenshots) { |
| Set<ScreenshotComponent> components = openedScreenshots.get(engine); |
| if (components != null) { |
| final Set<ScreenshotComponent> theComponents = new HashSet<ScreenshotComponent>(components); |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| for (ScreenshotComponent c : theComponents) { |
| c.close(); |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| public void markBreakpoint(ComponentInfo ci) { |
| canvas.markBreakpoint(ci); |
| } |
| |
| public void unmarkBreakpoint(ComponentInfo ci) { |
| canvas.unmarkBreakpoint(ci); |
| } |
| |
| private class ScreenshotCanvas extends JComponent { |
| |
| private Image image; |
| private Rectangle selection; |
| private final Set<Rectangle> breakpoints = new HashSet<Rectangle>(); |
| private Listener listener; |
| private boolean active; |
| private double scale = 1D; |
| private final double SCALLE_FACTOR = Math.sqrt(2.0D); // Treated as static |
| |
| public ScreenshotCanvas(Image image) { |
| this.image = image; |
| listener = new Listener(); |
| updateSize(); |
| } |
| |
| private void updateSize() { |
| ImageObserver io = new ImageObserver() { |
| @Override |
| public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { |
| boolean hasHeight = (infoflags & ImageObserver.HEIGHT) > 0; |
| boolean hasWidth = (infoflags & ImageObserver.WIDTH) > 0; |
| if (!hasHeight || !hasWidth) { |
| return true; |
| } |
| ScreenshotCanvas.this.setSize(width, height); |
| ScreenshotCanvas.this.setPreferredSize(ScreenshotCanvas.this.getSize()); |
| return false; |
| } |
| }; |
| int width = (int) (scale * image.getWidth(io)) + 2; |
| int height = (int) (scale * image.getHeight(io)) + 2; |
| if (width > 0 && height > 0) { |
| setSize(width, height); |
| setPreferredSize(getSize()); |
| } |
| } |
| |
| @Override |
| public void paintComponent(Graphics g) { |
| int imageWidth = image.getWidth(this); |
| int imageHeight = image.getHeight(this); |
| int scaledWidth = (int) (scale * imageWidth); |
| int scaledHeight = (int) (scale * imageHeight); |
| g.drawImage(image, 1, 1, scaledWidth + 1, scaledHeight + 1, |
| 0, 0, imageWidth, imageHeight, this); |
| g.drawRect(0, 0, scaledWidth + 2, scaledHeight + 2); |
| synchronized (breakpoints) { |
| Color c = g.getColor(); |
| g.setColor(Color.RED); |
| for (Rectangle r : breakpoints) { |
| g.drawRect((int) (scale * r.x), (int) (scale * r.y), |
| (int) (scale * r.width) + 1, (int) (scale * r.height) + 1); |
| } |
| g.setColor(c); |
| } |
| if (selection != null) { |
| Color c = g.getColor(); |
| g.setColor(Color.BLUE); |
| g.drawRect((int) (scale * selection.x), (int) (scale * selection.y), |
| (int) (scale * selection.width) + 1, (int) (scale * selection.height) + 1); |
| g.setColor(c); |
| } |
| //image.getSource(); |
| } |
| |
| void activated() { |
| if (selection != null) { |
| listener.selectComponentAt((int) (scale * selection.x) + 1, (int) (scale * selection.y) + 1, true); |
| } |
| if (!active) { |
| addMouseListener(listener); |
| ComponentHierarchy.getInstance().getExplorerManager().addPropertyChangeListener(listener); |
| } |
| active = true; |
| } |
| |
| void deactivated() { |
| if (!active) return ; |
| active = false; |
| removeMouseListener(listener); |
| ComponentHierarchy.getInstance().getExplorerManager().removePropertyChangeListener(listener); |
| } |
| |
| private Rectangle getBreakpointRectangle(ComponentInfo ci) { |
| Rectangle r = ci.getWindowBounds(); |
| return new Rectangle(r.x - 1, r.y - 1, r.width + 2, r.height + 2); |
| } |
| |
| private void markBreakpoint(ComponentInfo ci) { |
| Rectangle r; |
| synchronized (breakpoints) { |
| r = getBreakpointRectangle(ci); |
| breakpoints.add(r); |
| } |
| repaint(r.x, r.y, r.width + 3, r.height + 3); |
| } |
| |
| private void unmarkBreakpoint(ComponentInfo ci) { |
| Rectangle r; |
| synchronized (breakpoints) { |
| r = getBreakpointRectangle(ci); |
| breakpoints.remove(r); |
| } |
| repaint(r.x, r.y, r.width + 3, r.height + 3); |
| } |
| |
| public void zoomIn() { |
| setScale(scale * SCALLE_FACTOR); |
| } |
| |
| public void zoomOut() { |
| double newScale = scale / SCALLE_FACTOR; |
| if (newScale * image.getWidth(null) < 1 && newScale * image.getHeight(null) < 1) { |
| // Too small to further zoom out |
| return ; |
| } |
| setScale(newScale); |
| } |
| |
| public void zoomFit() { |
| Rectangle bounds = scrollPane.getViewportBorderBounds(); |
| int imageWidth = image.getWidth(null); |
| int imageHeight = image.getHeight(null); |
| if (imageWidth <= 0 || imageHeight <= 0) { |
| // nothing known yet |
| return; |
| } |
| double scalew = ((double) bounds.width)/(imageWidth + 2); |
| double scaleh = ((double) bounds.height)/(imageHeight + 2); |
| setScale(Math.min(scalew, scaleh)); |
| } |
| |
| public void zoom(double newScale) { |
| int imageWidth = image.getWidth(null); |
| int imageHeight = image.getHeight(null); |
| if (imageWidth <= 0 || imageHeight <= 0) { |
| // nothing known yet |
| return; |
| } |
| if (newScale * imageWidth < 1 && newScale * imageHeight < 1) { |
| newScale = Math.min(((double) 1)/imageWidth, ((double) 1)/imageHeight); |
| } |
| setScale(newScale); |
| } |
| |
| private void setScale(double scale) { |
| if (this.scale != scale) { |
| Point2D.Double center = computeCenterPoint(); |
| double oldScale = this.scale; |
| this.scale = scale; |
| updateSize(); |
| setCenterPoint(center); |
| repaint(); |
| firePropertyChange(PROP_ZOOM, oldScale, scale); |
| } |
| } |
| |
| public double getScale() { |
| return scale; |
| } |
| |
| /** Computes the current image point, that is in the center in the current view area. */ |
| private Point2D.Double computeCenterPoint() { |
| Point p = scrollPane.getViewport().getViewPosition(); |
| Rectangle viewRect = scrollPane.getViewport().getViewRect(); |
| p.x += viewRect.width/2; |
| p.y += viewRect.height/2; |
| return new Point2D.Double(p.x / scale, p.y / scale); |
| } |
| |
| /** Sets the current view area to have the image point in it's center. */ |
| private void setCenterPoint(Point2D.Double pd) { |
| Rectangle viewRect = scrollPane.getViewport().getViewRect(); |
| Point p = new Point((int) (pd.x * scale), (int) (pd.y * scale)); |
| p.x -= viewRect.width/2; |
| if (p.x < 0) p.x = 0; |
| p.y -= viewRect.height/2; |
| if (p.y < 0) p.y = 0; |
| scrollPane.getViewport().setViewPosition(p); |
| } |
| |
| void save(File file) throws IOException { |
| ImageWriter iw = null; |
| String name = file.getName(); |
| int i = name.lastIndexOf('.'); |
| if (i >= 0) { |
| String extension = name.substring(i + 1); |
| Iterator<ImageWriter> imageWritersBySuffix = ImageIO.getImageWritersBySuffix(extension); |
| if (imageWritersBySuffix.hasNext()) { |
| iw = imageWritersBySuffix.next(); |
| } |
| } |
| if (iw != null) { |
| file.delete(); |
| ImageOutputStream ios = ImageIO.createImageOutputStream(file); |
| iw.setOutput(ios); |
| try { |
| iw.write((BufferedImage) image); |
| } finally { |
| iw.dispose(); |
| ios.flush(); |
| ios.close(); |
| } |
| } else { |
| ImageIO.write((BufferedImage) image, "PNG", file); |
| } |
| } |
| |
| private class Listener implements MouseListener, PropertyChangeListener { |
| |
| @Override |
| public void mouseClicked(MouseEvent e) { |
| //selectComponentAt(e.getX(), e.getY()); |
| } |
| |
| @Override |
| public void mousePressed(MouseEvent e) { |
| if (e.isPopupTrigger()) { |
| selectComponentAt(e.getX(), e.getY(), false); |
| showPopupMenu(e.getX(), e.getY()); |
| } |
| } |
| |
| @Override |
| public void mouseReleased(MouseEvent e) { |
| selectComponentAt(e.getX(), e.getY(), false); |
| if (e.isPopupTrigger()) { |
| showPopupMenu(e.getX(), e.getY()); |
| } |
| } |
| |
| @Override |
| public void mouseEntered(MouseEvent e) {} |
| |
| @Override |
| public void mouseExited(MouseEvent e) {} |
| |
| private void showPopupMenu(int x, int y) { |
| Node[] activatedNodes = getActivatedNodes(); |
| if (activatedNodes.length == 1) { |
| Action[] actions = activatedNodes[0].getActions(true); |
| JPopupMenu contextMenu = Utilities.actionsToPopup(actions, ScreenshotComponent.this); |
| contextMenu.show(ScreenshotComponent.this.canvas, x, y); |
| } |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| logger.fine("propertyChange("+evt+") propertyName = "+evt.getPropertyName()); |
| String propertyName = evt.getPropertyName(); |
| if (ExplorerManager.PROP_SELECTED_NODES.equals(propertyName)) { |
| Node[] nodes = ComponentHierarchy.getInstance().getExplorerManager().getSelectedNodes(); |
| ComponentInfo ci = null; |
| if (nodes.length > 0) { |
| ci = nodes[0].getLookup().lookup(ComponentInfo.class); |
| } |
| logger.fine("nodes = "+Arrays.toString(nodes)+" => selectComponent("+ci+")"); |
| selectComponent(ci, false); |
| } else if (ExplorerManager.PROP_ROOT_CONTEXT.equals(propertyName)) { |
| deactivated(); |
| } |
| } |
| |
| private void selectComponentAt(int x, int y, boolean reActivated) { |
| x -= 1; |
| x = (int) (x / scale); |
| y -= 1; |
| y = (int) (y / scale); |
| ComponentInfo ci = screenshot.findAt(x, y); |
| logger.fine("Component Info at "+x+", "+y+" is: "+((ci != null) ? ci.getDisplayName() : null)); |
| selectComponent(ci, reActivated); |
| } |
| |
| private void selectComponent(ComponentInfo ci, boolean reActivated) { |
| Node node = null; |
| if (ci != null) { |
| Rectangle oldSelection = null; |
| if (selection != null) { |
| oldSelection = selection; |
| } |
| selection = ci.getWindowBounds(); |
| if (oldSelection != null) { |
| if (oldSelection.equals(selection) && !reActivated) { |
| return ; // already selected |
| } |
| repaint((int) (scale * oldSelection.x), (int) (scale * oldSelection.y), |
| (int) (scale * oldSelection.width) + 3, (int) (scale * oldSelection.height) + 3); |
| } |
| repaint((int) (scale * selection.x), (int) (scale * selection.y), |
| (int) (scale * selection.width) + 3, (int) (scale * selection.height) + 3); |
| logger.fine("New selection = "+selection); |
| node = componentNodes.findNodeFor(ci); |
| logger.fine("FindNodeFor("+ci+") on '"+componentNodes+"' gives: "+node); |
| } |
| Node[] nodes; |
| if (node != null) { |
| nodes = new Node[] { node }; |
| } else { |
| nodes = new Node[] {}; |
| } |
| logger.fine("setActivated/SelectedNodes("+Arrays.toString(nodes)+")"); |
| setActivatedNodes(nodes); |
| try { |
| ComponentHierarchy.getInstance().getExplorerManager().setSelectedNodes(nodes); |
| } catch (PropertyVetoException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| } |
| |
| } |
| |
| @NbBundle.Messages({"# {0} - The file name to overwrite", |
| "MSG_Overwrite=Do you want to overwrite {0}?", |
| "CTL_ImageFiles=Image Files"}) |
| private class ScreenshotSavable implements Savable { |
| |
| @Override |
| public void save() throws IOException { |
| FileChooserBuilder fchb = new FileChooserBuilder(ScreenshotComponent.class); |
| String[] writerFileSuffixes = ImageIO.getWriterFileSuffixes(); |
| fchb.setFileFilter(new FileNameExtensionFilter(Bundle.CTL_ImageFiles(), writerFileSuffixes)); |
| File file = fchb.showSaveDialog(); |
| if (file != null) { |
| if (file.exists()) { |
| NotifyDescriptor nd = new NotifyDescriptor.Confirmation(Bundle.MSG_Overwrite(file.getName()), toString()); |
| Object doOverwrite = DialogDisplayer.getDefault().notify(nd); |
| if (!NotifyDescriptor.YES_OPTION.equals(doOverwrite)) { |
| return ; |
| } |
| } |
| canvas.save(file); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return ScreenshotComponent.this.getDisplayName(); |
| } |
| |
| } |
| |
| } |