/* Copyright (c) 2006-2007 Timothy Wall, All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package furbelow;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.border.EmptyBorder;
/** Provide key-activated display of a Panner directly on a panned component.
* The panner may be given an absolute or relative size, and an arbitrary
* offset for placement relative to the parent component's edges. If the
* percentage size is less than 50%, the panner will appear in each corner
* in succession on successive key activations.
*/
public class PannerHandler {
public static final int PANNER_MASK =
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|InputEvent.SHIFT_MASK;
public static final long MOVE_TIMEOUT = 1000;
private JComponent target;
private Dimension size;
private Point offset;
private Panner panner;
private long lastHidden;
private int corner = UL;
private boolean affixed;
private int percent = 100;
private float transparency = 0.9f;
private boolean preserveAspect = true;
private static final int CENTER = -1;
private static final int UL = 0;
private static final int UR = 1;
private static final int LR = 2;
private static final int LL = 3;
public PannerHandler(JComponent target, Dimension size) {
this(target, size, 100, new Point(0, 0));
}
public PannerHandler(JComponent target, int percent) {
this(target, percent, new Point(0, 0));
}
public PannerHandler(JComponent target, int percent, Point offset) {
this(target, null, percent, offset);
}
public PannerHandler(JComponent target, Dimension size,
int percent, Point offset) {
this.target = target;
this.size = size;
this.percent = percent;
this.offset = offset;
this.corner = percent < 50 ? UL : CENTER;
target.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (!affixed) {
int modifiers = e.getModifiers();
if ((modifiers & PANNER_MASK) == PANNER_MASK) {
if (panner == null || !panner.isAttached())
show(false);
}
}
}
public void keyReleased(KeyEvent e) {
if (!affixed) {
hide();
}
}
});
ComponentListener listener = new ComponentAdapter() {
private void refresh() {
if (panner != null && panner.isShowing())
show(affixed);
}
public void componentResized(ComponentEvent e) {
refresh();
}
public void componentMoved(ComponentEvent e) {
refresh();
}
};
target.addComponentListener(listener);
if (target.getParent() instanceof JViewport) {
target.getParent().addComponentListener(listener);
}
}
/** Display the panner until {@link #hide} is called. */
public void show() {
show(true);
}
private void show(boolean fixed) {
this.affixed = fixed;
if (panner == null) {
panner = new Panner(target);
panner.setTransparency(transparency);
panner.setPreserveAspect(preserveAspect);
}
attach(panner, !fixed);
}
public void setPreserveAspect(boolean preserve) {
this.preserveAspect = preserve;
if (panner != null)
panner.setPreserveAspect(preserve);
}
public void setTransparency(float t) {
this.transparency = t;
if (panner != null)
panner.setTransparency(t);
}
private void attach(Panner panner, boolean nextCorner) {
Rectangle visible = target.getVisibleRect();
Dimension sz = size;
if (sz == null) {
sz = new Dimension(visible.width, visible.height);
sz.width = sz.width * percent / 100;
sz.height = sz.height * percent / 100;
}
panner.setSize(sz);
if (corner != CENTER && nextCorner) {
if (System.currentTimeMillis() - lastHidden < MOVE_TIMEOUT) {
corner = (corner + 1) & 0x3;
}
else {
corner = 0;
}
}
int x = visible.x;
int y = visible.y;
switch(corner) {
case UL:
x += offset.x;
y += offset.y;
break;
case UR:
x += visible.width - offset.x - panner.getPreferredSize().width;
y += offset.y;
break;
case LR:
x += visible.width - offset.x - panner.getPreferredSize().width;
y += visible.height - offset.y - panner.getPreferredSize().height;
break;
case LL:
x += offset.x;
y += visible.height - offset.y - panner.getPreferredSize().height;
break;
case CENTER:
default:
x += (visible.width - panner.getPreferredSize().width)/2;
y += (visible.height - panner.getPreferredSize().height)/2;
break;
}
panner.attach(x, y);
panner.revalidate();
panner.repaint();
}
public void hide() {
this.affixed = false;
if (panner != null) {
lastHidden = System.currentTimeMillis();
panner.detach();
panner = null;
}
}
/** @return whether the panner is currently visible. */
public boolean isShowing() {
return panner != null;
}
/** Demo. */
public static void main(String[] args) {
try {
JFrame frame = new JFrame("Panner");
JPanel p = (JPanel)frame.getContentPane();
URL url = PannerHandler.class.getResource("desert.jpg");
String key = System.getProperty("os.name").toLowerCase().indexOf("mac") != -1
? "\u2318" : "control";
JLabel label = new JLabel("Press " + key +
"+shift to display panner, click/drag to navigate");
label.setBorder(new EmptyBorder(4,4,4,4));
p.add(label, BorderLayout.NORTH);
JLabel image = new JLabel(new ImageIcon(url));
image.setFocusable(true);
PannerHandler handler = new PannerHandler(image, 95, new Point(0, 0));
image.putClientProperty("panner", handler);
p.add(new JScrollPane(image));
frame.pack();
Dimension size = image.getPreferredSize();
size.width /= 2;
size.height /= 2;
Dimension ssize = frame.getToolkit().getScreenSize();
frame.setLocation((ssize.width-size.width)/2, (ssize.height-size.height)/2);
frame.setSize(size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
image.requestFocus();
}
catch(Exception e) {
e.printStackTrace();
}
}
}