| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Common Public License v1.0 which accompanies this distribution, |
| * and is available at http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: IBM Corporation - initial API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.ui.internal.keys; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.ModifyEvent; |
| import org.eclipse.swt.events.ModifyListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.keys.KeySequence; |
| import org.eclipse.ui.keys.KeyStroke; |
| import org.eclipse.ui.keys.NaturalKey; |
| import org.eclipse.ui.keys.ParseException; |
| import org.eclipse.ui.keys.SWTKeySupport; |
| import org.eclipse.ui.keys.SpecialKey; |
| |
| /** |
| * 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. |
| */ |
| public final class KeySequenceText { |
| |
| /** |
| * A key listener that traps incoming events and displays them in the |
| * wrapped text field. It has no effect on traversal operations. |
| */ |
| private class KeyTrapListener implements Listener { |
| /** |
| * The index at which insertion should occur. This is used if there is |
| * a replacement occurring in the middle of the stroke, and the first |
| * key stroke was incomplete. |
| */ |
| private int insertionIndex = -1; |
| |
| /** |
| * Resets the insertion index to point nowhere. In other words, it is |
| * set to <code>-1</code>. |
| */ |
| void clearInsertionIndex() { |
| insertionIndex = -1; |
| } |
| |
| /** |
| * Deletes the current selection. If there is no selection, then it |
| * deletes the last key stroke. |
| * |
| * @param keyStrokes |
| * The key strokes from which to delete. This list must not |
| * be <code>null</code>, and must represent a valid key |
| * sequence. |
| */ |
| private void deleteKeyStroke(List keyStrokes) { |
| clearInsertionIndex(); |
| |
| if (hasSelection()) { |
| /* |
| * Delete the current selection -- disallowing incomplete |
| * strokes in the middle of the sequence. |
| */ |
| deleteSelection(keyStrokes, false); |
| |
| } else { |
| // Remove the last key stroke. |
| if (!keyStrokes.isEmpty()) { |
| keyStrokes.remove(keyStrokes.size() - 1); |
| } |
| |
| } |
| } |
| |
| /** |
| * Handles the key pressed and released events on the wrapped text |
| * widget. This makes sure to either add the pressed key to the |
| * temporary key stroke, or complete the current temporary key stroke |
| * and prompt for the next. In the case of a key release, this makes |
| * sure that the temporary stroke is correctly displayed -- |
| * corresponding with modifier keys that may have been released. |
| * |
| * @param event |
| * The triggering event; must not be <code>null</code>. |
| */ |
| public void handleEvent(Event event) { |
| List keyStrokes = new ArrayList(getKeySequence().getKeyStrokes()); |
| |
| // Dispatch the event to the correct handler. |
| if (event.type == SWT.KeyDown) { |
| handleKeyDown(event, keyStrokes); |
| } else if (event.type == SWT.KeyUp) { |
| handleKeyUp(event, keyStrokes); |
| } |
| |
| // Update the underlying widget. |
| setKeySequence(KeySequence.getInstance(keyStrokes)); |
| |
| // Prevent the event from reaching the widget. |
| event.doit = false; |
| } |
| |
| /** |
| * Handles the case where the key event is an <code>SWT.KeyDown</code> |
| * event. This either causes a deletion (if it is an unmodified |
| * backspace key stroke), or an insertion (if it is any other key). |
| * |
| * @param event |
| * The trigger key down event; must not be <code>null</code>. |
| * @param keyStrokes |
| * The current list of key strokes. This valud must not be |
| * <code>null</code>, and it must represent a valid key |
| * sequence. |
| */ |
| private void handleKeyDown(Event event, List keyStrokes) { |
| // Is it an unmodified backspace character? |
| if ((event.character == SWT.BS) && (event.stateMask == 0)) { |
| deleteKeyStroke(keyStrokes); |
| } else { |
| insertKeyStroke(event, keyStrokes); |
| } |
| } |
| |
| /** |
| * Handles the case where the key event is an <code>SWT.KeyUp</code> |
| * event. This resets the insertion index. If there is an incomplete |
| * stroke, then that incomplete stroke is modified to match the keys |
| * that are still held. If no keys are held, then the incomplete stroke |
| * is removed. |
| * |
| * @param event |
| * The triggering event; must not be <code>null</code> |
| * @param keyStrokes |
| * The key strokes that are part of the current key |
| * sequence; these key strokes are guaranteed to represent a |
| * valid key sequence. This valud must not be <code>null</code>. |
| */ |
| private void handleKeyUp(Event event, List keyStrokes) { |
| if (hasIncompleteStroke()) { |
| /* |
| * Figure out the SWT integer representation of the remaining |
| * values. |
| */ |
| Event mockEvent = new Event(); |
| if ((event.keyCode & SWT.MODIFIER_MASK) != 0) { |
| // This key up is a modifier key being released. |
| mockEvent.stateMask = event.stateMask - event.keyCode; |
| } else { |
| /* |
| * This key up is the other end of a key down that was |
| * trapped by the operating system or window manager. |
| */ |
| mockEvent.stateMask = event.stateMask; |
| } |
| |
| /* |
| * Get a reasonable facsimile of the stroke that is still |
| * pressed. |
| */ |
| int key = |
| SWTKeySupport.convertEventToUnmodifiedAccelerator(mockEvent); |
| KeyStroke remainingStroke = |
| SWTKeySupport.convertAcceleratorToKeyStroke(key); |
| if (!keyStrokes.isEmpty()) { |
| keyStrokes.remove(keyStrokes.size() - 1); |
| } |
| if (!remainingStroke.getModifierKeys().isEmpty()) { |
| keyStrokes.add(remainingStroke); |
| } |
| } |
| |
| } |
| |
| /** |
| * <p> |
| * Handles the case where a key down event is leading to a key stroke |
| * being inserted. The current selection is deleted, and an invalid |
| * remanents of the stroke are also removed. The insertion is carried |
| * out at the cursor position. |
| * </p> |
| * <p> |
| * If only a natural key is selected (as part of a larger key stroke), |
| * then it is possible for the user to press a natural key to replace |
| * the old natural key. In this situation, pressing any modifier keys |
| * will replace the whole thing. |
| * </p> |
| * <p> |
| * If the insertion point is not at the end of the sequence, then |
| * incomplete strokes will not be immediately inserted. Only when the |
| * sequence is completed is the stroke inserted. This is a requirement |
| * as the widget must always represent a valid key sequence. The |
| * insertion point is tracked using <code>insertionIndex</code>, |
| * which is an index into the key stroke array. |
| * </p> |
| * |
| * @param event |
| * The triggering key down event; must not be <code>null</code>. |
| * @param keyStrokes |
| * The key strokes into which the current stroke should be |
| * inserted. This value must not be <code>null</code>, |
| * and must represent a valid key sequence. |
| */ |
| private void insertKeyStroke(Event event, List keyStrokes) { |
| // Compute the key stroke to insert. |
| int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(event); |
| KeyStroke stroke = SWTKeySupport.convertAcceleratorToKeyStroke(key); |
| |
| /* |
| * Only insert the stroke if it is *not ScrollLock. Let's not get |
| * silly |
| */ |
| if ((SpecialKey.NUM_LOCK.equals(stroke.getNaturalKey())) |
| || (SpecialKey.CAPS_LOCK.equals(stroke.getNaturalKey())) |
| || (SpecialKey.SCROLL_LOCK.equals(stroke.getNaturalKey()))) { |
| return; |
| } |
| |
| if (insertionIndex != -1) { |
| // There is a previous replacement still going on. |
| if (stroke.isComplete()) { |
| insertStrokeAt(keyStrokes, stroke, insertionIndex); |
| clearInsertionIndex(); |
| } |
| |
| } else if (hasSelection()) { |
| // There is a selection that needs to be replaced. |
| insertionIndex = |
| deleteSelection(keyStrokes, stroke.isComplete()); |
| if ((stroke.isComplete()) |
| || (insertionIndex >= keyStrokes.size())) { |
| insertStrokeAt(keyStrokes, stroke, insertionIndex); |
| clearInsertionIndex(); |
| } |
| |
| } else { |
| // No selection, so remove the incomplete stroke, if any |
| if ((hasIncompleteStroke()) && (!keyStrokes.isEmpty())) { |
| keyStrokes.remove(keyStrokes.size() - 1); |
| } |
| |
| // And then add the new stroke. |
| if ((keyStrokes.isEmpty()) |
| || (insertionIndex >= keyStrokes.size()) |
| || (isCursorInLastPosition())) { |
| insertStrokeAt(keyStrokes, stroke, keyStrokes.size()); |
| clearInsertionIndex(); |
| } else { |
| /* |
| * I'm just getting the insertionIndex here. No actual |
| * deletion should occur. |
| */ |
| insertionIndex = |
| deleteSelection(keyStrokes, stroke.isComplete()); |
| if (stroke.isComplete()) { |
| insertStrokeAt(keyStrokes, stroke, insertionIndex); |
| clearInsertionIndex(); |
| } |
| } |
| |
| } |
| } |
| } |
| |
| /** |
| * A traversal listener that blocks all traversal except for tabs and arrow |
| * keys. |
| */ |
| private class TraversalFilter implements Listener { |
| /** |
| * Handles the traverse event on the text field wrapped by this class. |
| * It swallows all traverse events example for tab and arrow key |
| * navigation. The other forms of navigation can be reached by tabbing |
| * off of the control. |
| * |
| * @param event |
| * The trigger event; must not be <code>null</code>. |
| */ |
| public void handleEvent(Event event) { |
| switch (event.detail) { |
| case SWT.TRAVERSE_ESCAPE : |
| case SWT.TRAVERSE_MNEMONIC : |
| case SWT.TRAVERSE_NONE : |
| case SWT.TRAVERSE_PAGE_NEXT : |
| case SWT.TRAVERSE_PAGE_PREVIOUS : |
| case SWT.TRAVERSE_RETURN : |
| event.type = SWT.None; |
| event.doit = false; |
| break; |
| |
| case SWT.TRAVERSE_TAB_NEXT : |
| case SWT.TRAVERSE_TAB_PREVIOUS : |
| // Check if modifiers other than just 'Shift' were |
| // down. |
| if ((event.stateMask & (SWT.MODIFIER_MASK ^ SWT.SHIFT)) |
| != 0) { |
| // Modifiers other than shift were down. |
| event.type = SWT.None; |
| event.doit = false; |
| break; |
| } |
| |
| // fall through -- either no modifiers, or just shift. |
| |
| case SWT.TRAVERSE_ARROW_NEXT : |
| case SWT.TRAVERSE_ARROW_PREVIOUS : |
| default : |
| // Let the traversal happen, but clear the incomplete |
| // stroke |
| if (hasIncompleteStroke()) { |
| List keyStrokes = |
| new ArrayList(getKeySequence().getKeyStrokes()); |
| if (!keyStrokes.isEmpty()) { |
| keyStrokes.remove(keyStrokes.size() - 1); |
| } |
| setKeySequence(KeySequence.getInstance(keyStrokes)); |
| } |
| } |
| |
| } |
| } |
| |
| /** |
| * The manager resposible for installing and removing the traversal filter |
| * when the key sequence entry widget gains and loses focus. |
| */ |
| private class TraversalFilterManager implements FocusListener { |
| /** The managed filter. We only need one instance. */ |
| private TraversalFilter filter = new TraversalFilter(); |
| |
| /** |
| * Attaches the global traversal filter. |
| * |
| * @param event |
| * Ignored. |
| */ |
| public void focusGained(FocusEvent event) { |
| Display.getCurrent().addFilter(SWT.Traverse, filter); |
| } |
| |
| /** |
| * Detaches the global traversal filter. |
| * |
| * @param event |
| * Ignored. |
| */ |
| public void focusLost(FocusEvent event) { |
| Display.getCurrent().removeFilter(SWT.Traverse, filter); |
| } |
| } |
| |
| /** |
| * A modification listener that makes sure that external events to this |
| * class (i.e., direct modification of the underlying text) do not break |
| * this class' view of the world. |
| */ |
| private class UpdateSequenceListener implements ModifyListener { |
| /** |
| * Handles the modify event on the underlying text widget. |
| * |
| * @param event |
| * The triggering event; ignored. |
| */ |
| public void modifyText(ModifyEvent event) { |
| try { |
| // The original sequence. |
| KeySequence originalSequence = getKeySequence(); |
| |
| // The new sequence drawn from the text. |
| String contents = getText(); |
| KeySequence newSequence = KeySequence.getInstance(contents); |
| |
| // Check to see if they're the same. |
| if (!originalSequence.equals(newSequence)) { |
| setKeySequence(newSequence); |
| } |
| |
| } catch (ParseException e) { |
| // Abort any cut/paste-driven modifications |
| setKeySequence(getKeySequence()); |
| } |
| } |
| } |
| |
| static { |
| TreeSet trappedKeys = new TreeSet(); |
| trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.TAB)); |
| trappedKeys.add( |
| SWTKeySupport.convertAcceleratorToKeyStroke(SWT.TAB | SWT.SHIFT)); |
| trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.BS)); |
| List trappedKeyList = new ArrayList(trappedKeys); |
| TRAPPED_KEYS = Collections.unmodifiableList(trappedKeyList); |
| } |
| |
| /** An empty string instance for use in clearing text values. */ |
| private static final String EMPTY_STRING = ""; //$NON-NLS-1$ |
| /** |
| * The special integer value for the maximum number of strokes indicating |
| * that an infinite number should be allowed. |
| */ |
| public static final int INFINITE = -1; |
| /** |
| * The keys trapped by this widget. This list is guaranteed to be roughly |
| * accurate. Perfection is not possible, as SWT does not export traversal |
| * keys as constants. |
| */ |
| public static final List TRAPPED_KEYS; |
| |
| /** |
| * The key filter attached to the underlying widget that traps key events. |
| */ |
| private final KeyTrapListener keyFilter = new KeyTrapListener(); |
| /** |
| * The text of the key sequence -- containing only the complete key |
| * strokes. |
| */ |
| private KeySequence keySequence = KeySequence.getInstance(); |
| /** The maximum number of key strokes permitted in the sequence. */ |
| private int maxStrokes = INFINITE; |
| /** The text widget that is wrapped for this class. */ |
| private final Text text; |
| /** |
| * The listener that makes sure that the text widget remains up-to-date |
| * with regards to external modification of the text (e.g., cut & pasting). |
| */ |
| private final UpdateSequenceListener updateSequenceListener = |
| new UpdateSequenceListener(); |
| |
| /** |
| * Constructs an instance of <code>KeySequenceTextField</code> with the |
| * text field to use. If the platform is carbon (MacOS X), then the font is |
| * set to be the same font used to display accelerators in the menus. |
| * |
| * @param wrappedText |
| * The text widget to wrap; must not be <code>null</code>. |
| */ |
| public KeySequenceText(Text wrappedText) { |
| text = wrappedText; |
| |
| // Set the font if the platform is carbon. |
| if ("carbon".equals(SWT.getPlatform())) { //$NON-NLS-1$ |
| // Don't worry about this font name here; it is the official menu |
| // font and point size on the Mac. |
| final Font font = new Font(text.getDisplay(), "Lucida Grande", 13, SWT.NORMAL); //$NON-NLS-1$ |
| text.setFont(font); |
| text.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| font.dispose(); |
| } |
| }); |
| } |
| |
| // Add the key listener. |
| text.addListener(SWT.KeyUp, keyFilter); |
| text.addListener(SWT.KeyDown, keyFilter); |
| |
| // Add the focus listener that attaches the global traversal filter. |
| text.addFocusListener(new TraversalFilterManager()); |
| |
| // Add an internal modify listener. |
| text.addModifyListener(updateSequenceListener); |
| } |
| |
| /** |
| * Clears the text field and resets all the internal values. |
| */ |
| public void clear() { |
| keySequence = KeySequence.getInstance(); |
| text.setText(EMPTY_STRING); |
| } |
| |
| /** |
| * Removes the key strokes from the list corresponding the selection. If |
| * <code>allowIncomplete</code>, then invalid key sequences will be |
| * allowed (i.e., those with incomplete strokes in the non-terminal |
| * position). Otherwise, incomplete strokes will be removed. This modifies |
| * <code>keyStrokes</code> in place, and has no effect on the text widget |
| * this class wraps. |
| * |
| * @param keyStrokes |
| * The list of key strokes from which the selection should be |
| * removed; must not be <code>null</code>. |
| * @param allowIncomplete |
| * Whether incomplete strokes should be allowed to exist in the |
| * list after the deletion. |
| * @return The index at which a subsequent insert should occur. This index |
| * only has meaning to the <code>insertStrokeAt</code> method. |
| */ |
| private int deleteSelection(List keyStrokes, boolean allowIncomplete) { |
| // Get the current selection. |
| Point selection = text.getSelection(); |
| int start = selection.x; |
| int end = selection.y; |
| |
| /* |
| * Using the key sequence format method, discover the point at which |
| * adding key strokes passes or equals the start of the selection. In |
| * other words, find the first stroke that is part of the selection. |
| * Keep track of the text range under which the stroke appears (i.e., |
| * startTextIndex->string.length() is the first selected stroke). |
| */ |
| String string = new String(); |
| List currentStrokes = new ArrayList(); |
| Iterator keyStrokeItr = keyStrokes.iterator(); |
| int startTextIndex = 0; // keeps track of the start of the stroke |
| while ((string.length() < start) && (keyStrokeItr.hasNext())) { |
| startTextIndex = string.length(); |
| currentStrokes.add(keyStrokeItr.next()); |
| string = KeySequence.getInstance(currentStrokes).format(); |
| } |
| |
| /* |
| * If string.length() == start, then the cursor is positioned between |
| * strokes (i.e., selection is outside of a stroke). |
| */ |
| int startStrokeIndex; |
| if (string.length() == start) { |
| startStrokeIndex = currentStrokes.size(); |
| } else { |
| startStrokeIndex = currentStrokes.size() - 1; |
| } |
| |
| /* |
| * Check to see if the cursor is only positioned, rather than actually |
| * selecting something. We only need to compute the end if there is a |
| * selection. |
| */ |
| int endStrokeIndex; |
| if (start == end) { |
| return startStrokeIndex; |
| |
| } else { |
| while ((string.length() < end) && (keyStrokeItr.hasNext())) { |
| currentStrokes.add(keyStrokeItr.next()); |
| string = KeySequence.getInstance(currentStrokes).format(); |
| } |
| endStrokeIndex = currentStrokes.size() - 1; |
| if (endStrokeIndex < 0) { |
| endStrokeIndex = 0; |
| } |
| |
| } |
| |
| /* |
| * Remove the strokes that are touched by the selection. Keep track of |
| * the first stroke removed. |
| */ |
| KeyStroke startStroke = (KeyStroke) keyStrokes.get(startStrokeIndex); |
| while (startStrokeIndex <= endStrokeIndex) { |
| keyStrokes.remove(startStrokeIndex); |
| endStrokeIndex--; |
| } |
| |
| /* |
| * Allow the first stroke removed to be replaced by an incomplete |
| * stroke. |
| */ |
| if (allowIncomplete) { |
| SortedSet modifierKeys = new TreeSet(startStroke.getModifierKeys()); |
| KeyStroke incompleteStroke = |
| KeyStroke.getInstance(modifierKeys, null); |
| int incompleteStrokeLength = incompleteStroke.format().length(); |
| if ((startTextIndex + incompleteStrokeLength) <= start) { |
| keyStrokes.add(startStrokeIndex, incompleteStroke); |
| } |
| } |
| |
| return startStrokeIndex; |
| } |
| |
| /** |
| * An accessor for the <code>KeySequence</code> that corresponds to the |
| * current state of the text field. This includes incomplete strokes. |
| * |
| * @return The key sequence representation; never <code>null</code>. |
| */ |
| public KeySequence getKeySequence() { |
| return keySequence; |
| } |
| |
| /** |
| * An accessor for the underlying text widget's contents. |
| * |
| * @return The text contents of this entry; never <code>null</code>. |
| */ |
| private String getText() { |
| return text.getText(); |
| } |
| |
| /** |
| * Tests whether the current key sequence has a stroke with no natural key. |
| * |
| * @return <code>true</code> is there is an incomplete stroke; <code>false</code> |
| * otherwise. |
| */ |
| private boolean hasIncompleteStroke() { |
| return !keySequence.isComplete(); |
| } |
| |
| /** |
| * Tests whether the current text widget has some text selection. |
| * |
| * @param <code>true</code> if the number of selected characters it greater |
| * than zero; <code>false</code> otherwise. |
| */ |
| private boolean hasSelection() { |
| return (text.getSelectionCount() > 0); |
| } |
| |
| /** |
| * Inserts the key stroke at the current insertion point. This does a |
| * regular delete and insert, as if the key had been pressed. |
| * |
| * @param stroke |
| * The key stroke to insert; must not be <code>null</code>. |
| */ |
| public void insert(KeyStroke stroke) { |
| if (!stroke.isComplete()) { |
| return; |
| } |
| |
| // Copy the key strokes in the current key sequence. |
| List keyStrokes = new ArrayList(getKeySequence().getKeyStrokes()); |
| |
| if ((hasIncompleteStroke()) && (!keyStrokes.isEmpty())) { |
| keyStrokes.remove(keyStrokes.size() - 1); |
| } |
| |
| int index = deleteSelection(keyStrokes, false); |
| insertStrokeAt(keyStrokes, stroke, index); |
| keyFilter.clearInsertionIndex(); |
| setKeySequence(KeySequence.getInstance(keyStrokes)); |
| } |
| |
| /** |
| * Inserts the stroke at the given index in the list of strokes. If the |
| * stroke currently at that index is incomplete, then it tries to merge the |
| * two strokes. If merging is a complete failure (unlikely), then it will |
| * simply overwrite the incomplete stroke. If the stroke at the index is |
| * complete, then it simply inserts the stroke independently. |
| * |
| * @param keyStrokes |
| * The list of key strokes in which the key stroke should be |
| * appended; must not be <code>null</code>. |
| * @param stroke |
| * The stroke to insert; should not be <code>null</code>. |
| * @param index |
| * The index at which to insert; must be a valid index into the |
| * list of key strokes. |
| */ |
| private void insertStrokeAt(List keyStrokes, KeyStroke stroke, int index) { |
| KeyStroke currentStroke = |
| (index >= keyStrokes.size()) |
| ? null |
| : (KeyStroke) keyStrokes.get(index); |
| if ((currentStroke != null) && (!currentStroke.isComplete())) { |
| SortedSet modifierKeys = |
| new TreeSet(currentStroke.getModifierKeys()); |
| NaturalKey naturalKey = stroke.getNaturalKey(); |
| modifierKeys.addAll(stroke.getModifierKeys()); |
| keyStrokes.remove(index); |
| keyStrokes.add( |
| index, |
| KeyStroke.getInstance(modifierKeys, naturalKey)); |
| } else { |
| keyStrokes.add(index, stroke); |
| } |
| } |
| |
| /** |
| * Tests whether the cursor is in the last position. This means that the |
| * selection extends to the last position. |
| * |
| * @return <code>true</code> if the selection extends to the last |
| * position; <code>false</code> otherwise. |
| */ |
| private boolean isCursorInLastPosition() { |
| return (text.getSelection().y >= getText().length()); |
| } |
| |
| /** |
| * <p> |
| * A mutator for the key sequence stored within this widget. The text and |
| * caret position are updated. |
| * </p> |
| * <p> |
| * All sequences are limited to maxStrokes number of strokes in length. If |
| * there are already that number of strokes, then it does not show |
| * incomplete strokes, and does not keep track of them. |
| * </p> |
| * |
| * @param newKeySequence |
| * The new key sequence for this widget; may be <code>null</code> |
| * if none. |
| */ |
| public void setKeySequence(KeySequence newKeySequence) { |
| keySequence = newKeySequence; |
| |
| // Trim any extra strokes. |
| if (maxStrokes != INFINITE) { |
| List keyStrokes = new ArrayList(keySequence.getKeyStrokes()); |
| int keyStrokesSize = keyStrokes.size(); |
| for (int i = keyStrokesSize - 1; i >= maxStrokes; i--) { |
| keyStrokes.remove(i); |
| } |
| keySequence = KeySequence.getInstance(keyStrokes); |
| } |
| |
| // Check to see if the text has changed. |
| String currentString = getText(); |
| String newString = keySequence.format(); |
| if (!currentString.equals(newString)) { |
| // We need to update the text |
| text.removeModifyListener(updateSequenceListener); |
| text.setText(keySequence.format()); |
| text.addModifyListener(updateSequenceListener); |
| text.setSelection(getText().length()); |
| } |
| } |
| |
| /** |
| * Returns the maximum number of strokes that are permitted in this widget |
| * at one time. |
| * |
| * @return The maximum number of strokes; will be a positive integer or |
| * <code>INFINITE</code>. |
| */ |
| public int getKeyStrokeLimit() { |
| return maxStrokes; |
| } |
| |
| /** |
| * A mutator for the maximum number of strokes that are permitted in this |
| * widget at one time. |
| * |
| * @param keyStrokeLimit |
| * The maximum number of strokes; must be a positive integer or |
| * <code>INFINITE</code>. |
| */ |
| public void setKeyStrokeLimit(int keyStrokeLimit) { |
| if (keyStrokeLimit > 0 || keyStrokeLimit == INFINITE) |
| this.maxStrokes = keyStrokeLimit; |
| else |
| throw new IllegalArgumentException(); |
| |
| // Make sure we are obeying the new limit. |
| setKeySequence(getKeySequence()); |
| } |
| } |