Search |
|||
Stephen Friedrich's blogSpice up Text Components with Keyboard ShortcutsPosted by skelvin on February 14, 2006 at 7:22 AM PST
In a new Swing project I was missing the alternative set of cut/copy/paste shortcuts and also the ability to quickly delete all characters up to the start or end of the current word. I had done this before, but wasn't able to dig up the code again. Neither did Google find any code I could simply borrow. So, here's how I implemented those, both for your and for my future reference. Usually to add new a shortcut you can modify a component's Unfortunately it is problematic to do so in a generic way for all instances and all types of text components. After some discussion on the javadesktop forum it turned out that for text component you can use a shortcut: Simply add the action itself to the input map and it will be used directly. Windows-style Cut/Copy/Paste shortcuts
I discovered these quite late, but have been using it alot. Guess it's because with my personal system of writing with four and a half fingers I almost never use the right modifier keys and typing ctrl-x with using the left control key is awkward. These are quite easy to implement, because we can just reuse the existing actions and only need new bindings. Delete to start/end of word
Interestingly the first two editors I tried handle Ctrl-Delete slightly differently: UltraEdit deletes all characters up the the start of the next word while Idea deletes only up to the end of the current word. I decided to implement the latter because it's consistent with the next two editors I checked: Both OpenOffice and MS Word only delete to end of word.
These were a little harder to implement, because custom actions are needed. References Keyboard Bindings in Swing [Sun Developer Network]Swing: Understanding Input/Action Maps [javalobby] Discussion in the javadesktop forum Implementation To add the shortcuts to the text components in your swing application, simply add a single line that is executed during startup:SwingUtils.addTextComponentActions();Without further ado, here is the complete code, together with a simple ui that let's you test it: package com.eekboom.xswing; import javax.swing.*; import javax.swing.text.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.ActionEvent; import java.awt.*; public class SwingUtils { // See http://forums.java.net/jive/thread.jspa?threadID=8503 public static void addTextComponentActions() { // note: we can safely register all keystrokes even for password fields: // the cut/copy actions won't work (unless the client property // "JPasswordField.cutCopyAllowed" has been set on the component) // and the deleteTpPrevious/NextAction explicitly check for password fields. String[] keys = {"TextField", "FormattedTextField", "PasswordField", "TextArea", "TextPane", "EditorPane"}; registerActions(keys); } private static void registerActions(String[] propertyPrefixes) { DeleteToEndOfWordAction deleteToEndOfWordAction = new DeleteToEndOfWordAction(); DeleteToStartOfWordAction deleteToStartOfWordAction = new DeleteToStartOfWordAction(); DefaultEditorKit.CopyAction copyAction = new DefaultEditorKit.CopyAction(); DefaultEditorKit.CutAction cutAction = new DefaultEditorKit.CutAction(); DefaultEditorKit.PasteAction pasteAction = new DefaultEditorKit.PasteAction(); for(int i = 0; i < propertyPrefixes.length; i++) { String propertyPrefix = propertyPrefixes[i]; UIDefaults defaults = UIManager.getDefaults(); Object o = defaults.get(propertyPrefix + ".focusInputMap"); InputMap inputMap = (InputMap) o; inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.SHIFT_MASK, false), pasteAction); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_MASK, false), cutAction); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.CTRL_MASK, false), copyAction); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, InputEvent.CTRL_MASK, false), deleteToStartOfWordAction); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK, false), deleteToEndOfWordAction); } } } class DeleteToStartOfWordAction extends TextAction { public static final String NAME = "delete-to-previous-word"; public DeleteToStartOfWordAction() { super(NAME); } public void actionPerformed(ActionEvent e) { JTextComponent textComponent = getTextComponent(e); if(textComponent == null || !textComponent.isEditable()) { UIManager.getLookAndFeel().provideErrorFeedback(textComponent); return; } try { Document document = textComponent.getDocument(); Caret caret = textComponent.getCaret(); int caretIndex = caret.getDot(); int markIndex = caret.getMark(); int selectionEndIndex = Math.max(caretIndex, markIndex); int selectionStartIndex = Math.min(caretIndex, markIndex); int startIndex = getStartOfWordOffset(textComponent, selectionStartIndex); if(startIndex != selectionEndIndex) { document.remove(startIndex, selectionEndIndex - startIndex); } else if(caretIndex > 0) { UIManager.getLookAndFeel().provideErrorFeedback(textComponent); } } catch(BadLocationException bl) { UIManager.getLookAndFeel().provideErrorFeedback(textComponent); } } private int getStartOfWordOffset(JTextComponent target, int offset) throws BadLocationException { if(target instanceof JPasswordField) { return 0; } Element currentParagraph = Utilities.getParagraphElement(target, offset); int wordOffset = Utilities.getPreviousWord(target, offset); boolean isInPreviousParagraph = wordOffset < currentParagraph.getStartOffset(); if(isInPreviousParagraph) { //noinspection UnnecessaryLocalVariable int endOfPreviousParagraph = Utilities .getParagraphElement(target, wordOffset).getEndOffset() - 1; wordOffset = endOfPreviousParagraph; } return wordOffset; } } class DeleteToEndOfWordAction extends TextAction { public static final String NAME = "delete-to-next-word"; public DeleteToEndOfWordAction() { super(NAME); } public void actionPerformed(ActionEvent e) { JTextComponent textComponent = getTextComponent(e); if(textComponent == null || !textComponent.isEditable()) { UIManager.getLookAndFeel().provideErrorFeedback(textComponent); return; } try { Document document = textComponent.getDocument(); Caret caret = textComponent.getCaret(); int caretIndex = caret.getDot(); int markIndex = caret.getMark(); int selectionEndIndex = Math.max(caretIndex, markIndex); int selectionStartIndex = Math.min(caretIndex, markIndex); int endIndex = getEndOfWordOffset(textComponent, selectionEndIndex); if(endIndex != selectionEndIndex) { document.remove(selectionStartIndex, endIndex - selectionStartIndex); } else if(caretIndex > 0) { UIManager.getLookAndFeel().provideErrorFeedback(textComponent); } } catch(BadLocationException bl) { UIManager.getLookAndFeel().provideErrorFeedback(textComponent); } } private int getEndOfWordOffset(JTextComponent target, int offset) throws BadLocationException { Element currentPararaph = Utilities.getParagraphElement(target, offset); int currentParagraphEndOffset = currentPararaph.getEndOffset(); if(target instanceof JPasswordField) { return currentParagraphEndOffset - 1; } int wordOffset = offset; try { int startOfNextWord = Utilities.getNextWord(target, offset); int endOfCurrentWord = Utilities.getWordEnd(target, offset); boolean isInWhiteSpace = startOfNextWord == endOfCurrentWord; if(isInWhiteSpace) { wordOffset = Utilities.getWordEnd(target, startOfNextWord); } else { wordOffset = endOfCurrentWord; } if(wordOffset >= currentParagraphEndOffset && offset != currentParagraphEndOffset - 1) { wordOffset = currentParagraphEndOffset - 1; } } catch(BadLocationException badLocationException) { int end = target.getDocument().getLength(); if(wordOffset != end) { if(offset != currentParagraphEndOffset - 1) { wordOffset = currentParagraphEndOffset - 1; } else { wordOffset = end; } } else { throw badLocationException; } } return wordOffset; } } class Main extends JFrame { int _rowIndex = 0; private Main() { getContentPane().setLayout(new GridBagLayout()); addLabelAndComponent("Text Field", new JTextField(40), false); addLabelAndComponent("Formatted Text Field", new JFormattedTextField(), false); addLabelAndComponent("Password Field", new JPasswordField(), false); addLabelAndComponent("Text Area", new JTextArea(40, 3), true); addLabelAndComponent("Text Pane", new JTextPane(), true); addLabelAndComponent("Editor Pane", new JEditorPane(), true); setBounds(100, 100, 600, 400); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } private void addLabelAndComponent(String text, JTextComponent component, boolean fillVertically) { addLabel(text); addTextComponent(component, fillVertically); } private void addLabel(String text) { GridBagConstraints c = new GridBagConstraints(0, _rowIndex, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(2, 2, 0, 0), 0, 0); getContentPane().add(new JLabel(text), c); } private void addTextComponent(JTextComponent component, boolean fillVertically) { int fill = fillVertically ? GridBagConstraints.BOTH : GridBagConstraints.HORIZONTAL; double weighty = fillVertically ? 1.0 : 0.0; Insets insets = new Insets(2, 2, 0, 0); GridBagConstraints c = new GridBagConstraints(1, _rowIndex, 1, 1, 1.0, weighty, GridBagConstraints.NORTHWEST, fill, insets, 0, 0); if(fillVertically) { getContentPane().add(new JScrollPane(component), c); } else { getContentPane().add(component, c); } ++_rowIndex; } public static void main(String[] args) { SwingUtils.addTextComponentActions(); new Main(); } } »
Related Topics >>
Java Desktop Comments
Comments are listed in date ascending order (oldest first)
|
CategoriesArchivesRecent Entries |
||
|
|