package com.jakeapp.gui.swing.controls; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.MouseInputListener; import java.awt.*; import java.awt.event.MouseEvent; /** * A border that takes care of button-like behavior. Simply override * buttonActivated to supply your own behavior. Use isArmed in paintBorder * to change your appearance based on whether or not the button is "depressed". */ public abstract class InteractiveBorder extends EmptyBorder implements MouseInputListener { private boolean activateOnPress = false; private boolean armed = false; private boolean onLeft; private int sideLength; private JComponent component; /** * We assume that the addition to the border is square, and can only go * on the left or right. * @param sideLength * @param onLeft */ InteractiveBorder(int sideLength, boolean onLeft) { super(makeInsetsForPosition(sideLength, onLeft)); this.sideLength = sideLength; this.onLeft = onLeft; } /** * Sets whether you want menu-like behavior (true) or button-like * behavior (false). The default is false, and buttonActivated is * only invoked after a press and release, both on-button. * @param newState */ public void setActivateOnPress(boolean newState) { this.activateOnPress = newState; } /** * Attaches this border to the given component, using a CompoundBorder. * You can add both a left and a right border to a component by invoking * this once on each border with the same component argument. * @param c */ public void attachTo(JComponent c) { this.component = c; c.setBorder(BorderFactory.createCompoundBorder(c.getBorder(), this)); c.addMouseListener(this); c.addMouseMotionListener(this); } /** * Invoked when the button is activated; what 'activated' means depends * on whether or not this border is set to activate on press. * @param e */ public abstract void buttonActivated(MouseEvent e); /** * Tests whether the 'button' this border represents is armed. Armed means * that the user has pressed the mouse button over us, but not yet * released the mouse button. * <p/> * You can call this from paintBorder to change your appearance (buttons * tend to depress or look darker, for example, when armed). * @return */ public boolean isArmed() { return armed; } private static Insets makeInsetsForPosition(int sideLength, boolean left) { if (left) { return new Insets(0, sideLength, 0, 0); } else { return new Insets(0, 0, 0, sideLength); } } private boolean isOverButton(MouseEvent e) { // If the button is down, we might be outside the component // without having had mouseExited invoked. if (!component.contains(e.getPoint())) { return false; } // If the mouse wasn't in the border, it can't be over a button. Rectangle interior = SwingUtilities.calculateInnerArea(component, null); if (interior.contains(e.getPoint())) { return false; } if (onLeft && e.getX() < sideLength) { return true; } else if (!onLeft && e.getX() > component.getWidth() - sideLength) { return true; } return false; } public void mouseDragged(MouseEvent e) { arm(e); } public void mouseEntered(MouseEvent e) { arm(e); } public void mouseExited(MouseEvent e) { disarm(); } public void mousePressed(MouseEvent e) { if (activateOnPress && isInterestingEvent(e)) { buttonActivated(e); } arm(e); } public void mouseReleased(MouseEvent e) { if (armed) { buttonActivated(e); } disarm(); } public void mouseMoved(MouseEvent e) { // Who cares? } public void mouseClicked(MouseEvent e) { // Nothing to do: mouseReleased has already taken care of everything. } private boolean isInterestingEvent(MouseEvent e) { return (isOverButton(e) && SwingUtilities.isLeftMouseButton(e)); } private void arm(MouseEvent e) { setArmed(isInterestingEvent(e)); } private void disarm() { setArmed(false); } private void setArmed(boolean newState) { if (activateOnPress) { // If we activate on a button press, there can be no notion of // being armed. return; } armed = newState; component.repaint(); } }