How to detect ctrl-f in my SWT application - java

I have written an SWT UI which has a primary function of displaying text in a StyledText control. I want to add a handler for Ctrl+F so that when that shortcut is pressed the focus is set to a search box. I have tried using the following code to detect the keypress.
sWindow = new Shell();
...
sWindow.getDisplay().addFilter(SWT.KeyDown, new Listener()
{
#Override
public void handleEvent(Event e)
{
System.out.println("Filter-ctrl: " + SWT.CTRL);
System.out.println("Filter-mask: " + e.stateMask);
System.out.println("Filter-char: " + e.character);
}
});
I was expecting that when I pressed Ctrl+f I would see the following output:
Filter-ctrl: 262144
Filter-mask: 262144
Filter-char: f
However, in practice I actually see the following.
Filter-ctrl: 262144
Filter-mask: 262144
Filter-char: <unprintable char - displayed as a box in eclipse console>
I have two questions:
Is Display.addFilter(...) the best way to add a global shortcut? I tried Display.addListener(...) but this didn't receive any events at all.
Why don't I get the pressed character when I'm holding down Ctrl? When I hold down alt or shift I get the expected mask and the pressed character.

Is Display.addFilter(...) the best way to add a glbal shortcut? I tried Display.addListener(...) but this didn't receive any events at all.
Yes, normally Display.addFilter(...) is the best way to add a glbal shortcut because they have higher preference over the event listeners. See the below comment from Display.addFilter(...) javadoc.
Because event filters run before other
listeners, event filters can both
block other listeners and set
arbitrary fields within an event. For
this reason, event filters are both
powerful and dangerous. They should
generally be avoided for performance,
debugging and code maintenance
reasons.
For your second question:
Why don't I get the pressed character when I'm holding down ctrl? When I hold down alt or shift I get the expected mask and the pressed character.
The problem is that you are looking at the wrong place. Instead of querying e.character you should be using e.keyCode. As per javadoc of e.character you won't get just character f:
Depending on the event, the character
represented by the key that was typed.
This is the final character that
results after all modifiers have been
applied. For example, when the user
types Ctrl+A, the character value is
0x01 (ASCII SOH).
So when you press CTRL+f then it converts in 0x06 (ASCII ACK). Which is not the case when you do ALT+f or SHIFT+f.
On the other hand the javadoc of e.keyCode says:
depending on the event, the key code
of the key that was typed, as defined
by the key code constants in class
SWT. When the character field of the
event is ambiguous, this field
contains the unaffected value of the
original character. For example,
typing Ctrl+M or Enter both result in
the character '\r' but the keyCode
field will also contain '\r' when
Enter was typed and 'm' when Ctrl+M
was typed.
Check the below code for more details. For demo I have tried to put listener on Display and Test.
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
public class ControlF
{
public static void main(String[] args)
{
Display display = new Display ();
final Shell shell = new Shell (display);
final Color green = display.getSystemColor (SWT.COLOR_GREEN);
final Color orig = shell.getBackground();
display.addFilter(SWT.KeyDown, new Listener() {
public void handleEvent(Event e) {
if(((e.stateMask & SWT.CTRL) == SWT.CTRL) && (e.keyCode == 'f'))
{
System.out.println("From Display I am the Key down !!" + e.keyCode);
}
}
});
shell.addKeyListener(new KeyListener() {
public void keyReleased(KeyEvent e) {
if(((e.stateMask & SWT.CTRL) == SWT.CTRL) && (e.keyCode == 'f'))
{
shell.setBackground(orig);
System.out.println("Key up !!");
}
}
public void keyPressed(KeyEvent e) {
if(((e.stateMask & SWT.CTRL) == SWT.CTRL) && (e.keyCode == 'f'))
{
shell.setBackground(green);
System.out.println("Key down !!");
}
}
});
shell.setSize (200, 200);
shell.open ();
while (!shell.isDisposed()) {
if (!display.readAndDispatch ()) display.sleep ();
}
display.dispose ();
}
}

Related

Custom JTextField bug when trying to process minus key

I made a custom JTextField to input numeric values. The class looks as follows:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
public class JNumberField extends JTextField {
public JNumberField(int width, int height) {
this();
setPreferredSize(new Dimension(width, height));
}
public JNumberField() {
setFont(new Font("Monospace", Font.BOLD, 23));
setHorizontalAlignment(JTextField.HORIZONTAL);
}
#Override
public void processKeyEvent(KeyEvent ev) {
//Accepts digits, backspace, minus key and left/right arrow keys
if (Character.isDigit(ev.getKeyChar()) || ev.getKeyCode() == KeyEvent.VK_BACK_SPACE || ev.getKeyCode() == KeyEvent.VK_MINUS || ev.getKeyCode() == KeyEvent.VK_LEFT || ev.getKeyCode() == KeyEvent.VK_RIGHT) {
super.processKeyEvent(ev);
}
ev.consume();
}
public static void main(String[] args) {
JOptionPane.showInputDialog(new JNumberField(300,50));
}
}
All the keys are working except for the "-" key. I also printed the keycode that was pressed and it responded with 45 so the if statement is executed but it doesn't insert the - into the text field. Is this a bug or how can it be fixed? I also tried removing the if statement and just call the processKeyEvent method and then it works to insert the - sign...
First of all I totally agree with Andrew, there are better solutions to solving the problem. For example you can use:
a JSpinner
a JFormattedTextField
a DocumentFilter - see: How to automatically update small letter characters to capital letter in Java JEditorPane?
The reason the above solutions are better is because:
you don't need to extend a class
the solutions are more abstract
Your solution is a low level solution, which means you need to understands the events that are generated when you press a key and how those events are handled by Swing.
A more abstract solution is always better since you don't need to know the details. This is why the Swing API has evolved.
In your case you are not understanding the difference between a "displayable" key event and a "non displayable" event.
A text component is updated when the keyTyped event is generated which is only generated for "displayable" character like " numeric digits" and "-".
A "keyTyped" event is not generated for using the arrow keys or backspace keys.
So your logic needs to be changed to handle the "-" as a character (not a key code):
//if (Character.isDigit(ev.getKeyChar()) || ev.getKeyCode() == KeyEvent.VK_MINUS ...
if (Character.isDigit(ev.getKeyChar()) || ev.getKeyChar() == '-' ...

is there any way we can mask the value entering into jText area at runtime and should clear the contents if backspace kept pressed

I have a requirement where need to mask the value entering into jText area at runtime. I am able to achieve this but problem is scenario with backspace. When I press back space sequentially (one by one) then it work while if I kept pressing then its counting the event as one and removing only one character (by considering the key event as one key released).
Here my code snippets is :
public void showPrompt() throws InterruptedException {
sem.acquire();
this.toFront();
this.setAlwaysOnTop(true);
this.setVisible(true);
if(encryptKeystroke == true) {
jTextArea.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
if (e.getExtendedKeyCode() == KeyEvent.VK_BACK_SPACE) {
text = text.substring(0, text.length() - 1);
}
else {
text += String.valueOf(e.getKeyChar());
}
jTextArea.setText(text.replaceAll(".", "*"));
}
});
}
}
Is there any way if I kept pressing the backspace then it should remove all the characters irrespective of considering it one key event ?
As you have said in the comment that the requirements are not exactly like the password and so you won't be using JPasswordField, I would like to give a solution for the question.
Here, the code to detect a backspace key stroke is written inside the keyReleased() method. Now, the keyReleased() method will be called by the keyListener when you will pull your finger up from a key in this case your backspace key. That is why even if you continuously keep pressing the backspace key, it will execute the code only once i.e. only when you release the key.
Now, you wish to remove one character every time backspace is pressed so you can just move your code from the keyReleased() method to the keyPressed() method.
If you move the code inside the keyPressed() method, then the code will be executed at every key stroke even if you continuously keep pressing the backspace key.

SWT.KeyUp triggers too often

I've some trouble to understand the key event in SWT. My program runs under Windows 8.1 + Java 8.
For example I have a text field and try to handle KeyUp:
textField.addListener(SWT.KeyUp, new Listener() {
#Override
public void handleEvent(Event e) {
}
});
In the handleEvent method I set e.doit to false and then I try to differ between the arrow keys, the space key or some combinations of ALT + letter etc.
I'm wondering a bit because my application executes not the right tasks. So I debugged the coe and found out that the handleEvent method is executing three times with different event object each time.
That's not good beacause in this case it's almost impossible to make it work in the expected way - each call could replace f.e. attribute values of the previous call.
And I don't know the reason: I press one key and the handler method is called three times. Why? It's a little bit illogical for me...
+++ EDITED +++
package mytest;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class Main {
private static int i = 0;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
Text textField = new Text(shell, SWT.BORDER | SWT.READ_ONLY | SWT.SINGLE);
textField.addListener(SWT.KeyUp, new Listener() {
#Override
public void handleEvent(Event e) {
e.doit = false;
System.out.println("Call #" + (++i) + ": keyCode=" + e.keyCode);
}
});
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
You can run this little example and then press f.e. ALT+A, CTRL+I or ARROW_DOWN and you will see that the handle method is called very often per press and this is not what I'm expecting from a key listener!
Solution
What you want to observe is the other fields in the event:
stateMask:
the state of the keyboard modifier keys and mouse masks at the time
the event was generated.
keyLocation:
depending on the event, the location of key specified by the keyCode
or character. The possible values for this field are SWT.LEFT,
SWT.RIGHT, SWT.KEYPAD, or SWT.NONE representing the main keyboard
area.
The location field can be used to differentiate key events that
have the same key code and character but are generated by different
keys on the keyboard. For example, a key down event with the key code
equal to SWT.SHIFT can be generated by the left and the right shift
keys on the keyboard.
The location field can only be used to determine the location of the
key code or character in the current event. It does not include
information about the location of modifiers in the state mask.
Updated Example
The correct, expected behaviour is as each key is released you get notification. Replace your println in your question with:
System.out.println("Call #" + (++i) + ": keyCode=" + e.keyCode + " stateMask=" + e.stateMask
+ " ctrlPressed=" + ((e.stateMask & SWT.CTRL) != 0) + " shiftPressed="
+ ((e.stateMask & SWT.SHIFT) != 0)+ " altPressed="
+ ((e.stateMask & SWT.ALT) != 0));
and you should observe the correct behaviour. The stateMask details the state (e.g. control key pressed).
With your code updated with the extra prints, this is what you see in the console:
for a:
Call #11: keyCode=97 stateMask=0 ctrlPressed=false shiftPressed=false altPressed=false
For ctrl+a, with a released first:
Call #14: keyCode=97 stateMask=262144 ctrlPressed=true shiftPressed=false altPressed=false
Call #15: keyCode=262144 stateMask=262144 ctrlPressed=true shiftPressed=false altPressed=false
For ctrl+a, with ctrl released first:
Call #16: keyCode=262144 stateMask=262144 ctrlPressed=true shiftPressed=false altPressed=false
Call #17: keyCode=97 stateMask=0 ctrlPressed=false shiftPressed=false altPressed=false
For ctrl+shift+alt+a, with a released first:
Call #19: keyCode=97 stateMask=458752 ctrlPressed=true shiftPressed=true altPressed=true
Call #20: keyCode=65536 stateMask=458752 ctrlPressed=true shiftPressed=true altPressed=true
Call #21: keyCode=131072 stateMask=393216 ctrlPressed=true shiftPressed=true altPressed=false
Call #22: keyCode=262144 stateMask=262144 ctrlPressed=true shiftPressed=false altPressed=false
Why?
There are lots of places where you want to know if just the ctrl key is pressed and/or released. Consider hovering over a method or variable in Eclipse and pressing/releasing ctrl.
Update
Based on additional information provided in the comments, the OP wants to allow users to create accelerators.
jface, the layer on top of SWT has such a class already KeySequenceText (code: in git),
A wrapper around the SWT text widget that traps literal key presses
and converts them into key sequences for display. There are two types
of key strokes that are displayed: complete and incomplete. A complete
key stroke is one with a natural key, while an incomplete one has no
natural key. Incomplete key strokes are only displayed until they are
made complete or their component key presses are released.
Obviously I would recommend reusing that instead of spinning a new one. It is just shy of 1000 lines of code that is heavily in use already, so presumably well exercised and (mostly!) bug free.
Since you are using SWT outside of the Eclipse platform, if you do want to take this approach, you can use jface outside the platform too.
Usually listeners added with addListener are called very frequently at every breath almost. For key events , use
textField.addKeyListener( new KeyAdapter()
{
public void keyPressed( Event e )
} );

Java: Register <ENTER> key press on JTextPane

I'm making an application with java that has a JTextPane. I want to be able to execute some code when the enter key is pressed (or when the user goes to the next line). I've looked on the web and not found a solution. Would it be better to tackle this with C#? If not, how can i register the Enter key in the JTextPane's keyTyped() event? If C# is a good option, how would i do this in C#?
Here is a solution i thought would work...but did not
//Event triggered when a key is typed
private void keyTyped(java.awt.event.KeyEvent evt) {
int key = evt.getKeyCode();
if (key == KeyEvent.VK_ENTER) {
Toolkit.getDefaultToolkit().beep();
System.out.println("ENTER pressed");
}
}
Why the above example does not work is because no matter which key i press, i get a keyCode of 0. I would prefer a solution to this problem in Java but C# would work just as well, maybe better. Also, please try to answer the question with examples and not links(unless you really need to). Thanks!
One solution is to add a key binding on the textpane. e.g.,
JTextPane textPane = new JTextPane();
int condition = JComponent.WHEN_FOCUSED;
InputMap iMap = textPane.getInputMap(condition);
ActionMap aMap = textPane.getActionMap();
String enter = "enter";
iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), enter);
aMap.put(enter, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("enter pressed");
}
});
This answer is in case anyone ever views this thread I got the same things as Mr. Mohammad Adib.
So instead of using ...
(evt.getKeyCode()==evt.VK_ENTER)
I used ...
(evt.getKeyChar()=='\n')
and the solution worked.
I am looking for ENTER key in the password text field, to launch the login method when ENTER was pressed. The code below will print in the console the keycode. After running the program and typing a few tihngs in the box I discovered for ENTER key it is code 13.
txtPass = new Text(shlLogin, SWT.BORDER | SWT.PASSWORD);
txtPass.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
System.out.println(e.keyCode);
if (e.keyCode == 13) { /* ... Do your stuff ... */ }
}
});
If you are looking for a single key press, you can still be a little lazy and avoid learning new stuff about key bindings, by using this method. The fun begins when adding CTRL+[Letter] shortcuts - but this is for another discussion.

Remapping the Keyboard at a low level

We are replacing a legacy C application originally written for MSDOS (yes, believe it or not!). This application uses a specially remapped keyboard which intercepts the DOS keyboard interrupt (remember that??!) to sometimes alter the scan codes of the keys pressed by the user so that different processing would occur. Special labels were then placed on the keys telling the users the "new" meaning of these keys.
The new Java version is required to preserve this keyboard layout which the targeted group of users is very familiar with.
An example of what we are trying to do is as follows:
You may never have thought about this, but the numeric keypad of a modern telephone is reversed from the numeric keypad of a computer keyboard. On the former 1-2-3 is on the top row and on the latter it is on the bottom row. We are required to make the keyboard's numeric keypad look like the telephone. Let's say, when the user types "7" on the numeric keypad, we want it look as though he typed a "1", when he types an "8", we want a "2", when he types a "3" we want a "9".
There is much more that we have to do to emulate the DOS application, but we can't even solve this simple case now. I have been all over Key Binding, KeyAdapters, KeyListeners, and even KeyEventDispatchers, and I cannot make this work. I am pretty sure we have to work on the lowest level we are allowed to work by Java to come as close as possible to what the legacy app does. And needless to say, we want the cleanest implementation possible, so that the application level code is not littered with inputMaps and actionMaps etc. As much as possible this needs to be handled globally. Can anyone help?
If I were doing this, I would write the Java App without worrying about the key bindings. Assume that when a component gets a keyevent for #7 its #7, don't worry about whether the 7 or 1 was really typed. The app shouldn't care about how keys are mapped on the keyboard. This should let you start developing the app immediately.
As far as overriding key bindings, this seems like where you want to look: http://download.oracle.com/javase/7/docs/api/java/awt/KeyEventDispatcher.html
It sounds like you can write your own KeyEventDispatcher to handle all the key mapping logic and it prevents mapping logic from messing up the rest of the logic in your application.
This is hacky, and I'll admit I haven't used it myself, but you could subclass KeyEvent and override the fields as needed. So yourSubclass.VK_NUMPAD1 is the integer value of KeyEvent.VK_NUMPAD7, yourSubclass.VK_NUMPAD2 is the integer value of KeyEvent.VK_NUMPAD8, etcetera. Then use your subclass everywhere KeyEvent is normally used.
I also agree that the KeyEventDispatcher should work. But if this a version/platform issue, then maybe you can use a custom Event Queue. See Global Event Dispatching
My stack overfloweth!
The answer was here:
http://download.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html
which says:
For key pressed and key released
events, the getKeyCode method returns
the event's keyCode. For key typed
events, the getKeyCode method always
returns VK_UNDEFINED.
My original attempt thought it could get a keyCode on KEY_TYPED. It could not, and it was the KEY_TYPED event that was clobbering the mapping done in KEY_PRESSED.
Here is a working implementation:
import static java.awt.event.KeyEvent.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
public class KeyboardDispatcherDemo extends JFrame {
/**
* This class shows how to map numeric keypad keys.
* It performs two conversions:
* 1. lower-to-uppercase
* 2. if numeric keypad 7-9 is typed, 1-3 appears and vice versa.
*
* This is modified from the code at
* http://www.exampledepot.com/egs/java.awt/DispatchKey.html#comment-51807
* which demoes the lower-to-upper conversion.
*
* It doesn't yet handle modified numeric keypad keys.
*
*/
public KeyboardDispatcherDemo() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(
new KeyEventDispatcher() {
private char lastMappedKey;
private final Map<Integer, Character> keyMap =
new HashMap<Integer, Character>() {
{
put(VK_NUMPAD1, '7');
put(VK_NUMPAD2, '8');
put(VK_NUMPAD3, '9');
put(VK_NUMPAD7, '1');
put(VK_NUMPAD8, '2');
put(VK_NUMPAD9, '3');
}};
public boolean dispatchKeyEvent(KeyEvent e) {
System.out.println(String.format("INPUT: %s", e.toString()));
boolean dispatch = false;
switch (e.getID()) {
case KeyEvent.KEY_PRESSED:
dispatch = dispatchKeyPressed(e);
break;
case KeyEvent.KEY_TYPED:
dispatch = dispatchKeyTyped(e);
break;
case KeyEvent.KEY_RELEASED:
dispatch = dispatchKeyReleased(e);
break;
default:
throw new IllegalArgumentException();
}
System.out.println(String.format("OUTPUT: %s", e.toString()));
System.out.println();
return dispatch;
}
private boolean dispatchKeyPressed(KeyEvent e) {
char k = e.getKeyChar();
if (k != CHAR_UNDEFINED) {
if (Character.isLetter(k)) {
e.setKeyChar(Character.toUpperCase(e.getKeyChar()));
} else if (e.getModifiers() == 0){
Character mapping = keyMap.get(e.getKeyCode());
if (mapping != null) {
e.setKeyChar(mapping);
}
}
// save the last mapping so that KEY_TYPED can use it.
// note we don't do this for modifier keys.
this.lastMappedKey = e.getKeyChar();
}
return false;
}
// KEY_TYPED events don't have keyCodes so we rely on the
// lastMappedKey that was saved on KeyPressed.
private boolean dispatchKeyTyped(KeyEvent e) {
char k = e.getKeyChar();
if (k != CHAR_UNDEFINED) {
e.setKeyChar(lastMappedKey);
}
return false;
}
private boolean dispatchKeyReleased(KeyEvent e) {
char k = e.getKeyChar();
if (k != CHAR_UNDEFINED) {
e.setKeyChar(lastMappedKey);
this.lastMappedKey=CHAR_UNDEFINED;
}
return false;
}
});
setTitle("KeyboardDispatcherDemo");
JPanel panel = new JPanel();
panel.setBackground(new Color(204, 153, 255));
panel.setLayout(new BorderLayout());
getContentPane().add(panel, BorderLayout.CENTER);
JTextArea staticText = new JTextArea();
staticText.setText("This demonstrates how to map numeric keypad keys. It uppercases all letters and converts Numeric Keypad 1-3 to 7-9 and vice versa. Try it.");
staticText.setLineWrap(true);
staticText.setWrapStyleWord(true);
panel.add(staticText, BorderLayout.NORTH);
staticText.setFocusable(false);
JTextField textField = new JTextField();
textField.setText("");
textField.setHorizontalAlignment(SwingConstants.LEFT);
panel.add(textField, BorderLayout.SOUTH);
textField.setColumns(10);
textField.setFocusable(true);
setSize(getPreferredSize());
setVisible(true);
}
/**
* #param args
*/
public static void main(String[] args) {
new KeyboardDispatcherDemo();
}
#Override
public Dimension getPreferredSize() {
// TODO Auto-generated method stub
return new Dimension(400,300);
}
}
Thanks to all who nudged me toward the answer.
which brings me to my next question ... stay tuned.

Categories

Resources