/* * @(#)MiniMapView.java * * Project: JHotdraw - a GUI framework for technical drawings * http://www.jhotdraw.org * http://jhotdraw.sourceforge.net * Copyright: © by the original author(s) and all contributors * License: Lesser GNU Public License (LGPL) * http://www.opensource.org/licenses/lgpl-license.html */ package CH.ifa.draw.contrib; import CH.ifa.draw.framework.DrawingView; import CH.ifa.draw.framework.DrawingChangeEvent; import CH.ifa.draw.framework.DrawingChangeListener; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; import javax.swing.event.*; /** * Utility component for enhancing component scrolling. It provides a "minature" view of the entire contents of a JScrollPane * and the means to scroll that JScrollPane by moving a rectangle representing the viewable area of the JScrollPane. If the * user clicks on an area inside of the MiniMapView, the JScrollPane's view is centered on that area. The user may also drag * the rectangle representation of the JScrollPane's view around the MiniMap to scroll. * * @author S. Ruman (sruman@rogers.com) * @version <$CURRENT_VERSION$> */ public class MiniMapView extends JComponent { // Instance Variables private JScrollPane m_subject; private DrawingView myMappedDrawingView; private SubjectListener m_subjectListener; private DrawingChangeListener myDrawingChangeListener; private Color m_viewBoxColor = Color.red; // Constructors public MiniMapView(DrawingView newMappedDrawingView, JScrollPane subject) { m_subjectListener = new SubjectListener(); setSubject(subject); setMappedDrawingView(newMappedDrawingView); myDrawingChangeListener = new MappedDrawingChangeListener(); getMappedDrawingView().drawing().addDrawingChangeListener(myDrawingChangeListener); MouseListener ml = new MouseListener(); addMouseListener(new MouseListener()); addMouseMotionListener(ml); } protected void setMappedDrawingView(DrawingView newMappedDrawingView) { myMappedDrawingView = newMappedDrawingView; } public DrawingView getMappedDrawingView() { return myMappedDrawingView; } // accessors protected void setSubject(JScrollPane subject) { if (m_subject != null) { m_subject.getViewport().removeChangeListener(m_subjectListener); } m_subject = subject; if (m_subject != null) { m_subject.getViewport().addChangeListener(m_subjectListener); } repaint(); } public JScrollPane getSubject() { return m_subject; } public Color getViewBowColor() { return m_viewBoxColor; } public void setViewBoxColor(Color c) { m_viewBoxColor = c; repaint(); } /** * @return The component that is actually being "mini-mapped", that is the component inside the scroll pane */ protected Component getMappedComponent() { return (Component)getMappedDrawingView(); } // Overridden public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; // Paint a small map representation of the subjects contents Component mappedComponent = getMappedComponent(); AffineTransform at = getViewToMiniMapTransform(mappedComponent); g2d.transform(at); getMappedDrawingView().drawAll(g2d); // Draw a rectangle representing the viewable area drawViewRectangle(g2d, getViewRectangle()); } // side-effect of setting the transform on g2d to identity protected void drawViewRectangle(Graphics2D g2d, Rectangle viewPortRectangle) { AffineTransform at = new AffineTransform(); at.setToIdentity(); g2d.setTransform(at); g2d.setColor(m_viewBoxColor); g2d.draw(viewPortRectangle); } protected AffineTransform getViewToMiniMapTransform(Component mappedComponent) { double scaleX = ((double)getWidth()) / ((double)mappedComponent.getWidth()); double scaleY = ((double)getHeight()) / ((double)mappedComponent.getHeight()); AffineTransform at = getInverseSubjectTransform(); // for subclass flexibility at.concatenate(AffineTransform.getScaleInstance(scaleX, scaleY)); return at; } /** * Allows subclasses to modify the transformation used in creating the mini-map */ protected AffineTransform getInverseSubjectTransform() { AffineTransform at = new AffineTransform(); at.setToIdentity(); return at; } /** * @return The rectangle (in Mini-Map world-coordinates) representing the area being viewed inside of the scroll pane subject */ protected Rectangle getViewRectangle() { Rectangle visiblePortion = m_subject.getViewportBorderBounds(); Point upperLeftViewPos = m_subject.getViewport().getViewPosition(); double [] srcRecCorners = new double[4]; double [] dstRecCorners = new double[4]; srcRecCorners[0] = upperLeftViewPos.x + visiblePortion.getX(); srcRecCorners[1] = upperLeftViewPos.y + visiblePortion.getY(); srcRecCorners[2] = upperLeftViewPos.x + visiblePortion.getX() + visiblePortion.getWidth(); srcRecCorners[3] = upperLeftViewPos.y + visiblePortion.getY() + visiblePortion.getHeight(); getViewToMiniMapTransform(getMappedComponent()).transform(srcRecCorners, 0, dstRecCorners, 0, srcRecCorners.length/2); // transform the coordinates to MiniMapView coordinates return new Rectangle((int)dstRecCorners[0], (int)dstRecCorners[1], (int)(dstRecCorners[2] - dstRecCorners[0]), (int)(dstRecCorners[3] - dstRecCorners[1])); } /** * Scrolls the subject scroll pane to the coordinates specified. * @param upperLeftX The new upper left corner X-coordinate (in subject world-coordinates) of the subject scroll pane * @param upperLeftY The new upper left corner Y-coordinate (in subject world-coordinates) of the subject scroll pane */ protected void scrollSubjectTo(int upperLeftX, int upperLeftY) { AffineTransform at = null; try { at = getViewToMiniMapTransform(getMappedComponent()).createInverse(); } catch (NoninvertibleTransformException nite) { nite.printStackTrace(); return; } double [] srcPoints = new double[2]; double [] destPoints = new double[2]; srcPoints[0] = upperLeftX; srcPoints[1] = upperLeftY; at.transform(srcPoints, 0, destPoints, 0, 1); if (destPoints[0] < 0) { destPoints[0] = 0; } if (destPoints[1] < 0) { destPoints[1] = 0; } m_subject.getViewport().setViewPosition(new Point((int)destPoints[0], (int)destPoints[1])); } protected int [] getUpperLeftPointsFromCenter(int centerX, int centerY) { int [] upperLeft = new int[2]; Rectangle oldRectangle = getViewRectangle(); upperLeft[0] = centerX - oldRectangle.width/2; upperLeft[1] = centerY - oldRectangle.height/2; return upperLeft; } // Inner Classes public class MouseListener extends MouseAdapter implements MouseMotionListener { public void mousePressed(MouseEvent e) { int [] rectangleUpperLeft = getUpperLeftPointsFromCenter(e.getX(), e.getY()); scrollSubjectTo(rectangleUpperLeft[0], rectangleUpperLeft[1]); } public void mouseDragged(MouseEvent e) { int [] rectangleUpperLeft = getUpperLeftPointsFromCenter(e.getX(), e.getY()); scrollSubjectTo(rectangleUpperLeft[0], rectangleUpperLeft[1]); } public void mouseMoved(MouseEvent e) { // empty implementation } } class SubjectListener implements ChangeListener { public void stateChanged(ChangeEvent e) { repaint(); } } class MappedDrawingChangeListener implements DrawingChangeListener { /** * Sent when an area is invalid */ public void drawingInvalidated(DrawingChangeEvent e) { repaint(); } /** * Sent when the drawing wants to be refreshed */ public void drawingRequestUpdate(DrawingChangeEvent e) { repaint(); } /** * Sent when the drawing Title has changed */ public void drawingTitleChanged(DrawingChangeEvent e) { } }}