Implementing “select on focus” behavior for an Eclipse Text control
-
29-05-2021 - |
Question
Goal
I'm trying to implement "select on focus" behavior for an Eclipse Text
control:
When the control is already focused:
- clicking and dragging behaves normally
When the control is not focused:
clicking and dragging to select text behaves normally
clicking without selecting text will select all text
giving focus using the keyboard will select all text
Problem
Simply selecting all text for
SWT.FocusIn
doesn't select text when focusing by mouse click.SWT.FocusIn
is fired beforeSWT.MouseDown
, so there's no way to tell if the control already had focus when the user presses the mouse down.
Questions
Why does Eclipse fire the events in that order? That doesn't make any sense to me. Is it a restriction of some supported OS?
Is there some workaround I can use to implement this functionality?
Solution
Someone in #eclipse linked me to an Eclipse bug raised a long time ago: Can't use focus listener to select all text
Using one of the suggestions there, I came up with the following solution (works in Windows, untested on other platforms):
/**
* This method adds select-on-focus functionality to a {@link Text} component.
*
* Specific behavior:
* - when the Text is already focused -> normal behavior
* - when the Text is not focused:
* -> focus by keyboard -> select all text
* -> focus by mouse click -> select all text unless user manually selects text
*
* @param text
*/
public static void addSelectOnFocusToText(Text text) {
Listener listener = new Listener() {
private boolean hasFocus = false;
private boolean hadFocusOnMousedown = false;
@Override
public void handleEvent(Event e) {
switch(e.type) {
case SWT.FocusIn: {
Text t = (Text) e.widget;
// Covers the case where the user focuses by keyboard.
t.selectAll();
// The case where the user focuses by mouse click is special because Eclipse,
// for some reason, fires SWT.FocusIn before SWT.MouseDown, and on mouse down
// it cancels the selection. So we set a variable to keep track of whether the
// control is focused (can't rely on isFocusControl() because sometimes it's wrong),
// and we make it asynchronous so it will get set AFTER SWT.MouseDown is fired.
t.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
hasFocus = true;
}
});
break;
}
case SWT.FocusOut: {
hasFocus = false;
((Text) e.widget).clearSelection();
break;
}
case SWT.MouseDown: {
// Set the variable which is used in SWT.MouseUp.
hadFocusOnMousedown = hasFocus;
break;
}
case SWT.MouseUp: {
Text t = (Text) e.widget;
if(t.getSelectionCount() == 0 && !hadFocusOnMousedown) {
((Text) e.widget).selectAll();
}
break;
}
}
}
};
text.addListener(SWT.FocusIn, listener);
text.addListener(SWT.FocusOut, listener);
text.addListener(SWT.MouseDown, listener);
text.addListener(SWT.MouseUp, listener);
}