Consider the MouseListener below. My question is this: is having the extra features that this listener provides, some of which you won’t need, worth the memory and processing overhead that comes with having these features? Or should “verbose” implementations like this be avoided?
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.Timer;
/**
* This is an overkill class that is useful for distinguishing between buttons and includes functions for hold and double-click events.
*
* @author Paranoid Android
*/
public class ParanoidMouseListener extends MouseAdapter {
public static final int LEFT = MouseEvent.BUTTON1;
public static final int MIDDLE = MouseEvent.BUTTON2;
public static final int RIGHT = MouseEvent.BUTTON3;
private DoubleClickTimer leftDouble = new DoubleClickTimer();
private DoubleClickTimer middleDouble = new DoubleClickTimer();
private DoubleClickTimer rightDouble = new DoubleClickTimer();
private MouseEvent event;
private int pressedButton;
private Component pressed;
private boolean dragging;
/**
* This method allows methods to ignore the MouseEvent when not needed.
*
* @return the latest mouse event.
*/
public MouseEvent getEvent() {
return event;
}
private HoldTimer leftHold = new HoldTimer() {
@Override
public void perform() {
onLeftHold();
}
};
private HoldTimer middleHold = new HoldTimer() {
@Override
public void perform() {
onMiddleHold();
}
};
private HoldTimer rightHold = new HoldTimer() {
@Override
public void perform() {
onRightHold();
}
};
@Override
public final void mouseClicked(MouseEvent event) {
this.event = event;
switch (event.getButton()) {
case LEFT:
if (leftDouble.isRunning()) {
leftDouble.stop();
onLeftDoubleClick();
} else {
leftDouble.start();
onPureLeftClick();
}
break;
case MIDDLE:
if (middleDouble.isRunning()) {
middleDouble.stop();
onMiddleDoubleClick();
} else {
middleDouble.start();
onPureMiddleClick();
}
break;
case RIGHT:
if (rightDouble.isRunning()) {
rightDouble.stop();
onRightDoubleClick();
} else {
rightDouble.start();
onPureRightClick();
}
break;
}
}
@Override
public final void mousePressed(MouseEvent event) {
this.event = event;
pressedButton = event.getButton();
pressed = event.getComponent();
switch (event.getButton()) {
case LEFT:
leftHold.start();
onLeftPress();
break;
case MIDDLE:
middleHold.start();
onMiddlePress();
break;
case RIGHT:
rightHold.start();
onRightPress();
break;
}
}
@Override
public final void mouseReleased(MouseEvent event) {
this.event = event;
pressedButton = -1;
Component src = event.getComponent();
boolean contains = src.contains(event.getPoint());
switch (event.getButton()) {
case LEFT:
leftHold.stop();
onLeftRelease();
if (!dragging && src == pressed && contains) onLeftClick();
break;
case MIDDLE:
middleHold.stop();
onMiddleRelease();
if (!dragging && src == pressed && contains) onMiddleClick();
break;
case RIGHT:
rightHold.stop();
onRightRelease();
if (!dragging && src == pressed && contains) onRightClick();
break;
}
dragging = false;
}
@Override
public final void mouseMoved(MouseEvent event) {
this.event = event;
moved();
}
@Override
public final void mouseDragged(MouseEvent event) {
this.event = event;
dragging = true;
switch (pressedButton) {
case LEFT:
onLeftDrag();
break;
case MIDDLE:
onMiddleDrag();
break;
case RIGHT:
onRightDrag();
break;
}
}
@Override
public final void mouseEntered(MouseEvent event) {
this.event = event;
entered();
}
@Override
public final void mouseExited(MouseEvent event) {
this.event = event;
exited();
}
private int getDoubleClickInterval() {
String property = "awt.multiClickInterval";
return (int) Toolkit.getDefaultToolkit().getDesktopProperty(property);
}
private class DoubleClickTimer extends Timer {
public DoubleClickTimer() {
super(getDoubleClickInterval(), null);
this.setRepeats(false);
}
}
public int getHoldInitialDelay() {
return 300;
}
public int getHoldQueueDelay() {
return 60;
}
private class HoldTimer extends Timer {
public HoldTimer() {
super(getHoldQueueDelay(), null);
this.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
perform();
}
});
this.setInitialDelay(getHoldInitialDelay());
}
public void perform() {
}
}
public void moved() {
}
public void entered() {
}
public void exited() {
}
public void onLeftHold() {
}
public void onMiddleHold() {
}
public void onRightHold() {
}
public void onLeftClick() {
}
public void onMiddleClick() {
}
public void onRightClick() {
}
public void onPureLeftClick() {
}
public void onPureMiddleClick() {
}
public void onPureRightClick() {
}
public void onLeftDoubleClick() {
}
public void onMiddleDoubleClick() {
}
public void onRightDoubleClick() {
}
public void onLeftPress() {
}
public void onMiddlePress() {
}
public void onRightPress() {
}
public void onLeftRelease() {
}
public void onMiddleRelease() {
}
public void onRightRelease() {
}
public void onLeftDrag() {
}
public void onMiddleDrag() {
}
public void onRightDrag() {
}
}
As Hovercraft Full Of Eels points out in the comments, this is a classic case of You Aren’t Gonna Need It. Implementing functionality before you have a clear notion of who will use it and when is generally a no-no. In this situation, considering the use cases you outlined in the comments, you have several options:
Use this class everywhere and accept the marginally higher overhead. Odds are you don’t much care about the performance implications, which are likely to be very small. However, this does introduce a greater dependency on this class throughout the rest of your code, meaning if you introduce a regression at a later date, you’re in risk of breaking a large number of related systems.
Allow consumers of the class to indicate which features they will use (e.g., double clicking) and disable features that the consumer does not want. This introduces complexity into your class and makes it more likely to be buggy, as well as making testing more difficult (though hardly impossible). If consistency is badly needed between classes, this may be an option.
Use this class when the added functionality is needed, and use an ordinary
MouseAdapterelsewhere. This is probably your best option, especially if certain behavior cases are not well-defined in your custom class. This reduces dependency on your class and simplifies the class internally as well. The trade-off is less consistency in how mouse interaction is handled between consumer classes, and slightly more code in order to implement aMouseAdapterfor consumers – generally a worthwhile trade-off.