KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > bindings > keys > KeySequenceText


1 /*******************************************************************************
2  * Copyright (c) 2004, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.jface.bindings.keys;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.Collection JavaDoc;
16 import java.util.Collections JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.TreeSet JavaDoc;
20
21 import org.eclipse.jface.util.IPropertyChangeListener;
22 import org.eclipse.jface.util.PropertyChangeEvent;
23 import org.eclipse.swt.SWT;
24 import org.eclipse.swt.events.DisposeEvent;
25 import org.eclipse.swt.events.DisposeListener;
26 import org.eclipse.swt.events.FocusEvent;
27 import org.eclipse.swt.events.FocusListener;
28 import org.eclipse.swt.events.ModifyEvent;
29 import org.eclipse.swt.events.ModifyListener;
30 import org.eclipse.swt.graphics.Font;
31 import org.eclipse.swt.graphics.Point;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.swt.widgets.Event;
34 import org.eclipse.swt.widgets.Listener;
35 import org.eclipse.swt.widgets.Text;
36
37 /**
38  * <p>
39  * A wrapper around the SWT text widget that traps literal key presses and
40  * converts them into key sequences for display. There are two types of key
41  * strokes that are displayed: complete and incomplete. A complete key stroke is
42  * one with a natural key, while an incomplete one has no natural key.
43  * Incomplete key strokes are only displayed until they are made complete or
44  * their component key presses are released.
45  * </p>
46  *
47  * @since 3.1
48  */

49 public final class KeySequenceText {
50
51     /**
52      * A key listener that traps incoming events and displays them in the
53      * wrapped text field. It has no effect on traversal operations.
54      */

55     private class KeyTrapListener implements Listener {
56         /**
57          * The index at which insertion should occur. This is used if there is a
58          * replacement occurring in the middle of the stroke, and the first key
59          * stroke was incomplete.
60          */

61         private int insertionIndex = -1;
62
63         /**
64          * Resets the insertion index to point nowhere. In other words, it is
65          * set to <code>-1</code>.
66          */

67         void clearInsertionIndex() {
68             insertionIndex = -1;
69         }
70
71         /**
72          * Deletes the current selection. If there is no selection, then it
73          * deletes the last key stroke.
74          *
75          * @param keyStrokes
76          * The key strokes from which to delete. This list must not
77          * be <code>null</code>, and must represent a valid key
78          * sequence.
79          */

80         private final KeyStroke[] deleteKeyStroke(final KeyStroke[] keyStrokes) {
81             clearInsertionIndex();
82
83             if (hasSelection()) {
84                 /*
85                  * Delete the current selection -- disallowing incomplete
86                  * strokes in the middle of the sequence.
87                  */

88                 final KeyStroke[][] deletedKeyStrokes = new KeyStroke[1][];
89                 deleteSelection(keyStrokes, false, deletedKeyStrokes);
90                 return deletedKeyStrokes[0];
91             }
92
93             // Remove the last key stroke.
94
if (keyStrokes.length > 0) {
95                 final int newKeyStrokesLength = keyStrokes.length - 1;
96                 final KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength];
97                 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
98                         newKeyStrokesLength);
99                 return newKeyStrokes;
100             }
101
102             return keyStrokes;
103         }
104
105         /**
106          * Handles the key pressed and released events on the wrapped text
107          * widget. This makes sure to either add the pressed key to the
108          * temporary key stroke, or complete the current temporary key stroke
109          * and prompt for the next. In the case of a key release, this makes
110          * sure that the temporary stroke is correctly displayed --
111          * corresponding with modifier keys that may have been released.
112          *
113          * @param event
114          * The triggering event; must not be <code>null</code>.
115          */

116         public void handleEvent(Event event) {
117             KeyStroke[] keyStrokes = getKeySequence().getKeyStrokes();
118
119             // Dispatch the event to the correct handler.
120
if (event.type == SWT.KeyDown) {
121                 keyStrokes = handleKeyDown(event, keyStrokes);
122             } else if (event.type == SWT.KeyUp) {
123                 keyStrokes = handleKeyUp(event, keyStrokes);
124             }
125
126             // Update the underlying widget.
127
setKeySequence(KeySequence.getInstance(keyStrokes));
128
129             // Prevent the event from reaching the widget.
130
event.doit = false;
131         }
132
133         /**
134          * Handles the case where the key event is an <code>SWT.KeyDown</code>
135          * event. This either causes a deletion (if it is an unmodified
136          * backspace key stroke), or an insertion (if it is any other key).
137          *
138          * @param event
139          * The trigger key down event; must not be <code>null</code>.
140          * @param keyStrokes
141          * The current list of key strokes. This valud must not be
142          * <code>null</code>, and it must represent a valid key
143          * sequence.
144          */

145         private KeyStroke[] handleKeyDown(Event event, KeyStroke[] keyStrokes) {
146             // Is it an unmodified backspace character?
147
if ((event.character == SWT.BS) && (event.stateMask == 0)) {
148                 return deleteKeyStroke(keyStrokes);
149             }
150
151             return insertKeyStroke(event, keyStrokes);
152         }
153
154         /**
155          * Handles the case where the key event is an <code>SWT.KeyUp</code>
156          * event. This resets the insertion index. If there is an incomplete
157          * stroke, then that incomplete stroke is modified to match the keys
158          * that are still held. If no keys are held, then the incomplete stroke
159          * is removed.
160          *
161          * @param event
162          * The triggering event; must not be <code>null</code>
163          * @param keyStrokes
164          * The key strokes that are part of the current key sequence;
165          * these key strokes are guaranteed to represent a valid key
166          * sequence. This value must not be <code>null</code>.
167          */

168         private final KeyStroke[] handleKeyUp(final Event event,
169                 final KeyStroke[] keyStrokes) {
170             if (hasIncompleteStroke()) {
171                 /*
172                  * Figure out the SWT integer representation of the remaining
173                  * values.
174                  */

175                 Event mockEvent = new Event();
176                 if ((event.keyCode & SWT.MODIFIER_MASK) != 0) {
177                     // This key up is a modifier key being released.
178
mockEvent.stateMask = event.stateMask - event.keyCode;
179                 } else {
180                     /*
181                      * This key up is the other end of a key down that was
182                      * trapped by the operating system or window manager.
183                      */

184                     mockEvent.stateMask = event.stateMask;
185                 }
186
187                 /*
188                  * Get a reasonable facsimile of the stroke that is still
189                  * pressed.
190                  */

191                 int key = SWTKeySupport
192                         .convertEventToUnmodifiedAccelerator(mockEvent);
193                 KeyStroke remainingStroke = SWTKeySupport
194                         .convertAcceleratorToKeyStroke(key);
195                 final int keyStrokesLength = keyStrokes.length;
196                 final KeyStroke[] newKeyStrokes;
197                 if ((keyStrokesLength > 0)
198                         && (remainingStroke.getModifierKeys() != 0)) {
199                     newKeyStrokes = new KeyStroke[keyStrokesLength];
200                     System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
201                             keyStrokesLength - 1);
202                     newKeyStrokes[keyStrokesLength - 1] = remainingStroke;
203
204                 } else if (keyStrokesLength > 0) {
205                     newKeyStrokes = new KeyStroke[keyStrokesLength - 1];
206                     System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
207                             keyStrokesLength - 1);
208
209                 } else if (remainingStroke.getModifierKeys() != 0) {
210                     newKeyStrokes = new KeyStroke[keyStrokesLength + 1];
211                     System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
212                             keyStrokesLength);
213                     newKeyStrokes[keyStrokesLength] = remainingStroke;
214
215                 } else {
216                     newKeyStrokes = keyStrokes;
217
218                 }
219
220                 return newKeyStrokes;
221             }
222
223             return keyStrokes;
224         }
225
226         /**
227          * <p>
228          * Handles the case where a key down event is leading to a key stroke
229          * being inserted. The current selection is deleted, and an invalid
230          * remanents of the stroke are also removed. The insertion is carried
231          * out at the cursor position.
232          * </p>
233          * <p>
234          * If only a natural key is selected (as part of a larger key stroke),
235          * then it is possible for the user to press a natural key to replace
236          * the old natural key. In this situation, pressing any modifier keys
237          * will replace the whole thing.
238          * </p>
239          * <p>
240          * If the insertion point is not at the end of the sequence, then
241          * incomplete strokes will not be immediately inserted. Only when the
242          * sequence is completed is the stroke inserted. This is a requirement
243          * as the widget must always represent a valid key sequence. The
244          * insertion point is tracked using <code>insertionIndex</code>,
245          * which is an index into the key stroke array.
246          * </p>
247          *
248          * @param event
249          * The triggering key down event; must not be
250          * <code>null</code>.
251          * @param keyStrokes
252          * The key strokes into which the current stroke should be
253          * inserted. This value must not be <code>null</code>, and
254          * must represent a valid key sequence.
255          */

256         private final KeyStroke[] insertKeyStroke(final Event event,
257                 KeyStroke[] keyStrokes) {
258             // Compute the key stroke to insert.
259
int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(event);
260             KeyStroke stroke = SWTKeySupport.convertAcceleratorToKeyStroke(key);
261
262             /*
263              * Only insert the stroke if it is *not ScrollLock. Let's not get
264              * silly
265              */

266             if ((SWT.NUM_LOCK == stroke.getNaturalKey())
267                     || (SWT.CAPS_LOCK == stroke.getNaturalKey())
268                     || (SWT.SCROLL_LOCK == stroke.getNaturalKey())) {
269                 return keyStrokes;
270             }
271
272             if (insertionIndex != -1) {
273                 // There is a previous replacement still going on.
274
if (stroke.isComplete()) {
275                     keyStrokes = insertStrokeAt(keyStrokes, stroke,
276                             insertionIndex);
277                     clearInsertionIndex();
278                 }
279
280             } else if (hasSelection()) {
281                 // There is a selection that needs to be replaced.
282
final KeyStroke[][] deletedKeyStrokes = new KeyStroke[1][];
283                 insertionIndex = deleteSelection(keyStrokes, stroke
284                         .isComplete(), deletedKeyStrokes);
285                 keyStrokes = deletedKeyStrokes[0];
286                 if ((stroke.isComplete())
287                         || (insertionIndex >= keyStrokes.length)) {
288                     keyStrokes = insertStrokeAt(keyStrokes, stroke,
289                             insertionIndex);
290                     clearInsertionIndex();
291                 }
292
293             } else {
294                 // No selection, so remove the incomplete stroke, if any
295
if ((hasIncompleteStroke()) && (keyStrokes.length > 0)) {
296                     final KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokes.length - 1];
297                     System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
298                             keyStrokes.length - 1);
299                     keyStrokes = newKeyStrokes;
300                 }
301
302                 // And then add the new stroke.
303
if ((keyStrokes.length == 0)
304                         || (insertionIndex >= keyStrokes.length)
305                         || (isCursorInLastPosition())) {
306                     keyStrokes = insertStrokeAt(keyStrokes, stroke,
307                             keyStrokes.length);
308                     clearInsertionIndex();
309                 } else {
310                     /*
311                      * I'm just getting the insertionIndex here. No actual
312                      * deletion should occur.
313                      */

314                     final KeyStroke[][] deletedKeyStrokes = new KeyStroke[1][];
315                     insertionIndex = deleteSelection(keyStrokes, stroke
316                             .isComplete(), deletedKeyStrokes);
317                     keyStrokes = deletedKeyStrokes[0];
318                     if (stroke.isComplete()) {
319                         keyStrokes = insertStrokeAt(keyStrokes, stroke,
320                                 insertionIndex);
321                         clearInsertionIndex();
322                     }
323                 }
324
325             }
326
327             return keyStrokes;
328         }
329     }
330
331     /**
332      * A traversal listener that blocks all traversal except for tabs and arrow
333      * keys.
334      */

335     private class TraversalFilter implements Listener {
336         /**
337          * Handles the traverse event on the text field wrapped by this class.
338          * It swallows all traverse events example for tab and arrow key
339          * navigation. The other forms of navigation can be reached by tabbing
340          * off of the control.
341          *
342          * @param event
343          * The trigger event; must not be <code>null</code>.
344          */

345         public void handleEvent(Event event) {
346             switch (event.detail) {
347             case SWT.TRAVERSE_ESCAPE:
348             case SWT.TRAVERSE_MNEMONIC:
349             case SWT.TRAVERSE_NONE:
350             case SWT.TRAVERSE_PAGE_NEXT:
351             case SWT.TRAVERSE_PAGE_PREVIOUS:
352             case SWT.TRAVERSE_RETURN:
353                 event.type = SWT.None;
354                 event.doit = false;
355                 break;
356
357             case SWT.TRAVERSE_TAB_NEXT:
358             case SWT.TRAVERSE_TAB_PREVIOUS:
359                 // Check if modifiers other than just 'Shift' were
360
// down.
361
if ((event.stateMask & (SWT.MODIFIER_MASK ^ SWT.SHIFT)) != 0) {
362                     // Modifiers other than shift were down.
363
event.type = SWT.None;
364                     event.doit = false;
365                     break;
366                 }
367
368                 // fall through -- either no modifiers, or just shift.
369

370             case SWT.TRAVERSE_ARROW_NEXT:
371             case SWT.TRAVERSE_ARROW_PREVIOUS:
372             default:
373                 // Let the traversal happen, but clear the incomplete
374
// stroke
375
if (hasIncompleteStroke()) {
376                     final KeyStroke[] oldKeyStrokes = getKeySequence()
377                             .getKeyStrokes();
378                     final int newKeyStrokesLength = oldKeyStrokes.length - 1;
379                     if (newKeyStrokesLength >= 1) {
380                         final KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength];
381                         System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
382                                 newKeyStrokesLength);
383                         setKeySequence(KeySequence.getInstance(newKeyStrokes));
384                     } else {
385                         setKeySequence(KeySequence.getInstance());
386                     }
387                 }
388             }
389
390         }
391     }
392
393     /**
394      * The manager resposible for installing and removing the traversal filter
395      * when the key sequence entry widget gains and loses focus.
396      */

397     private class TraversalFilterManager implements FocusListener {
398         /** The managed filter. We only need one instance. */
399         private TraversalFilter filter = new TraversalFilter();
400         
401         private boolean filtering = false;
402
403         /**
404          * Attaches the global traversal filter.
405          *
406          * @param event
407          * Ignored.
408          */

409         public void focusGained(FocusEvent event) {
410             Display.getCurrent().addFilter(SWT.Traverse, filter);
411             filtering = true;
412         }
413
414         /**
415          * Detaches the global traversal filter.
416          *
417          * @param event
418          * Ignored.
419          */

420         public void focusLost(FocusEvent event) {
421             Display.getCurrent().removeFilter(SWT.Traverse, filter);
422             filtering = false;
423         }
424         
425         /**
426          * Remove the traverse filter if we close without focusOut.
427          */

428         public void dispose() {
429             if (filtering) {
430                 Display.getCurrent().removeFilter(SWT.Traverse, filter);
431             }
432         }
433     }
434
435     /**
436      * A modification listener that makes sure that external events to this
437      * class (i.e., direct modification of the underlying text) do not break
438      * this class' view of the world.
439      */

440     private class UpdateSequenceListener implements ModifyListener {
441         /**
442          * Handles the modify event on the underlying text widget.
443          *
444          * @param event
445          * The triggering event; ignored.
446          */

447         public void modifyText(ModifyEvent event) {
448             try {
449                 // The original sequence.
450
KeySequence originalSequence = getKeySequence();
451
452                 // The new sequence drawn from the text.
453
String JavaDoc contents = getText();
454                 KeySequence newSequence = KeySequence.getInstance(contents);
455
456                 // Check to see if they're the same.
457
if (!originalSequence.equals(newSequence)) {
458                     setKeySequence(newSequence);
459                 }
460
461             } catch (ParseException e) {
462                 // Abort any cut/paste-driven modifications
463
setKeySequence(getKeySequence());
464             }
465         }
466     }
467
468     static {
469         TreeSet JavaDoc trappedKeys = new TreeSet JavaDoc();
470         trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.TAB));
471         trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.TAB
472                 | SWT.SHIFT));
473         trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.BS));
474         List JavaDoc trappedKeyList = new ArrayList JavaDoc(trappedKeys);
475         TRAPPED_KEYS = Collections.unmodifiableList(trappedKeyList);
476     }
477
478     /** An empty string instance for use in clearing text values. */
479     private static final String JavaDoc EMPTY_STRING = ""; //$NON-NLS-1$
480

481     /**
482      * The special integer value for the maximum number of strokes indicating
483      * that an infinite number should be allowed.
484      */

485     public static final int INFINITE = -1;
486
487     /**
488      * The name of the property representing the current key sequence in this
489      * key sequence widget.
490      *
491      * @since 3.2
492      */

493     public static final String JavaDoc P_KEY_SEQUENCE = "org.eclipse.jface.bindings.keys.KeySequenceText.KeySequence"; //$NON-NLS-1$
494

495     /**
496      * The keys trapped by this widget. This list is guaranteed to be roughly
497      * accurate. Perfection is not possible, as SWT does not export traversal
498      * keys as constants.
499      */

500     public static final List JavaDoc TRAPPED_KEYS;
501
502     /**
503      * The key filter attached to the underlying widget that traps key events.
504      */

505     private final KeyTrapListener keyFilter = new KeyTrapListener();
506
507     /**
508      * The text of the key sequence -- containing only the complete key strokes.
509      */

510     private KeySequence keySequence = KeySequence.getInstance();
511
512     /**
513      * Those listening to changes to the key sequence in this widget. This value
514      * may be <code>null</code> if there are no listeners.
515      */

516     private Collection JavaDoc listeners = null;
517
518     /** The maximum number of key strokes permitted in the sequence. */
519     private int maxStrokes = INFINITE;
520
521     /** The text widget that is wrapped for this class. */
522     private final Text text;
523
524     /**
525      * The listener that makes sure that the text widget remains up-to-date with
526      * regards to external modification of the text (e.g., cut & pasting).
527      */

528     private final UpdateSequenceListener updateSequenceListener = new UpdateSequenceListener();
529
530     /**
531      * Constructs an instance of <code>KeySequenceTextField</code> with the
532      * text field to use. If the platform is carbon (MacOS X), then the font is
533      * set to be the same font used to display accelerators in the menus.
534      *
535      * @param wrappedText
536      * The text widget to wrap; must not be <code>null</code>.
537      */

538     public KeySequenceText(Text wrappedText) {
539         text = wrappedText;
540
541         // Set the font if the platform is carbon.
542
if ("carbon".equals(SWT.getPlatform())) { //$NON-NLS-1$
543
// Don't worry about this font name here; it is the official menu
544
// font and point size on the Mac.
545
final Font font = new Font(text.getDisplay(),
546                     "Lucida Grande", 13, SWT.NORMAL); //$NON-NLS-1$
547
text.setFont(font);
548             text.addDisposeListener(new DisposeListener() {
549                 public void widgetDisposed(DisposeEvent e) {
550                     font.dispose();
551                 }
552             });
553         }
554
555         // Add the key listener.
556
text.addListener(SWT.KeyUp, keyFilter);
557         text.addListener(SWT.KeyDown, keyFilter);
558
559         final TraversalFilterManager traversalFilterManager = new TraversalFilterManager();
560         text.addFocusListener(traversalFilterManager);
561         text.addDisposeListener(new DisposeListener() {
562             public void widgetDisposed(DisposeEvent e) {
563                 traversalFilterManager.dispose();
564             }
565         });
566
567         // Add an internal modify listener.
568
text.addModifyListener(updateSequenceListener);
569     }
570
571     /**
572      * Adds a property change listener to this key sequence widget. It will be
573      * notified when the key sequence changes.
574      *
575      * @param listener
576      * The listener to be notified when changes occur; must not be
577      * <code>null</code>.
578      * @since 3.2
579      */

580     public final void addPropertyChangeListener(
581             final IPropertyChangeListener listener) {
582         if (listener == null) {
583             return;
584         }
585
586         if (listeners == null) {
587             listeners = new ArrayList JavaDoc(1);
588         }
589
590         listeners.add(listener);
591     }
592
593     /**
594      * Clears the text field and resets all the internal values.
595      */

596     public void clear() {
597         final KeySequence oldKeySequence = keySequence;
598         keySequence = KeySequence.getInstance();
599         text.setText(EMPTY_STRING);
600         firePropertyChangeEvent(oldKeySequence);
601     }
602
603     /**
604      * Removes the key strokes from the list corresponding the selection. If
605      * <code>allowIncomplete</code>, then invalid key sequences will be
606      * allowed (i.e., those with incomplete strokes in the non-terminal
607      * position). Otherwise, incomplete strokes will be removed. This modifies
608      * <code>keyStrokes</code> in place, and has no effect on the text widget
609      * this class wraps.
610      *
611      * @param keyStrokes
612      * The list of key strokes from which the selection should be
613      * removed; must not be <code>null</code>.
614      * @param allowIncomplete
615      * Whether incomplete strokes should be allowed to exist in the
616      * list after the deletion.
617      * @return The index at which a subsequent insert should occur. This index
618      * only has meaning to the <code>insertStrokeAt</code> method.
619      */

620     private final int deleteSelection(final KeyStroke[] keyStrokes,
621             final boolean allowIncomplete, final KeyStroke[][] deletedKeyStrokes) {
622         // Get the current selection.
623
Point selection = text.getSelection();
624         int start = selection.x;
625         int end = selection.y;
626
627         /*
628          * Using the key sequence format method, discover the point at which
629          * adding key strokes passes or equals the start of the selection. In
630          * other words, find the first stroke that is part of the selection.
631          * Keep track of the text range under which the stroke appears (i.e.,
632          * startTextIndex->string.length() is the first selected stroke).
633          */

634         String JavaDoc string = new String JavaDoc();
635         List JavaDoc currentStrokes = new ArrayList JavaDoc();
636         int startTextIndex = 0; // keeps track of the start of the stroke
637
final int keyStrokesLength = keyStrokes.length;
638         int i;
639         for (i = 0; (i < keyStrokesLength) && (string.length() < start); i++) {
640             startTextIndex = string.length();
641             currentStrokes.add(keyStrokes[i]);
642             string = KeySequence.getInstance(currentStrokes).format();
643         }
644
645         /*
646          * If string.length() == start, then the cursor is positioned between
647          * strokes (i.e., selection is outside of a stroke).
648          */

649         int startStrokeIndex;
650         if (string.length() == start) {
651             startStrokeIndex = currentStrokes.size();
652         } else {
653             startStrokeIndex = currentStrokes.size() - 1;
654         }
655
656         /*
657          * Check to see if the cursor is only positioned, rather than actually
658          * selecting something. We only need to compute the end if there is a
659          * selection.
660          */

661         int endStrokeIndex;
662         if (start == end) {
663             return startStrokeIndex;
664         }
665
666         for (; (i < keyStrokesLength) && (string.length() < end); i++) {
667             currentStrokes.add(keyStrokes[i]);
668             string = KeySequence.getInstance(currentStrokes).format();
669         }
670         endStrokeIndex = currentStrokes.size() - 1;
671         if (endStrokeIndex < 0) {
672             endStrokeIndex = 0;
673         }
674
675         /*
676          * Remove the strokes that are touched by the selection. Keep track of
677          * the first stroke removed.
678          */

679         final int newLength = endStrokeIndex - startStrokeIndex + 1;
680         deletedKeyStrokes[0] = new KeyStroke[newLength];
681         final KeyStroke startStroke = keyStrokes[startStrokeIndex];
682         System.arraycopy(keyStrokes, 0, keyStrokes, 0, newLength);
683
684         /*
685          * Allow the first stroke removed to be replaced by an incomplete
686          * stroke.
687          */

688         if (allowIncomplete) {
689             final int modifierKeys = startStroke.getModifierKeys();
690             KeyStroke incompleteStroke = KeyStroke.getInstance(modifierKeys,
691                     KeyStroke.NO_KEY);
692             int incompleteStrokeLength = incompleteStroke.format().length();
693             if ((startTextIndex + incompleteStrokeLength) <= start) {
694                 final KeyStroke[] added = new KeyStroke[newLength + 1];
695                 System.arraycopy(deletedKeyStrokes[0], 0, added, 0,
696                         startStrokeIndex);
697                 added[startStrokeIndex] = incompleteStroke;
698                 System.arraycopy(deletedKeyStrokes[0], startStrokeIndex, added,
699                         startStrokeIndex + 1, newLength);
700                 deletedKeyStrokes[0] = added;
701             }
702         }
703
704         return startStrokeIndex;
705     }
706
707     /**
708      * Fires a property change event to all of the listeners.
709      *
710      * @param oldKeySequence
711      * The old key sequence; must not be <code>null</code>.
712      * @since 3.2
713      */

714     protected final void firePropertyChangeEvent(
715             final KeySequence oldKeySequence) {
716         if (listeners != null) {
717             final Iterator JavaDoc listenerItr = listeners.iterator();
718             final PropertyChangeEvent event = new PropertyChangeEvent(this,
719                     P_KEY_SEQUENCE, oldKeySequence, getKeySequence());
720             while (listenerItr.hasNext()) {
721                 final IPropertyChangeListener listener = (IPropertyChangeListener) listenerItr
722                         .next();
723                 listener.propertyChange(event);
724             }
725         }
726     }
727
728     /**
729      * An accessor for the <code>KeySequence</code> that corresponds to the
730      * current state of the text field. This includes incomplete strokes.
731      *
732      * @return The key sequence representation; never <code>null</code>.
733      */

734     public KeySequence getKeySequence() {
735         return keySequence;
736     }
737
738     /**
739      * An accessor for the underlying text widget's contents.
740      *
741      * @return The text contents of this entry; never <code>null</code>.
742      */

743     private String JavaDoc getText() {
744         return text.getText();
745     }
746
747     /**
748      * Tests whether the current key sequence has a stroke with no natural key.
749      *
750      * @return <code>true</code> is there is an incomplete stroke;
751      * <code>false</code> otherwise.
752      */

753     private boolean hasIncompleteStroke() {
754         return !keySequence.isComplete();
755     }
756
757     /**
758      * Tests whether the current text widget has some text selection.
759      *
760      * @return <code>true</code> if the number of selected characters it
761      * greater than zero; <code>false</code> otherwise.
762      */

763     private boolean hasSelection() {
764         return (text.getSelectionCount() > 0);
765     }
766
767     /**
768      * Inserts the key stroke at the current insertion point. This does a
769      * regular delete and insert, as if the key had been pressed.
770      *
771      * @param stroke
772      * The key stroke to insert; must not be <code>null</code>.
773      */

774     public void insert(KeyStroke stroke) {
775         if (!stroke.isComplete()) {
776             return;
777         }
778
779         // Copy the key strokes in the current key sequence.
780
final KeySequence keySequence = getKeySequence();
781         final KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
782         final KeyStroke[] newKeyStrokes;
783         if ((hasIncompleteStroke()) && (!keySequence.isEmpty())) {
784             final int newKeyStrokesLength = oldKeyStrokes.length - 1;
785             newKeyStrokes = new KeyStroke[newKeyStrokesLength];
786             System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
787                     newKeyStrokesLength);
788         } else {
789             newKeyStrokes = oldKeyStrokes;
790         }
791
792         KeyStroke[][] deletedKeyStrokes = new KeyStroke[1][];
793         int index = deleteSelection(newKeyStrokes, false, deletedKeyStrokes);
794         if (index == -1) {
795             index = 0;
796         }
797
798         final KeyStroke[] keyStrokes = insertStrokeAt(newKeyStrokes, stroke, index);
799         keyFilter.clearInsertionIndex();
800         setKeySequence(KeySequence.getInstance(keyStrokes));
801     }
802
803     /**
804      * Inserts the stroke at the given index in the list of strokes. If the
805      * stroke currently at that index is incomplete, then it tries to merge the
806      * two strokes. If merging is a complete failure (unlikely), then it will
807      * simply overwrite the incomplete stroke. If the stroke at the index is
808      * complete, then it simply inserts the stroke independently.
809      *
810      * @param keyStrokes
811      * The list of key strokes in which the key stroke should be
812      * appended; must not be <code>null</code>.
813      * @param stroke
814      * The stroke to insert; should not be <code>null</code>.
815      * @param index
816      * The index at which to insert; must be a valid index into the
817      * list of key strokes.
818      */

819     private final KeyStroke[] insertStrokeAt(final KeyStroke[] keyStrokes,
820             KeyStroke stroke, int index) {
821         final int keyStrokesLength = keyStrokes.length;
822         final KeyStroke currentStroke = (index >= keyStrokesLength) ? null
823                 : keyStrokes[index];
824         if ((currentStroke != null) && (!currentStroke.isComplete())) {
825             int modifierKeys = currentStroke.getModifierKeys();
826             final int naturalKey = stroke.getNaturalKey();
827             modifierKeys |= stroke.getModifierKeys();
828             keyStrokes[index] = KeyStroke.getInstance(modifierKeys, naturalKey);
829             return keyStrokes;
830         }
831
832         final KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokesLength + 1];
833         System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, index);
834         newKeyStrokes[index] = stroke;
835         if (index < keyStrokesLength) {
836             System.arraycopy(keyStrokes, index, newKeyStrokes, index + 1,
837                     keyStrokesLength-index);
838         }
839         return newKeyStrokes;
840     }
841
842     /**
843      * Tests whether the cursor is in the last position. This means that the
844      * selection extends to the last position.
845      *
846      * @return <code>true</code> if the selection extends to the last
847      * position; <code>false</code> otherwise.
848      */

849     private boolean isCursorInLastPosition() {
850         return (text.getSelection().y >= getText().length());
851     }
852
853     /**
854      * Removes the given listener from this key sequence widget.
855      *
856      * @param listener
857      * The listener to be removed; must not be <code>null</code>.
858      * @since 3.2
859      */

860     public final void removePropertyChangeListener(
861             final IPropertyChangeListener listener) {
862         if ((listener == null) || (listeners == null)) {
863             return;
864         }
865
866         listeners.remove(listener);
867     }
868
869     /**
870      * <p>
871      * A mutator for the key sequence stored within this widget. The text and
872      * caret position are updated.
873      * </p>
874      * <p>
875      * All sequences are limited to maxStrokes number of strokes in length. If
876      * there are already that number of strokes, then it does not show
877      * incomplete strokes, and does not keep track of them.
878      * </p>
879      *
880      * @param newKeySequence
881      * The new key sequence for this widget; may be <code>null</code>
882      * if none.
883      */

884     public void setKeySequence(KeySequence newKeySequence) {
885         final KeySequence oldKeySequence = keySequence;
886         keySequence = newKeySequence;
887
888         // Trim any extra strokes.
889
if (maxStrokes != INFINITE) {
890             final KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
891             if (maxStrokes < oldKeyStrokes.length) {
892                 final KeyStroke[] newKeyStrokes = new KeyStroke[maxStrokes];
893                 System
894                         .arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
895                                 maxStrokes);
896                 keySequence = KeySequence.getInstance(newKeyStrokes);
897             }
898         }
899
900         // Check to see if the text has changed.
901
String JavaDoc currentString = getText();
902         String JavaDoc newString = keySequence.format();
903         if (!currentString.equals(newString)) {
904             // We need to update the text
905
text.removeModifyListener(updateSequenceListener);
906             text.setText(keySequence.format());
907             text.addModifyListener(updateSequenceListener);
908             text.setSelection(getText().length());
909         }
910
911         firePropertyChangeEvent(oldKeySequence);
912     }
913
914     /**
915      * Returns the maximum number of strokes that are permitted in this widget
916      * at one time.
917      *
918      * @return The maximum number of strokes; will be a positive integer or
919      * <code>INFINITE</code>.
920      */

921     public int getKeyStrokeLimit() {
922         return maxStrokes;
923     }
924
925     /**
926      * A mutator for the maximum number of strokes that are permitted in this
927      * widget at one time.
928      *
929      * @param keyStrokeLimit
930      * The maximum number of strokes; must be a positive integer or
931      * <code>INFINITE</code>.
932      */

933     public void setKeyStrokeLimit(int keyStrokeLimit) {
934         if (keyStrokeLimit > 0 || keyStrokeLimit == INFINITE) {
935             this.maxStrokes = keyStrokeLimit;
936         } else {
937             throw new IllegalArgumentException JavaDoc();
938         }
939
940         // Make sure we are obeying the new limit.
941
setKeySequence(getKeySequence());
942     }
943 }
944
Popular Tags