// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.ui.components; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Observable; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JViewport; public class ZoomPane extends JPanel implements Scalable, MouseListener, MouseMotionListener { private final static long serialVersionUID = 0xAB343927; private double scale; private Zoomable<? extends JComponent> viewPort; private JScrollPane scrollPane; private List<MouseListener> mouseListeners; private List<MouseMotionListener> mouseMotionListeners; private MyObservable observable = new MyObservable(); private JComponent root; public ZoomPane() { this(true); } public ZoomPane(boolean enableScrolling) { super(); mouseListeners = new LinkedList<MouseListener>(); mouseMotionListeners = new LinkedList<MouseMotionListener>(); scale = 1; inheritListeners(); setLayout(new BorderLayout()); internal.addMouseListener(this); internal.addMouseMotionListener(this); JViewport port = new GrabbableViewport(); port.setView(internal); if (enableScrolling) { scrollPane = new JScrollPane(); scrollPane.setViewport(port); scrollPane.setDoubleBuffered(true); scrollPane.getVerticalScrollBar().setUnitIncrement(30); add(scrollPane, BorderLayout.CENTER); } else { add(internal, BorderLayout.CENTER); } root = this; } /* * Note: this method has been inserted because otherwise in full-screen * mode the shape of the cursor would not change. FullScreen sets * the root to its "glass pane", this is enough to work a way around * the problem. */ /** * Sets the <tt>JComponent</tt> whose cursor is changed when the * <tt>setCursor</tt> method is called on the internal <tt>JPanel</tt> * containing the zoomable content. * * @param root * the <tt>JComponent</tt> whose cursor is changed when the * <tt>setCursor</tt> method is called on the internal * <tt>JPanel</tt> containing the zoomable content */ public void setRoot(JComponent root) { this.root = root; } public void addMouseListener(MouseListener mouseListener) { internal.addMouseListener(mouseListener); } public void addMouseMotionListener(MouseMotionListener mouseMotionListener) { internal.addMouseMotionListener(mouseMotionListener); } public JPanel getPanel() { return internal; } public void scrollToBottom() { scrollPane.getVerticalScrollBar().setValue( scrollPane.getVerticalScrollBar().getMaximum()); } public void scrollToPosition(float xratio, float yratio) { final int hx = internal.getVisibleRect().width / 2; final int hy = internal.getVisibleRect().height / 2; final int ymax = scrollPane.getVerticalScrollBar().getMaximum(); int y = (int) (yratio * ymax); scrollPane.getVerticalScrollBar().setValue(y - hy); final int xmax = scrollPane.getHorizontalScrollBar().getMaximum(); int x = (int) (xratio * xmax); scrollPane.getHorizontalScrollBar().setValue(x - hx); } public void home() { scrollPane.getHorizontalScrollBar().setValue(0); scrollPane.getVerticalScrollBar().setValue(0); } private void inheritListeners() { mouseListeners.clear(); mouseMotionListeners.clear(); if (viewPort != null) { mouseListeners.addAll(Arrays.asList(((Component) viewPort) .getMouseListeners())); mouseMotionListeners.addAll(Arrays.asList(((Component) viewPort) .getMouseMotionListeners())); } } public void fitWidth() { if (viewPort == null) { return; } setScale(1D * getWidth() / viewPort.getAbsoluteWidth()); } public void fitHeight() { if (viewPort == null) { return; } setScale(1D * getHeight() / viewPort.getAbsoluteHeight()); } public void fitSize() { if (viewPort == null) { return; } double w = 1D * getWidth() / viewPort.getAbsoluteWidth(); double h = 1D * getHeight() / viewPort.getAbsoluteHeight(); setScale(Math.min(w, h)); } public void setScale(double scale) { Rectangle visible = internal.getVisibleRect(); double factor = scale / this.scale; this.scale = Math.min(4, scale); redraw(); visible = new Rectangle((int) (visible.x * factor), (int) (visible.y * factor), (int) (visible.width * factor), (int) (visible.height * factor)); internal.scrollRectToVisible(visible); observable.setChanged(); observable.notifyObservers(this); } public void redraw() { if (scrollPane != null) { // scrollingEnabled scrollPane.setViewportView(internal); } } public JScrollPane getScrollPane() { return scrollPane; } public void setViewportView(Zoomable viewPort) { this.viewPort = viewPort; if (viewPort != null) { viewPort.setZoomPane(this); } inheritListeners(); redraw(); } public double getScale() { return scale; } private JPanel internal = new JPanel() { public Dimension getPreferredSize() { if (viewPort == null) { return super.getPreferredSize(); } return new Dimension((int) (viewPort.getAbsoluteWidth() * scale), (int) (viewPort.getAbsoluteHeight() * scale)); } @Override public void setCursor(Cursor cursor) { root.setCursor(cursor); } @Override public String getToolTipText(MouseEvent e) { if (viewPort == null) { return ""; } return viewPort.asJComponent().getToolTipText(translate(e)); } @Override public void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.scale(scale, scale); if (viewPort == null) { Rectangle rectangle = g2d.getClipBounds(); g2d.setColor(Color.WHITE); g2d.fill(rectangle); } else { viewPort.paintComponent(g2d); } g2d.dispose(); } }; private MouseEvent translate(MouseEvent e) { int x = (int) (e.getX() / scale); int y = (int) (e.getY() / scale); return new MouseEvent((Component) e.getSource(), e.getID(), e.getWhen(), e.getModifiers(), x, y, e.getClickCount(), e .isPopupTrigger(), e.getButton()); } public void mouseEntered(MouseEvent e) { for (MouseListener ml : mouseListeners) { ml.mouseEntered(translate(e)); } } public void mouseExited(MouseEvent e) { for (MouseListener ml : mouseListeners) { ml.mouseExited(translate(e)); } } public void mouseClicked(MouseEvent e) { for (MouseListener ml : mouseListeners) { ml.mouseClicked(translate(e)); } } public void mousePressed(MouseEvent e) { for (MouseListener ml : mouseListeners) { ml.mousePressed(translate(e)); } } public void mouseReleased(MouseEvent e) { for (MouseListener ml : mouseListeners) { ml.mouseReleased(translate(e)); } } public void mouseDragged(MouseEvent e) { for (MouseMotionListener ml : mouseMotionListeners) { ml.mouseDragged(translate(e)); } } public void mouseMoved(MouseEvent e) { for (MouseMotionListener ml : mouseMotionListeners) { ml.mouseMoved(translate(e)); } } // Multiple inheritance, Java style: public Observable asObservable() { return observable; } private static class MyObservable extends Observable { public synchronized void setChanged() { super.setChanged(); } } }