views:

211

answers:

2

Hi, I'm trying to create a subclass of JButton or AbstractButton that would call specified .actionPerformed as long as the mouse is held down on the button.

So far I was thinking of extending JButton, adding a mouse listener on creation (inside constructor) and calling actionPerformed while the mouse is down. So far i came up with that but I was wondwering if I was on the right track and if so, how to correctly implement the "held down" logic.

package components;

import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;

public class HoldButton extends JButton {

    private class HeldDownMouseListener implements MouseListener {

        private boolean mouseIsHeldDown;

        private HoldButton button;

        private long millis;

        public HeldDownMouseListener(HoldButton button, long millis) {
            this.button = button;
            this.millis = millis;
        }

        @Override
        public void mouseClicked(MouseEvent arg0) { }

        @Override
        public void mouseEntered(MouseEvent arg0) { }

        @Override
        public void mouseExited(MouseEvent arg0) { }

        @Override
        public void mousePressed(MouseEvent arg0) {
            mouseIsHeldDown = true;
//          This should be run in a sub thread?
//          while (mouseIsHeldDown) {
//              button.fireActionPerformed(new ActionEvent(button, ActionEvent.ACTION_PERFORMED, "heldDown"));
//              try {
//                  Thread.sleep(millis);
//              } catch (InterruptedException e) {
//                  e.printStackTrace();
//                  continue;
//              }
//          }
        }

        @Override
        public void mouseReleased(MouseEvent arg0) {
            mouseIsHeldDown = false;
        }

    }

    public HoldButton() {
        addHeldDownMouseListener();
    }

    public HoldButton(Icon icon) {
        super(icon);
        addHeldDownMouseListener();
    }

    public HoldButton(String text) {
        super(text);
        addHeldDownMouseListener();
    }

    public HoldButton(Action a) {
        super(a);
        addHeldDownMouseListener();
    }

    private void addHeldDownMouseListener() {
        addMouseListener(new HeldDownMouseListener(this, 300));
    }

}

Thanks a lot for your time.

edit: Choosing the Timer method I came up with a working implementation:

package components;

import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;

public class HoldButton extends JButton {

    private static final long serialVersionUID = 1L;

    public static final long CLICK_LAG = 300;

    public static final long INITIAL_FIRE_DELAY = 500;

    public static final double FIRE_DELAY_STEP_MULTIPLIER = 25;

    public static final long MIN_FIRE_DELAY = 100;

    private class HeldDownMouseListener implements MouseListener {

        private class HeldDownCheckerTask extends TimerTask {

            private HeldDownMouseListener listener;

            public HeldDownCheckerTask(HeldDownMouseListener listener) {
                this.listener = listener;
            }

            @Override
            public void run() {
                long delay = INITIAL_FIRE_DELAY;

                while (listener.isMouseHeldDownOnButton()) {
                    listener.fireMouseHeldDown();

                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    if (delay > MIN_FIRE_DELAY) {
                        final long decrease = Math.round(FIRE_DELAY_STEP_MULTIPLIER * Math.pow(INITIAL_FIRE_DELAY / delay, 2));
                        delay = Math.max(delay - decrease, MIN_FIRE_DELAY);
                    }
                }
            }

        }

        private boolean mouseIsHeldDown;

        private boolean mouseIsOnButton;

        private HoldButton button;

        private Timer timer;

        public HeldDownMouseListener(HoldButton button) {
            this.button = button;
        }

        public boolean isMouseHeldDownOnButton() {
            return mouseIsHeldDown && mouseIsOnButton;
        }

        private void cancelTimer() {
            if (timer != null) {
                timer.cancel();
                timer = null;
            }
        }

        private void fireMouseHeldDown() {
            button.fireActionPerformed(new ActionEvent(button, ActionEvent.ACTION_PERFORMED, "heldDown"));
        }

        @Override
        public void mouseClicked(MouseEvent arg0) {
            cancelTimer();
        }

        @Override
        public void mouseEntered(MouseEvent arg0) {
            mouseIsOnButton = true;
        }

        @Override
        public void mouseExited(MouseEvent arg0) {
            mouseIsOnButton = false;
        }

        @Override
        public void mousePressed(MouseEvent arg0) {
            cancelTimer();
            mouseIsHeldDown = true;
            timer = new Timer();
            timer.schedule(new HeldDownCheckerTask(this), CLICK_LAG);
        }

        @Override
        public void mouseReleased(MouseEvent arg0) {
            mouseIsHeldDown = false;
        }

    }

    public HoldButton() {
        addHeldDownMouseListener();
    }

    public HoldButton(Icon icon) {
        super(icon);
        addHeldDownMouseListener();
    }

    public HoldButton(String text) {
        super(text);
        addHeldDownMouseListener();
    }

    public HoldButton(Action a) {
        super(a);
        addHeldDownMouseListener();
    }

    private void addHeldDownMouseListener() {
        addMouseListener(new HeldDownMouseListener(this));
    }

}
+1  A: 

When the mouse is pressed you could start a timer that invokes your action repeatedly at the interval you need until the button is let go. Then you can stop the timer. By submitting it to a timer, you can submit it to a new thread and not do the thread management yourself.

Jeff Storey
+1  A: 

At least one good practice is to do the 'work', in this case you actionPerformed-loop in a runnable via the SwingUtilies.invokeLater(). That way it gets offloaded to the event threadpool/queue and you're not blocking the awt main thread so you're not blocking your GUI.

momania