Mouse events on an SWT Scrollbar - java

Using standalone SWT Scrollbars is something of a hack (using this workaround), but it can be done. Here's a snippet:
ScrolledComposite scrolledComposite = new ScrolledComposite(
parent, SWT.V_SCROLL);
ScrollBar scrollbar = scrolledComposite.getVerticalBar();
Shell tip = new Shell(UserInterface.getShell(), SWT.ON_TOP
| SWT.NO_FOCUS | SWT.TOOL);
// ..stylize and fill the tooltip..
Now what I'm trying to do is monitor when the user is interacting with the scrollbar. In particular, I want to know when the user is dragging the scrollbar—and when it has been released—in order to display an Office 2007-style tooltip revealing which page the position of the scrollbar corresponds with.
Presently, I have the following code which displays the tooltip:
scrollbar.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent event) {}
public void widgetSelected(SelectionEvent event) {
tip.setVisible(true);
}
}
It would seem logical then to have the tooltip disappear when the mouse button is released:
scrollbar.addListener(SWT.MouseUp, new Listener() {
public void handleEvent(Event event) {
tip.setVisible(false);
}
});
However, neither scrollbar nor scrolledComposite seem to respond to the SWT.MouseUp event when the user interacts with the scrollbar.
I presently have a workaround that hides the tip after a timeout, but I'm not satisfied with this. Any insights would be most appreciated!

Scrollbar's javadoc said this:
When widgetSelected is called, the
event object detail field contains one
of the following values: SWT.NONE -
for the end of a drag. SWT.DRAG.
SWT.HOME. SWT.END. SWT.ARROW_DOWN.
SWT.ARROW_UP. SWT.PAGE_DOWN.
SWT.PAGE_UP. widgetDefaultSelected is
not called.
So my suggestion is get your tooltip to appear and disappear is to check for the event.detail type.
public void widgetSelected(SelectionEvent event) {
tip.setVisible(event.detail != SWT.NONE);
}

scrollBar.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent e) {
}
public void widgetSelected(SelectionEvent e) {
if (e.detail == SWT.NONE) {
// end of drag
System.out.println("Drag end");
}
else if (e.detail == SWT.DRAG) {
// drag
System.out.println("Currently dragging");
}
}
});
Hope this will help you... But I can see a problem with mousewheel use that throws multiple drag end events...

Paul,
try using addMouseEvent method from a Scrollable object. For example:
Scrollable scrollable = scrollbar.getParent();
scrollable.addMouseListener(new MouseListener () {
void mouseDoubleClick(MouseEvent e) { ... }
void mouseDown(MouseEvent e) { ... }
void mouseUp(MouseEvent e) { ... }
});
Actually, I don't know if this approach will work. But, it's an attempt.
Good luck!

Related

Higlight text in JTextField but only when tabbing

I want to create a JDialog where the text in the textfields is selected but only if the focus is gained from keyboard (TAB, CTRL+TAB). I have found several topics on this matter but had problems with implementing it.
Here is one which I was trying.
And my code:
public class Dialogg extends JDialog implements FocusListener, MouseListener {
private boolean focusFromMouse = false;
public Dialogg() {
JTextField tf1 = new JTextField("text1");
JTextField tf2 = new JTextField("text2");
tf1.addMouseListener(this);
tf2.addMouseListener(this);
tf1.addFocusListener(this);
tf2.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
if (!focusFromMouse) {
JTextField tf = (JTextField) e.getComponent();
tf.selectAll();
focusFromMouse = true;
}
}
#Override
public void focusLost(FocusEvent e) {
focusFromMouse = false;
}
#Override
public void mouseClicked(MouseEvent e) {
focusFromMouse = true;
}
}
It does not work as intended, it does not matter what is focus source the text always highlights. When I run the code and follow it step by step it turns out that focusGained code happens before mouseClicked code so the flag is not reset when it should. Any hints?
EDIT:
As suggested by M. Prokhorov I have deleted less relevant (for the question) lines from the code.Thank you.
EDIT 2:
I am trying to wrap focus listener as suggested by camickr. It looks like this now:
tf1.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent evt){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if (!focusFromMouse){
tf1.selectAll();
focusFromMouse=true;
}
}
});
}
public void focusLost(FocusEvent evt){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
focusFromMouse=false;
}
});
}
});
public void mouseClicked(MouseEvent e) {
focusFromMouse=true;
I am printing line after each event to see the action order and still mouseClicked happens last. What am I doing wrong?
EDIT 3:
OK, I have found a solution which fulfils requirements of my simple Dialog.
I could not find a way of doing this with use of invokeLater or EventQueue. Vladislav's method works but as I understand it restricts the user to only use the keyboard.
I have used the initial approach but I have added an auxiliary variable and few conditions which allow to pass the flag "unharmed" trough Events that should not change the flag at given moment. It may not be subtle or universal but works for my app. Here is the code:
public void focusGained(FocusEvent e) {
if(!focusFromMouse){
if (higlight){
JTextField tf = (JTextField) e.getComponent();
tf.selectAll();
focusFromMouse=false;
}
}
}
public void focusLost(FocusEvent e) {
if (focusFromMouse){
higlight=false;
focusFromMouse=false;
}else{
higlight=true;
}
}
public void mousePressed(MouseEvent e) {
focusFromMouse=true;
}
At the first, by default, focus on JTextField is requested by mouse-press event, not by mouse-click.
So, this method:
public void mouseClicked(MouseEvent e) {
focusFromMouse = true;
}
is useless because the mouse-click event is triggered after the mouse-press event.
One way to solve your problem is to remove all native MouseListeners from JTextField:
...
for( MouseListener ml : tf1.getMouseListeners() ){
tf1.removeMouseListener(ml);
}
for( MouseMotionListener mml : tf1.getMouseMotionListeners() ){
tf1.removeMouseMotionListener(mml);
}
...
Another way is to handle all mouse events and consume those of them, which are triggered by JTextField:
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if( event.getSource() == tf1 ){
((MouseEvent)event).consume();
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
When I run the code and follow it step by step it turns out that focusGained code happens before mouseClicked
Wrap the code in the FocusListener in a SwingUtilities.invokeLater(). The will place the code on the end of the Event Dispatch Thread (EDT), so the code will run after the variable in the MouseListener has been set.
See Concurrency in Swing for more information about the EDT.
Edit:
Just noticed the other answer. You might be able to do something simpler. Istead of listener for mouseClicked, listen for mousePressed. A mouseClicked event is only generated AFTER the mouseReleased event, so by that time the FocusListener logic has already been executed, even when added to the end of the EDT.
Edit 2:
If the above doesn't work then you might be able to use the EventQueue.peek() method to see if a MouseEvent is on the queue. This might even be easier than worrying about using the invokeLater.

Opening JXDatePicker on gaining focus

I am trying to extend JXDatePicker so that it opens up when it gains focus.
Have searched for suggest that I understand without success.
Is there an elegant way of doing this?
Astonishingly, it's not really possible :-(
For once, the JXDatePicker itself has no api to show/hide the popup (only BasicDatePickerUI has). Plus the ui delegate has some internal magic (read: hacks ... cough) that makes a FocusListener even worse to handle than usually in compound components.
A snippet to play with:
final JXDatePicker picker = new JXDatePicker();
FocusListener l = new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
// no api on the picker, need to use the ui delegate
BasicDatePickerUI pickerUI = (BasicDatePickerUI) picker.getUI();
if (!pickerUI.isPopupVisible()) {
pickerUI.toggleShowPopup();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// opening the popup moves the focus to ... ?
// need to grab it back onto the editor
picker.getEditor().requestFocusInWindow();
}
});
}
#Override
public void focusLost(FocusEvent e) {
}
};
// need to register the listener on the editor
picker.getEditor().addFocusListener(l);
JComponent content = new JPanel();
content.add(new JButton("dummy"));
content.add(picker);
Not really satisfying, as automatic closing of the popup on transfering the focus out again doesn't work reliably, needs two tabs (don't know why)
I had the same problem.
This worked for me:
jXDatePicker.getEditor().addFocusListener(new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
BasicDatePickerUI pickerUI = (BasicDatePickerUI) jXDatePicker.getUI();
if (!pickerUI.isPopupVisible() && e.getOppositeComponent() != getRootPane() && e.getOppositeComponent() != jXDatePicker.getMonthView()) {
pickerUI.toggleShowPopup();
}
}
#Override
public void focusLost(FocusEvent e) {}
});
This piece of code is used to avoid focus issues:
e.getOppositeComponent() != getRootPane()

How to release JScrollpane to make it be not always down to the bottom?

I added the following code segment to my project to force the JScrollPane automatically be down to the bottom after the user acting selection performance, however, when I tried to drag the scroll to go to the top, it still is forced to make the scroll down to the bottom, and I want to ask any solution to resolve it? Thanks in advance.
private void autoScrollToBottom() {
sdPanel.getTabScrollPane().getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
}
});
}
try to use some boolean flag which will indicate if you should move your scrollbar. Set it to to true when you pereform a correct action then in your code:
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if(isScrollingDownRequired) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
isScrollingDownRequired = false;
}
}

Right click on JButton

I am trying to write a Minesweeper clone in Java for fun. I have a grid of JButtons whose labels I will change to represent the danger count, flags, etc.
My problem is, I don't know how to get a right click on a JButton to depress the button. I've done the following:
button.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
boolean mine = field.isMine(x, y);
if (e.isPopupTrigger()) {
button.setText("F");
}
else {
if (mine) {
button.setText("X");
}
}
}
});
This doesn't seem to be working at all; the "F" is never shown, only the "X" part. But more importantly, this does nothing for depressing the button.
EDIT: Macs have popup trigger happen on mousePress, not mouseClick.
EDIT: Here's the solution I worked out based off of accepted answer:
button.addMouseListener(new MouseAdapter(){
boolean pressed;
#Override
public void mousePressed(MouseEvent e) {
button.getModel().setArmed(true);
button.getModel().setPressed(true);
pressed = true;
}
#Override
public void mouseReleased(MouseEvent e) {
//if(isRightButtonPressed) {underlyingButton.getModel().setPressed(true));
button.getModel().setArmed(false);
button.getModel().setPressed(false);
if (pressed) {
if (SwingUtilities.isRightMouseButton(e)) {
button.setText("F");
}
else {
button.setText("X");
}
}
pressed = false;
}
#Override
public void mouseExited(MouseEvent e) {
pressed = false;
}
#Override
public void mouseEntered(MouseEvent e) {
pressed = true;
}
});
add(button);
Minesweeper clone http://grab.by/1y9z
Button can't be pressed by right click. Add such a lines to you mouse listener
mousePressed:
if(isRightButtonPressed) {underlyingButton.getModel().setPressed(true));
mouseReleased:
if(needReset) {underlyingButton.getModel().setPressed(false));
or do there whatever want.
I wouldn't use isPopupTrigger but directly check for the right button:
button.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
boolean mine = field.isMine(x, y);
if (e.getButton() == MouseEvent.BUTTON2) {
button.setText("F");
}
...
Just a small addition: the simplest way to check for the right button is SwingUtilities.isRightMouseButton
As you have mentioned that checking for "mousePressed" solved your issue. And the Javadoc of isPopupTrigger would explain the need for this:
public boolean isPopupTrigger()
...
Note: Popup menus are triggered differently on different systems. Therefore, isPopupTrigger should be checked in both mousePressed and mouseReleased for proper cross-platform functionality.
Also see the section on The Mouse Listener API in the Java Swing tutorial.
MouseEvent has some properties
static int BUTTON1
static int BUTTON2
static int BUTTON3
among others. Check those when your event fires.
EDIT
public int getButton()
Returns which, if any, of the mouse buttons has changed state.
The button being visibly depressed on right click isn't part of the "normal" behavior of buttons. You may be able to fake it using JToggleButtons, or simply changing the button's background color and maybe border while the right mouse button is being held down.
If you are certain that the event is properly being triggered (debug FTW!) and that the button.setText("F") is happening, then perhaps you simply need to repaint.
Repaint the button.
http://java.sun.com/javase/6/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)
This works for me fine on Mac:
import java.awt.event.*;
import javax.swing.*;
public class ButtonTest extends JFrame {
JButton button;
public ButtonTest() {
button = new JButton("W");
button.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == 3) { // if right click
button.setText("F");
button.getModel().setPressed(false);
// button.setEnabled(true);
} else {
button.setText("X");
button.getModel().setPressed(true);
// button.setEnabled(false);
}
}
});
this.add(button);
this.setVisible(true);
}
public static void main(String[] args) {
new ButtonTest();
}
}
You might as well check for e.getButton() == 2 but I don't know when this one is triggered on Macs.

Showing/hiding a JPopupMenu from a JButton; FocusListener not working?

I needed a JButton with an attached dropdown style menu. So I took a JPopupMenu and attached it to the JButton in the way you can see in the code below. What it needs to do is this:
show the popup when clicked
hide it if clicked a second time
hide it if an item is selected in the popup
hide it if the user clicks somewhere else in the screen
These 4 things work, but because of the boolean flag I'm using, if the user clicks somewhere else or selects an item, I have to click twice on the button before it shows up again. That's why I tried to add a FocusListener (which is absolutely not responding) to fix that and set the flag false in these cases.
EDIT: Last attempt in an answer post...
Here are the listeners: (It's in a class extending JButton, so the second listener is on the JButton.)
// Show popup on left click.
menu.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("LOST FOCUS");
isShowingPopup = false;
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("GAINED FOCUS");
}
});
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("isShowingPopup: " + isShowingPopup);
if (isShowingPopup) {
isShowingPopup = false;
} else {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
isShowingPopup = true;
}
}
});
I've been fighting with this for way too long now. If someone can give me a clue about what's wrong with this, it would be great!
Thanks!
Code:
public class Button extends JButton {
// Icon.
private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");
// Unit popup menu.
private final JPopupMenu menu;
// Is the popup showing or not?
private boolean isShowingPopup = false;
public Button(int height) {
super(ARROW_SOUTH);
menu = new JPopupMenu(); // menu is populated somewhere else
// FocusListener on the JPopupMenu
menu.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("LOST FOCUS");
isShowingPopup = false;
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("GAINED FOCUS");
}
});
// ComponentListener on the JPopupMenu
menu.addComponentListener(new ComponentListener() {
#Override
public void componentShown(ComponentEvent e) {
System.out.println("SHOWN");
}
#Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED");
}
#Override
public void componentMoved(ComponentEvent e) {
System.out.println("MOVED");
}
#Override
public void componentHidden(ComponentEvent e) {
System.out.println("HIDDEN");
}
});
// ActionListener on the JButton
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("isShowingPopup: " + isShowingPopup);
if (isShowingPopup) {
menu.requestFocus();
isShowingPopup = false;
} else {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
isShowingPopup = true;
}
}
});
// Skip when navigating with TAB.
setFocusable(true); // Was false first and should be false in the end.
menu.setFocusable(true);
}
}
Here's a variant of Amber Shah's "big hack" suggestion I just made. Without the isShowingPopup flag...
It's not bulletproof, but it works quite well until someone comes in with an incredibly slow click to close the popup (or a very fast second click to reopen it...).
public class Button extends JButton {
// Icon.
private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");
// Popup menu.
private final JPopupMenu menu;
// Last time the popup closed.
private long timeLastShown = 0;
public Button(int height) {
super(ARROW_SOUTH);
menu = new JPopupMenu(); // Populated somewhere else.
// Show and hide popup on left click.
menu.addPopupMenuListener(new PopupMenuListener() {
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
timeLastShown = System.currentTimeMillis();
}
#Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {}
#Override public void popupMenuCanceled(PopupMenuEvent arg0) {}
});
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if ((System.currentTimeMillis() - timeLastShown) > 300) {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
}
}
});
// Skip when navigating with TAB.
setFocusable(false);
}
}
As I said in comments, that's not the most elegant solution, but it's horribly simple and it works in 98% of the cases.
Open to suggestions!
Here is another approach which is not too bad of a hack, if not elegant, and which, as far as I could tell, works. First, at the very top, I added a second boolean called showPopup.
The FocusListener has to be as follows:
menu.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("LOST FOCUS");
isShowingPopup = false;
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("GAINED FOCUS");
isShowingPopup = true;
}
});
The isShowingPopup boolean does not get changed anywhere else--if it gains focus, it assumes it's shown and if it loses focus, it assumes it isn't.
Next, the ActionListener on the button is different:
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("isShowingPopup: " + isShowingPopup);
if (showPopup) {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
menu.requestFocus();
} else {
showPopup = true;
}
}
});
Now comes the really new bit. It's a MouseListener on the button:
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
System.out.println("ispopup?: " + isShowingPopup);
if (isShowingPopup) {
showPopup = false;
}
}
#Override
public void mouseReleased(MouseEvent e) {
showPopup = true;
}
});
Basically, mousePressed gets called before the menu loses focus, so isShowingPopup reflects whether the popup was shown before the button is pressed. Then, if the menu was there, we just set showPopup to false, so that the actionPerformed method does not show the menu once it gets called (after the mouse is let go).
This behaved as expected in every case but one: if the menu was showing and the user pressed the mouse on the button but released it outside of it, actionPerformed was never called. This meant that showPopup remained false and the menu was not shown the next time the button was pressed. To fix this, the mouseReleased method resets showPopup. The mouseReleased method gets called after actionPerformed, as far as I can tell.
I played around with the resulting button for a bit, doing all the things I could think of to the button, and it worked as expected. However, I am not 100% sure that the events will always happen in the same order.
Ultimately, I think this is, at least, worth trying.
You could use the JPopupMenu.isVisible() instead of your Boolean variable to check the current state of the popup menu.
Have you tried adding a ComponentListener to the JPopupMenu, so that you know when it's been shown and hidden (and update your isShowingPopup flag accordingly)? I'm not sure listening for focus changes is necessarily the right approach.
What you need is a PopupMenuListener:
menu.addPopupMenuListener(new PopupMenuListener() {
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
System.out.println("MENU INVIS");
isShowingPopup = false;
}
#Override
public void popupMenuCanceled(PopupMenuEvent arg0) {
System.out.println("MENU CANCELLED");
isShowingPopup = false;
}
});
I inserted this into your code and verified that it works.
Well, I can't be sure without seeing all of your code, but is it possible that the popup never actually gets focus at all? I've had problems with things' not getting focus properly in Swing before, so it could be the culprit. Try calling setFocusable(true) on the menu and then calling requestFocus() when you make the menu appear.
I tried the Answer of Tikhon Jelvis (introducing a smart combination of focusListener and mouseListener). It does not work for me on Linux (Java7/gtk). :-(
Reading http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 there is written "Note that the use of this method is discouraged because its behavior is platform dependent."
It may be that the order of listener calls changed with Java7 or it changes with GTK vs Windows. I would not recommend this solution if you want to be platform independent.
BTW: I created a new account on stackoverflow to give this hint. It seems I am not allowed to comment to his answer (because of reputation). But it seems I have a button to edit it. This stackoverflow is a very funny thing. :-)

Categories

Resources