package com.ebenius.widget;
import static java.awt.AWTEvent.MOUSE_EVENT_MASK;
import static java.awt.AWTEvent.MOUSE_MOTION_EVENT_MASK;
import static java.awt.event.InputEvent.BUTTON3_DOWN_MASK;
import static java.awt.event.MouseEvent.MOUSE_DRAGGED;
import static java.awt.event.MouseEvent.MOUSE_PRESSED;
import static java.awt.event.MouseEvent.MOUSE_RELEASED;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
public class JXScrollPane extends JScrollPane {
private static final long serialVersionUID = 4240385570313149664L;
private static ToolkitMouseListener multiListener = null;
private Point draggingFrom;
private int viewDragModifierMask = BUTTON3_DOWN_MASK;
private boolean horizontalViewDraggingEnabled = true;
private boolean verticalViewDraggingEnabled = true;
public JXScrollPane() {
super();
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
public JXScrollPane(Component view, int vsbPolicy, int hsbPolicy) {
super(view, vsbPolicy, hsbPolicy);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
public JXScrollPane(Component view) {
super(view);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
public JXScrollPane(int vsbPolicy, int hsbPolicy) {
super(vsbPolicy, hsbPolicy);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
protected boolean isViewDraggable(MouseEvent e) {
final int mask = viewDragModifierMask;
return isViewDraggingEnabled() && (mask & e.getModifiersEx()) == mask;
}
protected boolean viewportContainsPoint(Point point) {
return getViewport().getBounds().contains(point);
}
protected void startDraggingView(MouseEvent e) {
draggingFrom = e.getPoint();
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
multiListener.setCurrentDraggingSlave(this);
}
protected void stopDraggingView(MouseEvent e) {
multiListener.setCurrentDraggingSlave(null);
draggingFrom = null;
setCursor(null);
}
protected void dragView(MouseEvent e) {
final JViewport viewport = getViewport();
final Component view = viewport.getView();
final Point from;
if ((from = draggingFrom) == null || view == null) {
return;
}
final Point to = e.getPoint();
int dx = isHorizontalViewDraggingEnabled() ? from.x - to.x : 0;
int dy = isVerticalViewDraggingEnabled() ? from.y - to.y : 0;
final Point viewPos = viewport.getViewPosition();
final Dimension viewSize = viewport.getViewSize();
final Dimension extentSize = viewport.getExtentSize();
final int maxViewPosX = viewSize.width - extentSize.width;
final int maxViewPosY = viewSize.height - extentSize.height;
viewPos.x = Math.max(0, Math.min(viewPos.x + dx, maxViewPosX));
viewPos.y = Math.max(0, Math.min(viewPos.y + dy, maxViewPosY));
viewport.setViewPosition(viewPos);
viewport.repaint();
draggingFrom = to;
}
@Override
protected void processMouseEvent(MouseEvent e) {
super.processMouseEvent(e);
processDragEvent(e);
}
@Override
protected void processMouseMotionEvent(MouseEvent e) {
super.processMouseMotionEvent(e);
processDragEvent(e);
}
protected void processDragEvent(MouseEvent e) {
switch (e.getID()) {
case MOUSE_DRAGGED:
if (draggingFrom != null && !e.isConsumed() && isViewDraggable(e)) {
dragView(e);
}
break;
case MOUSE_RELEASED:
stopDraggingView(e);
break;
case MOUSE_PRESSED:
if (!e.isConsumed()
&& isViewDraggable(e)
&& viewportContainsPoint(e.getPoint())) {
startDraggingView(e);
}
break;
}
}
@Override
public void addNotify() {
synchronized (ToolkitMouseListener.class) {
if (multiListener == null) {
multiListener = new ToolkitMouseListener();
}
multiListener.addSlave(this);
}
super.addNotify();
}
@Override
public void removeNotify() {
super.removeNotify();
synchronized (ToolkitMouseListener.class) {
if (multiListener != null && !multiListener.removeSlave(this)) {
multiListener = null;
}
}
}
public int getViewDragModifierMask() {
return viewDragModifierMask;
}
public void setViewDragModifierMask(int modifierMask) {
final int old = this.viewDragModifierMask;
this.viewDragModifierMask = modifierMask;
firePropertyChange("viewDragModifierMask",
old, viewDragModifierMask);
}
public boolean isViewDraggingEnabled() {
return horizontalViewDraggingEnabled || verticalViewDraggingEnabled;
}
public boolean isHorizontalViewDraggingEnabled() {
return horizontalViewDraggingEnabled;
}
public void setHorizontalViewDraggingEnabled(boolean enabled) {
final boolean old = this.horizontalViewDraggingEnabled;
this.horizontalViewDraggingEnabled = enabled;
firePropertyChange("horizontalViewDraggingEnabled",
old, horizontalViewDraggingEnabled);
}
public boolean isVerticalViewDraggingEnabled() {
return verticalViewDraggingEnabled;
}
public void setVerticalViewDraggingEnabled(boolean enabled) {
final boolean old = this.verticalViewDraggingEnabled;
this.verticalViewDraggingEnabled = enabled;
firePropertyChange("horizontalViewDraggingEnabled",
old, verticalViewDraggingEnabled);
}
private static class ToolkitMouseListener implements AWTEventListener {
private final Set<JXScrollPane> slaves = new HashSet<JXScrollPane>();
private JXScrollPane currentDraggingSlave = null;
public ToolkitMouseListener() {}
public void eventDispatched(AWTEvent e) {
final JXScrollPane slave = currentDraggingSlave;
switch (e.getID()) {
case MOUSE_DRAGGED:
if (slave != null) {
slave.processDragEvent(convertMouseEvent((MouseEvent) e, slave));
}
break;
case MOUSE_RELEASED:
if (slave != null) {
slave.stopDraggingView(convertMouseEvent((MouseEvent) e, slave));
}
break;
case MOUSE_PRESSED:
redispatchDragEvent((MouseEvent) e);
break;
default:
return;
}
}
private void redispatchDragEvent(MouseEvent e) {
final Component src = (Component) e.getSource();
for (Component c = src.getParent(); c != null; c = c.getParent()) {
if (c instanceof JXScrollPane) {
((JXScrollPane) c).processDragEvent(convertMouseEvent(e, c));
break;
}
}
}
private MouseEvent convertMouseEvent(
MouseEvent e,
Component targetComponent) {
return SwingUtilities.convertMouseEvent((Component) e.getSource(), e,
targetComponent);
}
void addSlave(JXScrollPane slave) {
synchronized (ToolkitMouseListener.class) {
slaves.add(slave);
if (slaves.size() == 1) {
slave.getToolkit().addAWTEventListener(this, MOUSE_EVENT_MASK);
}
}
}
boolean removeSlave(JXScrollPane slave) {
synchronized (ToolkitMouseListener.class) {
slaves.remove(slave);
final boolean hasMoreSlaves = !slaves.isEmpty();
if (!hasMoreSlaves) {
slave.getToolkit().removeAWTEventListener(this);
}
return hasMoreSlaves;
}
}
void setCurrentDraggingSlave(JXScrollPane slave) {
synchronized (ToolkitMouseListener.class) {
final JXScrollPane oldSlave = currentDraggingSlave;
final Toolkit tk;
long mask;
if (slave != null) {
tk = slave.getToolkit();
mask = MOUSE_EVENT_MASK | MOUSE_MOTION_EVENT_MASK;
} else if (oldSlave != null) {
tk = oldSlave.getToolkit();
mask = MOUSE_EVENT_MASK;
} else {
return;
}
tk.removeAWTEventListener(this);
tk.addAWTEventListener(this, mask);
this.currentDraggingSlave = slave;
}
}
}
}