Forum Stats

  • 3,727,123 Users
  • 2,245,325 Discussions
  • 7,852,604 Comments

Discussions

JList where mouse click acts like ctrl-mouse click

843807
843807 Member Posts: 46,582
edited April 2010 in Swing
I'd like to create a JList where I can select multiple items as if the control key were down, but without having the control key down. I thought that perhaps I could do this by extending JList and overriding its processMouseEvent method, setting the MouseEvent object to think that control is pressed and then calling the super method like so, but it doesn't work (I still need to press the control key to get my desired effect):
import java.awt.Component;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;

import javax.swing.*;

public class Ctrl_Down_JList {
  private static void createAndShowUI() {
    String[] items = {"Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"};
    
    JList myJList = new JList(items) {
      @Override
      protected void processMouseEvent(MouseEvent e) {
        
        // change the modifiers to believe that control key is down
        int modifiers = e.getModifiers() | InputEvent.CTRL_DOWN_MASK;
        
        // can I use this anywhere?  I don't see how to change the 
        // modifiersEx of the MouseEvent
        int modifiersEx = e.getModifiersEx() | InputEvent.CTRL_DOWN_MASK;
        
        MouseEvent myME = new MouseEvent(
            (Component) e.getSource(), 
            e.getID(), 
            e.getWhen(), 
            modifiers, // my changed modifier
            e.getX(), 
            e.getY(), 
            e.getXOnScreen(), 
            e.getYOnScreen(), 
            e.getClickCount(), 
            e.isPopupTrigger(), 
            e.getButton());
        super.processMouseEvent(myME);
      }
    };
    
    JFrame frame = new JFrame("Ctrl_Down_JList");
    frame.getContentPane().add(new JScrollPane(myJList));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  public static void main(String[] args) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        createAndShowUI();
      }
    });
  }
}
Any ideas would be much appreciated!

Comments

  • 843807
    843807 Member Posts: 46,582
    edited March 2010
    Any ideas would be much appreciated!
    Here is a hack, idea is same as yours, but using Robot:
    public static void createAndShowUI2() {
    	String[] items = { "Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat" };
    
    	final JList myJList = new JList(items);
    	myJList.addMouseListener(new MouseAdapter() {
    		Robot robot;
    		{
    			try {
    				robot = new Robot();
    			} catch (AWTException ex) {
    				ex.printStackTrace();
    			}
    		}
    
    		@Override
    		public void mouseEntered(MouseEvent e) {
    			if (robot != null)
    				robot.keyPress(KeyEvent.VK_CONTROL);
    		}
    
    		@Override
    		public void mouseExited(MouseEvent e) {
    			if (robot != null)
    				robot.keyRelease(KeyEvent.VK_CONTROL);
    		}
    	});
    	JFrame frame = new JFrame("Ctrl_Down_JList");
    	frame.getContentPane().add(new JScrollPane(myJList));
    	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	frame.pack();
    	frame.setLocationRelativeTo(null);
    	frame.setVisible(true);
    }
    It does work, but there has to be some cleaner way of doing this, perhaps using SelectionModel but I don't know about it.

    Thanks!

    Edit: I found that if Control key is pressed manually, then this hack obviously, to handle this, it has to made dirtier:
    myJList.addMouseListener(new MouseAdapter() {
    	Robot robot;
    	{
    		try {
    			robot = new Robot();
    		} catch (AWTException ex) {
    			ex.printStackTrace();
    		}
    	}
    	Timer timer = new Timer(20, new ActionListener() {
    		@Override
    		public void actionPerformed(ActionEvent e) {
    			if (robot != null)
    				robot.keyPress(KeyEvent.VK_CONTROL);
    		}
    	});
    	
            @Override
    	public void mouseEntered(MouseEvent e) {
    		if (robot != null) {
    			robot.keyPress(KeyEvent.VK_CONTROL);
    			timer.start();
    		}
    	}
    	
            @Override
    	public void mouseExited(MouseEvent e) {
    		if (robot != null) {
    			robot.keyRelease(KeyEvent.VK_CONTROL);
    			timer.stop();
    		}
    	}
    });
    Edited by: T.B.M on Mar 13, 2010 10:06 PM
  • darrylburke
    darrylburke Member Posts: 18,007
    edited March 2010
    JList's processMouseEvent(...) is inherited form JComponent and doesn't handle selection. That's done in BasicListUI.Handler#mouseClicked(...).

    Private classes, private methods ... I'm thinking it might be easier (or not) to implement this via a custom ListSelectionModel.

    db

    edit Try this:
    import javax.swing.*;
    
    public class MultiSelectListDemo {
    
      public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
    
          @Override
          public void run() {
            new MultiSelectListDemo().makeUI();
          }
        });
      }
    
      public void makeUI() {
        String[] items = {"Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"};
        final JList list = new JList(items);
        list.setSelectionModel(new MultiListSelectionModel());
    
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null);
        frame.add(new JScrollPane(list));
        frame.setVisible(true);
      }
    }
    
    class MultiListSelectionModel extends DefaultListSelectionModel {
    
      @Override
      public void setSelectionInterval(int index0, int index1) {
        if (index0 != index1) {
          super.setSelectionInterval(index0, index1);
          return;
        }
        if (isSelectedIndex(index0)) {
          super.removeSelectionInterval(index0, index1);
        } else {
          super.addSelectionInterval(index0, index1);
        }
      }
    }
    Edited by: DarrylBurke
  • 843807
    843807 Member Posts: 46,582
    DarrylBurke wrote:
    JList's processMouseEvent(...) is inherited form JComponent and doesn't handle selection. That's done in BasicListUI.Handler#mouseClicked(...).
    I didn't know this, but it makes sense and explains quite a bit of why it's not working. Thanks.
    Private classes, private methods ... I'm thinking it might be easier (or not) to implement this via a custom ListSelectionModel.
    I'll look into this. Thanks again Darryl

    /Pete
  • 843807
    843807 Member Posts: 46,582
    edited March 2010
    The problem is you are using the wrong mask when doing your hack.

    InputEvent.CTRL_DOWN_MASK is used by ModifiersEx while InputEvent.CTRL_MASK is used by plain old Modifiers, sent to the MouseEvent constructor (for backwards compatibility). Change
    int modifiers = e.getModifiers() | InputEvent.CTRL_DOWN_MASK;
    to
    int modifiers = e.getModifiers() | InputEvent.CTRL_MASK;
    and it works.


    Edit: it also seems to work if you simply pass in the modifiersEx instead of your modifiers variable.
  • darrylburke
    darrylburke Member Posts: 18,007
    and it works.
    An honest question, not a nitpick. Wouldn't that depend on processMouseEvent being executed before BasicListUI.Handler#mouseClicked(...) ? And is that behavior documented somewhere, or at least possible to infer from the API?

    db
  • 843807
    843807 Member Posts: 46,582
    DarrylBurke wrote:
    and it works.
    An honest question, not a nitpick. Wouldn't that depend on processMouseEvent being executed before BasicListUI.Handler#mouseClicked(...) ? And is that behavior documented somewhere, or at least possible to infer from the API?

    db
    It is my understanding that JComponent.processMouseEvent notifies all the listeners. As to whether the behavior is documented - don't know but it would probably break a lot of people's code if it were to be implemented differently somehow. I've overriden processMouseEvent to filter out events I don't want the UI to process for instance.
  • darrylburke
    darrylburke Member Posts: 18,007
    Thanks, Aephyr.

    db
  • 843807
    843807 Member Posts: 46,582
    Aephyr wrote:
    The problem is you are using the wrong mask when doing your hack.
    That's it! Thanks!

    I'm out of Dukes stars, but if you want some, I'll figure out a way to get you some. Again, thanks!
  • kleopatra-JavaNet
    kleopatra-JavaNet Member Posts: 1,105 Gold Badge
    hope this thread is not regarded as so old that a comment is regarded as resurrection <g>
    DarrylBurke wrote:
    An honest question, not a nitpick. Wouldn't that depend on processMouseEvent being executed before BasicListUI.Handler#mouseClicked(...) ? And is that behavior documented somewhere, or at least possible to infer from the API?
    Well, I think it's documented: all processXXEvent methods are documented as dispatching the XXEvents as they deem appropriate, f.i to registered XXlisteners. So it's one level-of-control above the listeners - which is what all ui-delegate installed Handlers are.

    CU
    Jeanette
  • darrylburke
    darrylburke Member Posts: 18,007
    edited April 2010
    Thanks Jeanette, I had gathered that from looking at the sources.

    And since this has already been resurrected, I'll hijack it to ask a question that's been on my mind: which would you consider the 'cleaner' way to achiever the desired result: a customized selection model or a synthetic mouse event? Or is it an even draw?

    db
  • kleopatra-JavaNet
    kleopatra-JavaNet Member Posts: 1,105 Gold Badge
    Darryl,

    not sure if it's possible to do completely on the selection model level - once tried on SwingX JXDatePicker without success, felt like not enough information at the time of the setSelection to decide what exactly to do. So dropped the idea without further digging ;-)

    CU
    Jeanette
  • darrylburke
    darrylburke Member Posts: 18,007
    not sure if it's possible to do completely on the selection model level
    Isn't that what I showed at #2? or what did I miss this time?

    Thanks, db
  • kleopatra-JavaNet
    kleopatra-JavaNet Member Posts: 1,105 Gold Badge
    edited April 2010
    could be - didn't try your code snippet :-) Just saying that from my experience it turned out to be a dead end. Forgot the details, though.

    CU
    Jeanette
  • kleopatra-JavaNet
    kleopatra-JavaNet Member Posts: 1,105 Gold Badge
    after some sleep: the answer to the "cleaner" might be "depends" - on what exactly is the requirement. The base question is if you want to change the overall behaviour of the selectionModel's setSelectionInterval or if you want to change the effect of individual user gestures. Doing the first will effect all clients - ui delegates in their key and mouseListeners plus all application code which programatically sets the selection. Personally, would stick to plug into the event level until it turns out that I really need a generally changed model behaviour. Which might be violating super's contract (or not, too lazy to check right now :-)

    CU
    Jeanette
This discussion has been closed.