package com.jidesoft.swing; import javax.swing.*; import javax.swing.event.MouseInputAdapter; import javax.swing.event.MouseInputListener; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.geom.Area; import java.awt.image.BufferedImage; /** * Original code http://forums.java.net/jive/thread.jspa?forumID=73&threadID=14674 under "Do whatever you want with this * code" license */ @SuppressWarnings("serial") public class ScrollPaneOverview extends JComponent { private static final int MAX_SIZE = 400; private static final int MAX_SCALE = 20; private Component _owner; private JScrollPane _scrollPane; private Component _viewComponent; protected JPopupMenu _popupMenu; private BufferedImage _image; private Rectangle _startRectangle; private Rectangle _rectangle; private Point _startPoint; private double _scale; private int xOffset; private int yOffset; private Color _selectionBorder = Color.BLACK; public ScrollPaneOverview(JScrollPane scrollPane, Component owner) { _scrollPane = scrollPane; _owner = owner; _image = null; _startRectangle = null; _rectangle = null; _startPoint = null; _scale = 0.0; setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); MouseInputListener mil = new MouseInputAdapter() { // A new approach suggested by thomas.bierhance@sdm.de @Override public void mousePressed(MouseEvent e) { if (_startPoint != null) { Point newPoint = e.getPoint(); int deltaX = (int) ((newPoint.x - _startPoint.x) / _scale); int deltaY = (int) ((newPoint.y - _startPoint.y) / _scale); scroll(deltaX, deltaY); } _startPoint = null; _startRectangle = _rectangle; } @Override public void mouseMoved(MouseEvent e) { if (_startPoint == null) { _startPoint = new Point(_rectangle.x + _rectangle.width / 2, _rectangle.y + _rectangle.height / 2); } Point newPoint = e.getPoint(); moveRectangle(newPoint.x - _startPoint.x, newPoint.y - _startPoint.y); } }; addMouseListener(mil); addMouseMotionListener(mil); _popupMenu = new JPopupMenu(); _popupMenu.setLayout(new BorderLayout()); _popupMenu.add(this, BorderLayout.CENTER); } public void setSelectionBorderColor(Color selectionBorder) { _selectionBorder = selectionBorder; } public Color getSelectionBorder() { return _selectionBorder; } @Override protected void paintComponent(Graphics g) { if (_image == null || _rectangle == null) return; Graphics2D g2d = (Graphics2D) g; Insets insets = getInsets(); int xOffset = insets.left; int yOffset = insets.top; g.setColor(_scrollPane.getViewport().getView().getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); g.drawImage(_image, xOffset, yOffset, null); int availableWidth = getWidth() - insets.left - insets.right; int availableHeight = getHeight() - insets.top - insets.bottom; Area area = new Area(new Rectangle(xOffset, yOffset, availableWidth, availableHeight)); area.subtract(new Area(_rectangle)); g.setColor(new Color(255, 255, 255, 128)); g2d.fill(area); Color oldcolor = g.getColor(); g.setColor(_selectionBorder); g.drawRect(_rectangle.x, _rectangle.y, _rectangle.width, _rectangle.height); g.setColor(oldcolor); } @Override public Dimension getPreferredSize() { if (_image == null || _rectangle == null) return new Dimension(); Insets insets = getInsets(); return new Dimension(_image.getWidth(null) + insets.left + insets.right, _image.getHeight(null) + insets.top + insets.bottom); } public void display() { _viewComponent = _scrollPane.getViewport().getView(); if (_viewComponent == null) { return; } int maxSize = Math.max(MAX_SIZE, Math.max(_scrollPane.getWidth(), _scrollPane.getHeight()) / 2); int width = Math.min(_viewComponent.getWidth(), _scrollPane.getViewport().getWidth() * MAX_SCALE); if (width <= 0) { return; } int height = Math.min(_viewComponent.getHeight(), _scrollPane.getViewport().getHeight() * MAX_SCALE); if (height <= 0) { return; } double scaleX = (double) maxSize / width; double scaleY = (double) maxSize / height; _scale = Math.max(1.0 / MAX_SCALE, Math.min(scaleX, scaleY)); _image = new BufferedImage((int) (width * _scale), (int) (height * _scale), BufferedImage.TYPE_INT_RGB); Graphics2D g = _image.createGraphics(); // If the view is larger than the max scale allows only the the top left most part will now be painted // One solution would be paint only the part around the current position, but I can't get it to paint - Walter Laan. // note that without limiting the scale, the width/height will become zero (illegal for BufferedImage) // See CornerScrollerVisualTest in the test folder // g.setColor(_viewComponent.getBackground()); // g.fillRect(0, 0, _viewComponent.getWidth(), _viewComponent.getHeight()); // Point viewPosition = _scrollPane.getViewport().getViewPosition(); // xOffset = Math.max(0, viewPosition.x - (width / 2)); // yOffset = Math.max(0, viewPosition.y - (height / 2)); // g.translate(-xOffset, -yOffset); // g.setClip(0, 0, width, height); g.scale(_scale, _scale); g.setClip(xOffset, yOffset, width, height); /// {{{ Qian Qian 10/72007 boolean wasDoubleBuffered = _viewComponent.isDoubleBuffered(); try { if (_viewComponent instanceof JComponent) { ((JComponent) _viewComponent).setDoubleBuffered(false); } _viewComponent.paint(g); } finally { if (_viewComponent instanceof JComponent) { ((JComponent) _viewComponent).setDoubleBuffered(wasDoubleBuffered); } g.dispose(); } /// QianQian 10/7/2007 }}} _startRectangle = _scrollPane.getViewport().getViewRect(); Insets insets = getInsets(); _startRectangle.x = (int) (_scale * _startRectangle.x + insets.left); _startRectangle.y = (int) (_scale * _startRectangle.y + insets.right); _startRectangle.width *= _scale; _startRectangle.height *= _scale; _rectangle = _startRectangle; Point centerPoint = new Point(_rectangle.x + _rectangle.width / 2, _rectangle.y + _rectangle.height / 2); showPopup(-centerPoint.x, -centerPoint.y, _owner); } /** * Show popup at designated location. * <p/> * You could override this method to show the popup in different location. * * @param x the x axis pixel * @param y the y axis pixel * @param owner the owner of the popup */ protected void showPopup(int x, int y, Component owner) { _popupMenu.show(owner, x, y); } private void moveRectangle(int aDeltaX, int aDeltaY) { if (_startRectangle == null) return; Insets insets = getInsets(); Rectangle newRect = new Rectangle(_startRectangle); newRect.x += aDeltaX; newRect.y += aDeltaY; newRect.x = Math.min(Math.max(newRect.x, insets.left), getWidth() - insets.right - newRect.width); newRect.y = Math.min(Math.max(newRect.y, insets.right), getHeight() - insets.bottom - newRect.height); Rectangle clip = new Rectangle(); Rectangle.union(_rectangle, newRect, clip); clip.grow(2, 2); _rectangle = newRect; paintImmediately(clip); } private void scroll(int aDeltaX, int aDeltaY) { JComponent component = (JComponent) _scrollPane.getViewport().getView(); Rectangle rect = component.getVisibleRect(); rect.x += xOffset + aDeltaX; rect.y += yOffset + aDeltaY; component.scrollRectToVisible(rect); _popupMenu.setVisible(false); } }