comparison org.eclipse.jface/src/org/eclipse/jface/bindings/keys/KeySequenceText.d @ 12:bc29606a740c

Added dwt-addons in original directory structure of eclipse.org
author Frank Benoit <benoit@tionex.de>
date Sat, 14 Mar 2009 18:23:29 +0100
parents
children
comparison
equal deleted inserted replaced
11:43904fec5dca 12:bc29606a740c
1 /*******************************************************************************
2 * Copyright (c) 2004, 2008 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 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13
14 module org.eclipse.jface.bindings.keys.KeySequenceText;
15
16 import org.eclipse.jface.bindings.keys.KeyStroke;
17 import org.eclipse.jface.bindings.keys.SWTKeySupport;
18 import org.eclipse.jface.bindings.keys.KeySequence;
19
20
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.events.DisposeEvent;
23 import org.eclipse.swt.events.DisposeListener;
24 import org.eclipse.swt.events.FocusEvent;
25 import org.eclipse.swt.events.FocusListener;
26 import org.eclipse.swt.events.ModifyEvent;
27 import org.eclipse.swt.events.ModifyListener;
28 import org.eclipse.swt.graphics.Font;
29 import org.eclipse.swt.graphics.Device;
30 import org.eclipse.swt.graphics.Point;
31 import org.eclipse.swt.widgets.Display;
32 import org.eclipse.swt.widgets.Event;
33 import org.eclipse.swt.widgets.Listener;
34 import org.eclipse.swt.widgets.Text;
35 import org.eclipse.jface.util.IPropertyChangeListener;
36 import org.eclipse.jface.util.PropertyChangeEvent;
37
38 import java.lang.all;
39 import java.util.Collections;
40 import java.util.Collection;
41 import java.util.List;
42 import java.util.ArrayList;
43 import java.util.Iterator;
44 import java.util.Set;
45 import java.util.TreeSet;
46 import java.text.ParseException;
47
48 /**
49 * <p>
50 * A wrapper around the SWT text widget that traps literal key presses and
51 * converts them into key sequences for display. There are two types of key
52 * strokes that are displayed: complete and incomplete. A complete key stroke is
53 * one with a natural key, while an incomplete one has no natural key.
54 * Incomplete key strokes are only displayed until they are made complete or
55 * their component key presses are released.
56 * </p>
57 *
58 * @since 3.1
59 */
60 public final class KeySequenceText {
61
62 /**
63 * A key listener that traps incoming events and displays them in the
64 * wrapped text field. It has no effect on traversal operations.
65 */
66 private class KeyTrapListener : Listener {
67 /**
68 * The index at which insertion should occur. This is used if there is a
69 * replacement occurring in the middle of the stroke, and the first key
70 * stroke was incomplete.
71 */
72 private int insertionIndex = -1;
73
74 /**
75 * Resets the insertion index to point nowhere. In other words, it is
76 * set to <code>-1</code>.
77 */
78 void clearInsertionIndex() {
79 insertionIndex = -1;
80 }
81
82 /**
83 * Deletes the current selection. If there is no selection, then it
84 * deletes the last key stroke.
85 *
86 * @param keyStrokes
87 * The key strokes from which to delete. This list must not
88 * be <code>null</code>, and must represent a valid key
89 * sequence.
90 * @return An array of keystrokes minus the keystrokes that were
91 * deleted.
92 */
93 private final KeyStroke[] deleteKeyStroke(KeyStroke[] keyStrokes) {
94 clearInsertionIndex();
95
96 if (hasSelection()) {
97 /*
98 * Delete the current selection -- disallowing incomplete
99 * strokes in the middle of the sequence.
100 */
101 KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
102 deleteSelection(keyStrokes, false, deletedKeyStrokes);
103 return deletedKeyStrokes[0];
104 }
105
106 // Remove the last key stroke.
107 if (keyStrokes.length > 0) {
108 int newKeyStrokesLength = keyStrokes.length - 1;
109 KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength];
110 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
111 newKeyStrokesLength);
112 return newKeyStrokes;
113 }
114
115 return keyStrokes;
116 }
117
118 /**
119 * Handles the key pressed and released events on the wrapped text
120 * widget. This makes sure to either add the pressed key to the
121 * temporary key stroke, or complete the current temporary key stroke
122 * and prompt for the next. In the case of a key release, this makes
123 * sure that the temporary stroke is correctly displayed --
124 * corresponding with modifier keys that may have been released.
125 *
126 * @param event
127 * The triggering event; must not be <code>null</code>.
128 */
129 public void handleEvent(Event event) {
130 KeyStroke[] keyStrokes = getKeySequence().getKeyStrokes();
131
132 // Dispatch the event to the correct handler.
133 if (event.type is SWT.KeyDown) {
134 keyStrokes = handleKeyDown(event, keyStrokes);
135 } else if (event.type is SWT.KeyUp) {
136 keyStrokes = handleKeyUp(event, keyStrokes);
137 }
138
139 // Update the underlying widget.
140 setKeySequence(KeySequence.getInstance(keyStrokes));
141
142 // Prevent the event from reaching the widget.
143 event.doit = false;
144 }
145
146 /**
147 * Handles the case where the key event is an <code>SWT.KeyDown</code>
148 * event. This either causes a deletion (if it is an unmodified
149 * backspace key stroke), or an insertion (if it is any other key).
150 *
151 * @param event
152 * The trigger key down event; must not be <code>null</code>.
153 * @param keyStrokes
154 * The current list of key strokes. This valud must not be
155 * <code>null</code>, and it must represent a valid key
156 * sequence.
157 */
158 private KeyStroke[] handleKeyDown(Event event, KeyStroke[] keyStrokes) {
159 // Is it an unmodified backspace character?
160 if ((event.character is SWT.BS || event.character is SWT.DEL) && (event.stateMask is 0)) {
161 return deleteKeyStroke(keyStrokes);
162 }
163
164 return insertKeyStroke(event, keyStrokes);
165 }
166
167 /**
168 * Handles the case where the key event is an <code>SWT.KeyUp</code>
169 * event. This resets the insertion index. If there is an incomplete
170 * stroke, then that incomplete stroke is modified to match the keys
171 * that are still held. If no keys are held, then the incomplete stroke
172 * is removed.
173 *
174 * @param event
175 * The triggering event; must not be <code>null</code>
176 * @param keyStrokes
177 * The key strokes that are part of the current key sequence;
178 * these key strokes are guaranteed to represent a valid key
179 * sequence. This value must not be <code>null</code>.
180 */
181 private final KeyStroke[] handleKeyUp(Event event,
182 KeyStroke[] keyStrokes) {
183 if (hasIncompleteStroke()) {
184 /*
185 * Figure out the SWT integer representation of the remaining
186 * values.
187 */
188 Event mockEvent = new Event();
189 if ((event.keyCode & SWT.MODIFIER_MASK) !is 0) {
190 // This key up is a modifier key being released.
191 mockEvent.stateMask = event.stateMask - event.keyCode;
192 } else {
193 /*
194 * This key up is the other end of a key down that was
195 * trapped by the operating system or window manager.
196 */
197 mockEvent.stateMask = event.stateMask;
198 }
199
200 /*
201 * Get a reasonable facsimile of the stroke that is still
202 * pressed.
203 */
204 int key = SWTKeySupport
205 .convertEventToUnmodifiedAccelerator(mockEvent);
206 KeyStroke remainingStroke = SWTKeySupport
207 .convertAcceleratorToKeyStroke(key);
208 int keyStrokesLength = keyStrokes.length;
209 KeyStroke[] newKeyStrokes;
210 if ((keyStrokesLength > 0)
211 && (remainingStroke.getModifierKeys() !is 0)) {
212 newKeyStrokes = new KeyStroke[keyStrokesLength];
213 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
214 keyStrokesLength - 1);
215 newKeyStrokes[keyStrokesLength - 1] = remainingStroke;
216
217 } else if (keyStrokesLength > 0) {
218 newKeyStrokes = new KeyStroke[keyStrokesLength - 1];
219 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
220 keyStrokesLength - 1);
221
222 } else if (remainingStroke.getModifierKeys() !is 0) {
223 newKeyStrokes = new KeyStroke[keyStrokesLength + 1];
224 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
225 keyStrokesLength);
226 newKeyStrokes[keyStrokesLength] = remainingStroke;
227
228 } else {
229 newKeyStrokes = keyStrokes;
230
231 }
232
233 return newKeyStrokes;
234 }
235
236 return keyStrokes;
237 }
238
239 /**
240 * <p>
241 * Handles the case where a key down event is leading to a key stroke
242 * being inserted. The current selection is deleted, and an invalid
243 * remanents of the stroke are also removed. The insertion is carried
244 * out at the cursor position.
245 * </p>
246 * <p>
247 * If only a natural key is selected (as part of a larger key stroke),
248 * then it is possible for the user to press a natural key to replace
249 * the old natural key. In this situation, pressing any modifier keys
250 * will replace the whole thing.
251 * </p>
252 * <p>
253 * If the insertion point is not at the end of the sequence, then
254 * incomplete strokes will not be immediately inserted. Only when the
255 * sequence is completed is the stroke inserted. This is a requirement
256 * as the widget must always represent a valid key sequence. The
257 * insertion point is tracked using <code>insertionIndex</code>,
258 * which is an index into the key stroke array.
259 * </p>
260 *
261 * @param event
262 * The triggering key down event; must not be
263 * <code>null</code>.
264 * @param keyStrokes
265 * The key strokes into which the current stroke should be
266 * inserted. This value must not be <code>null</code>, and
267 * must represent a valid key sequence.
268 */
269 private final KeyStroke[] insertKeyStroke(Event event,
270 KeyStroke[] keyStrokes) {
271 // Compute the key stroke to insert.
272 int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(event);
273 KeyStroke stroke = SWTKeySupport.convertAcceleratorToKeyStroke(key);
274
275 /*
276 * Only insert the stroke if it is *not ScrollLock. Let's not get
277 * silly
278 */
279 if ((SWT.NUM_LOCK is stroke.getNaturalKey())
280 || (SWT.CAPS_LOCK is stroke.getNaturalKey())
281 || (SWT.SCROLL_LOCK is stroke.getNaturalKey())) {
282 return keyStrokes;
283 }
284
285 if (insertionIndex !is -1) {
286 // There is a previous replacement still going on.
287 if (stroke.isComplete()) {
288 keyStrokes = insertStrokeAt(keyStrokes, stroke,
289 insertionIndex);
290 clearInsertionIndex();
291 }
292
293 } else if (hasSelection()) {
294 // There is a selection that needs to be replaced.
295 KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
296 insertionIndex = deleteSelection(keyStrokes, stroke
297 .isComplete(), deletedKeyStrokes);
298 keyStrokes = deletedKeyStrokes[0];
299 if ((stroke.isComplete())
300 || (insertionIndex >= keyStrokes.length)) {
301 keyStrokes = insertStrokeAt(keyStrokes, stroke,
302 insertionIndex);
303 clearInsertionIndex();
304 }
305
306 } else {
307 // No selection, so remove the incomplete stroke, if any
308 if ((hasIncompleteStroke()) && (keyStrokes.length > 0)) {
309 KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokes.length - 1];
310 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
311 keyStrokes.length - 1);
312 keyStrokes = newKeyStrokes;
313 }
314
315 // And then add the new stroke.
316 if ((keyStrokes.length is 0)
317 || (insertionIndex >= keyStrokes.length)
318 || (isCursorInLastPosition())) {
319 keyStrokes = insertStrokeAt(keyStrokes, stroke,
320 keyStrokes.length);
321 clearInsertionIndex();
322 } else {
323 /*
324 * I'm just getting the insertionIndex here. No actual
325 * deletion should occur.
326 */
327 KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
328 insertionIndex = deleteSelection(keyStrokes, stroke
329 .isComplete(), deletedKeyStrokes);
330 keyStrokes = deletedKeyStrokes[0];
331 if (stroke.isComplete()) {
332 keyStrokes = insertStrokeAt(keyStrokes, stroke,
333 insertionIndex);
334 clearInsertionIndex();
335 }
336 }
337
338 }
339
340 return keyStrokes;
341 }
342 }
343
344 /**
345 * A traversal listener that blocks all traversal except for tabs and arrow
346 * keys.
347 */
348 private class TraversalFilter : Listener {
349 /**
350 * Handles the traverse event on the text field wrapped by this class.
351 * It swallows all traverse events example for tab and arrow key
352 * navigation. The other forms of navigation can be reached by tabbing
353 * off of the control.
354 *
355 * @param event
356 * The trigger event; must not be <code>null</code>.
357 */
358 public void handleEvent(Event event) {
359 switch (event.detail) {
360 case SWT.TRAVERSE_ESCAPE:
361 case SWT.TRAVERSE_MNEMONIC:
362 case SWT.TRAVERSE_NONE:
363 case SWT.TRAVERSE_PAGE_NEXT:
364 case SWT.TRAVERSE_PAGE_PREVIOUS:
365 case SWT.TRAVERSE_RETURN:
366 event.type = SWT.None;
367 event.doit = false;
368 break;
369
370 case SWT.TRAVERSE_TAB_NEXT:
371 case SWT.TRAVERSE_TAB_PREVIOUS:
372 // Check if modifiers other than just 'Shift' were
373 // down.
374 if ((event.stateMask & (SWT.MODIFIER_MASK ^ SWT.SHIFT)) !is 0) {
375 // Modifiers other than shift were down.
376 event.type = SWT.None;
377 event.doit = false;
378 break;
379 }
380
381 // fall through -- either no modifiers, or just shift.
382
383 case SWT.TRAVERSE_ARROW_NEXT:
384 case SWT.TRAVERSE_ARROW_PREVIOUS:
385 default:
386 // Let the traversal happen, but clear the incomplete
387 // stroke
388 if (hasIncompleteStroke()) {
389 KeyStroke[] oldKeyStrokes = getKeySequence()
390 .getKeyStrokes();
391 int newKeyStrokesLength = oldKeyStrokes.length - 1;
392 if (newKeyStrokesLength >= 1) {
393 KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength];
394 System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
395 newKeyStrokesLength);
396 setKeySequence(KeySequence.getInstance(newKeyStrokes));
397 } else {
398 setKeySequence(KeySequence.getInstance());
399 }
400 }
401 }
402
403 }
404 }
405
406 /**
407 * The manager resposible for installing and removing the traversal filter
408 * when the key sequence entry widget gains and loses focus.
409 */
410 private class TraversalFilterManager : FocusListener {
411 /** The managed filter. We only need one instance. */
412 private TraversalFilter filter;
413
414 private bool filtering = false;
415
416 this(){
417 filter = new TraversalFilter();
418 }
419
420 /**
421 * Attaches the global traversal filter.
422 *
423 * @param event
424 * Ignored.
425 */
426 public void focusGained(FocusEvent event) {
427 Display.getCurrent().addFilter(SWT.Traverse, filter);
428 filtering = true;
429 }
430
431 /**
432 * Detaches the global traversal filter.
433 *
434 * @param event
435 * Ignored.
436 */
437 public void focusLost(FocusEvent event) {
438 Display.getCurrent().removeFilter(SWT.Traverse, filter);
439 filtering = false;
440 }
441
442 /**
443 * Remove the traverse filter if we close without focusOut.
444 */
445 public void dispose() {
446 if (filtering) {
447 Display.getCurrent().removeFilter(SWT.Traverse, filter);
448 }
449 }
450 }
451
452 /**
453 * A modification listener that makes sure that external events to this
454 * class (i.e., direct modification of the underlying text) do not break
455 * this class' view of the world.
456 */
457 private class UpdateSequenceListener : ModifyListener {
458 /**
459 * Handles the modify event on the underlying text widget.
460 *
461 * @param event
462 * The triggering event; ignored.
463 */
464 public void modifyText(ModifyEvent event) {
465 try {
466 // The original sequence.
467 KeySequence originalSequence = getKeySequence();
468
469 // The new sequence drawn from the text.
470 String contents = getText();
471 KeySequence newSequence = KeySequence.getInstance(contents);
472
473 // Check to see if they're the same.
474 if (!originalSequence.opEquals(newSequence)) {
475 setKeySequence(newSequence);
476 }
477
478 } catch (ParseException e) {
479 // Abort any cut/paste-driven modifications
480 setKeySequence(getKeySequence());
481 }
482 }
483 }
484
485 static this(){
486 TreeSet trappedKeys = new TreeSet();
487 trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.TAB));
488 trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.TAB
489 | SWT.SHIFT));
490 trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(SWT.BS));
491 List trappedKeyList = new ArrayList(trappedKeys);
492 TRAPPED_KEYS = Collections.unmodifiableList(trappedKeyList);
493 }
494
495 /** An empty string instance for use in clearing text values. */
496 private static const String EMPTY_STRING = ""; //$NON-NLS-1$
497
498 /**
499 * The special integer value for the maximum number of strokes indicating
500 * that an infinite number should be allowed.
501 */
502 public static const int INFINITE = -1;
503
504 /**
505 * The name of the property representing the current key sequence in this
506 * key sequence widget.
507 *
508 * @since 3.2
509 */
510 public static const String P_KEY_SEQUENCE = "org.eclipse.jface.bindings.keys.KeySequenceText.KeySequence"; //$NON-NLS-1$
511
512 /**
513 * The keys trapped by this widget. This list is guaranteed to be roughly
514 * accurate. Perfection is not possible, as SWT does not export traversal
515 * keys as constants.
516 */
517 public static const List TRAPPED_KEYS;
518
519 /**
520 * The key filter attached to the underlying widget that traps key events.
521 */
522 private const KeyTrapListener keyFilter;
523
524 /**
525 * The text of the key sequence -- containing only the complete key strokes.
526 */
527 private KeySequence keySequence;
528
529 /**
530 * Those listening to changes to the key sequence in this widget. This value
531 * may be <code>null</code> if there are no listeners.
532 */
533 private Collection listeners = null;
534
535 /** The maximum number of key strokes permitted in the sequence. */
536 private int maxStrokes = INFINITE;
537
538 /** The text widget that is wrapped for this class. */
539 private const Text text;
540
541 /**
542 * The listener that makes sure that the text widget remains up-to-date with
543 * regards to external modification of the text (e.g., cut & pasting).
544 */
545 private const UpdateSequenceListener updateSequenceListener;
546
547 /**
548 * Constructs an instance of <code>KeySequenceTextField</code> with the
549 * text field to use. If the platform is carbon (MacOS X), then the font is
550 * set to be the same font used to display accelerators in the menus.
551 *
552 * @param wrappedText
553 * The text widget to wrap; must not be <code>null</code>.
554 */
555 public this(Text wrappedText) {
556 keyFilter = new KeyTrapListener();
557 keySequence = KeySequence.getInstance();
558 updateSequenceListener = new UpdateSequenceListener();
559
560 text = wrappedText;
561
562 // Set the font if the platform is carbon.
563 if ("carbon".equals(SWT.getPlatform())) { //$NON-NLS-1$
564 // Don't worry about this font name here; it is the official menu
565 // font and point size on the Mac.
566 Font font = new Font(cast(Device)text.getDisplay(),
567 "Lucida Grande", 13, SWT.NORMAL); //$NON-NLS-1$
568 text.setFont(font);
569 text.addDisposeListener(new class(font) DisposeListener {
570 Font font_;
571 this(Font a){
572 font_=a;
573 }
574 public void widgetDisposed(DisposeEvent e) {
575 font_.dispose();
576 }
577 });
578 }
579
580 // Add the key listener.
581 text.addListener(SWT.KeyUp, keyFilter);
582 text.addListener(SWT.KeyDown, keyFilter);
583
584 TraversalFilterManager traversalFilterManager = new TraversalFilterManager();
585 text.addFocusListener(traversalFilterManager);
586 text.addDisposeListener(new class(traversalFilterManager) DisposeListener {
587 TraversalFilterManager traversalFilterManager_;
588 this(TraversalFilterManager a){
589 traversalFilterManager_=a;
590 }
591 public void widgetDisposed(DisposeEvent e) {
592 traversalFilterManager_.dispose();
593 }
594 });
595
596 // Add an internal modify listener.
597 text.addModifyListener(updateSequenceListener);
598 }
599
600 /**
601 * Adds a property change listener to this key sequence widget. It will be
602 * notified when the key sequence changes.
603 *
604 * @param listener
605 * The listener to be notified when changes occur; must not be
606 * <code>null</code>.
607 * @since 3.2
608 */
609 public final void addPropertyChangeListener(
610 IPropertyChangeListener listener) {
611 if (listener is null) {
612 return;
613 }
614
615 if (listeners is null) {
616 listeners = new ArrayList(1);
617 }
618
619 listeners.add(cast(Object)listener);
620 }
621
622 /**
623 * Clears the text field and resets all the internal values.
624 */
625 public void clear() {
626 KeySequence oldKeySequence = keySequence;
627 keySequence = KeySequence.getInstance();
628 text.setText(EMPTY_STRING);
629 firePropertyChangeEvent(oldKeySequence);
630 }
631
632 /**
633 * Removes the key strokes from the list corresponding the selection. If
634 * <code>allowIncomplete</code>, then invalid key sequences will be
635 * allowed (i.e., those with incomplete strokes in the non-terminal
636 * position). Otherwise, incomplete strokes will be removed. This modifies
637 * <code>keyStrokes</code> in place, and has no effect on the text widget
638 * this class wraps.
639 *
640 * @param keyStrokes
641 * The list of key strokes from which the selection should be
642 * removed; must not be <code>null</code>.
643 * @param allowIncomplete
644 * Whether incomplete strokes should be allowed to exist in the
645 * list after the deletion.
646 * @param deletedKeyStrokes
647 * The list of keystrokes that were deleted by this operation.
648 * Declared as final since it will hold a reference to the new
649 * keyStroke array that has deleted the selected keystrokes.
650 * @return The index at which a subsequent insert should occur. This index
651 * only has meaning to the <code>insertStrokeAt</code> method.
652 */
653 private final int deleteSelection(KeyStroke[] keyStrokes,
654 bool allowIncomplete, KeyStroke[][] deletedKeyStrokes) {
655 // Get the current selection.
656 Point selection = text.getSelection();
657 int start = selection.x;
658 int end = selection.y;
659
660 /*
661 * Using the key sequence format method, discover the point at which
662 * adding key strokes passes or equals the start of the selection. In
663 * other words, find the first stroke that is part of the selection.
664 * Keep track of the text range under which the stroke appears (i.e.,
665 * startTextIndex->string.length() is the first selected stroke).
666 */
667 String string;
668 auto currentStrokes = new ArrayList();
669 int startTextIndex = 0; // keeps track of the start of the stroke
670 int keyStrokesLength = keyStrokes.length;
671 int i;
672 for (i = 0; (i < keyStrokesLength) && (string.length < start); i++) {
673 startTextIndex = string.length;
674 currentStrokes.add(keyStrokes[i]);
675 string = KeySequence.getInstance(currentStrokes).format();
676 }
677
678 /*
679 * If string.length() is start, then the cursor is positioned between
680 * strokes (i.e., selection is outside of a stroke).
681 */
682 int startStrokeIndex;
683 if (string.length is start) {
684 startStrokeIndex = currentStrokes.size();
685 } else {
686 startStrokeIndex = currentStrokes.size() - 1;
687 }
688
689 /*
690 * Check to see if the cursor is only positioned, rather than actually
691 * selecting something. We only need to compute the end if there is a
692 * selection.
693 */
694 int endStrokeIndex;
695 if (start is end) {
696 // return the current keystrokes, nothing has to be deleted
697 deletedKeyStrokes[0] = keyStrokes;
698 return startStrokeIndex;
699 }
700
701 for (; (i < keyStrokesLength) && (string.length < end); i++) {
702 currentStrokes.add(keyStrokes[i]);
703 string = KeySequence.getInstance(currentStrokes).format();
704 }
705 endStrokeIndex = currentStrokes.size() - 1;
706 if (endStrokeIndex < 0) {
707 endStrokeIndex = 0;
708 }
709
710 /*
711 * Remove the strokes that are touched by the selection. Keep track of
712 * the first stroke removed.
713 */
714 int newLength = keyStrokesLength
715 - (endStrokeIndex - startStrokeIndex + 1);
716 deletedKeyStrokes[0] = new KeyStroke[newLength];
717 KeyStroke startStroke = keyStrokes[startStrokeIndex];
718 KeyStroke keyStrokeResult[] = new KeyStroke[newLength];
719 System.arraycopy(keyStrokes, 0, keyStrokeResult, 0, startStrokeIndex);
720 System.arraycopy(keyStrokes, endStrokeIndex + 1, keyStrokeResult,
721 startStrokeIndex, keyStrokesLength - endStrokeIndex - 1);
722 System.arraycopy(keyStrokeResult, 0, deletedKeyStrokes[0], 0, newLength);
723
724 /*
725 * Allow the first stroke removed to be replaced by an incomplete
726 * stroke.
727 */
728 if (allowIncomplete) {
729 int modifierKeys = startStroke.getModifierKeys();
730 KeyStroke incompleteStroke = KeyStroke.getInstance(modifierKeys,
731 KeyStroke.NO_KEY);
732 int incompleteStrokeLength = incompleteStroke.format().length;
733 if ((startTextIndex + incompleteStrokeLength) <= start) {
734 KeyStroke[] added = new KeyStroke[newLength + 1];
735 System.arraycopy(deletedKeyStrokes[0], 0, added, 0,
736 startStrokeIndex);
737 added[startStrokeIndex] = incompleteStroke;
738 System.arraycopy(deletedKeyStrokes[0], startStrokeIndex, added,
739 startStrokeIndex + 1, newLength - startStrokeIndex);
740 deletedKeyStrokes[0] = added;
741 }
742 }
743
744 return startStrokeIndex;
745 }
746
747 /**
748 * Fires a property change event to all of the listeners.
749 *
750 * @param oldKeySequence
751 * The old key sequence; must not be <code>null</code>.
752 * @since 3.2
753 */
754 protected final void firePropertyChangeEvent(
755 KeySequence oldKeySequence) {
756 if (listeners !is null) {
757 Iterator listenerItr = listeners.iterator();
758 PropertyChangeEvent event = new PropertyChangeEvent(this,
759 P_KEY_SEQUENCE, oldKeySequence, getKeySequence());
760 while (listenerItr.hasNext()) {
761 IPropertyChangeListener listener = cast(IPropertyChangeListener) listenerItr
762 .next();
763 listener.propertyChange(event);
764 }
765 }
766 }
767
768 /**
769 * An accessor for the <code>KeySequence</code> that corresponds to the
770 * current state of the text field. This includes incomplete strokes.
771 *
772 * @return The key sequence representation; never <code>null</code>.
773 */
774 public KeySequence getKeySequence() {
775 return keySequence;
776 }
777
778 /**
779 * An accessor for the underlying text widget's contents.
780 *
781 * @return The text contents of this entry; never <code>null</code>.
782 */
783 private String getText() {
784 return text.getText();
785 }
786
787 /**
788 * Tests whether the current key sequence has a stroke with no natural key.
789 *
790 * @return <code>true</code> is there is an incomplete stroke;
791 * <code>false</code> otherwise.
792 */
793 private bool hasIncompleteStroke() {
794 return !keySequence.isComplete();
795 }
796
797 /**
798 * Tests whether the current text widget has some text selection.
799 *
800 * @return <code>true</code> if the number of selected characters it
801 * greater than zero; <code>false</code> otherwise.
802 */
803 private bool hasSelection() {
804 return (text.getSelectionCount() > 0);
805 }
806
807 /**
808 * Inserts the key stroke at the current insertion point. This does a
809 * regular delete and insert, as if the key had been pressed.
810 *
811 * @param stroke
812 * The key stroke to insert; must not be <code>null</code>.
813 */
814 public void insert(KeyStroke stroke) {
815 if (!stroke.isComplete()) {
816 return;
817 }
818
819 // Copy the key strokes in the current key sequence.
820 KeySequence keySequence = getKeySequence();
821 KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
822 KeyStroke[] newKeyStrokes;
823 if ((hasIncompleteStroke()) && (!keySequence.isEmpty())) {
824 int newKeyStrokesLength = oldKeyStrokes.length - 1;
825 newKeyStrokes = new KeyStroke[newKeyStrokesLength];
826 System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
827 newKeyStrokesLength);
828 } else {
829 newKeyStrokes = oldKeyStrokes;
830 }
831
832 KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
833 int index = deleteSelection(newKeyStrokes, false, deletedKeyStrokes);
834 if (index is -1) {
835 index = 0;
836 }
837
838 KeyStroke[] keyStrokes = insertStrokeAt(newKeyStrokes, stroke, index);
839 keyFilter.clearInsertionIndex();
840 setKeySequence(KeySequence.getInstance(keyStrokes));
841 }
842
843 /**
844 * Inserts the stroke at the given index in the list of strokes. If the
845 * stroke currently at that index is incomplete, then it tries to merge the
846 * two strokes. If merging is a complete failure (unlikely), then it will
847 * simply overwrite the incomplete stroke. If the stroke at the index is
848 * complete, then it simply inserts the stroke independently.
849 *
850 * @param keyStrokes
851 * The list of key strokes in which the key stroke should be
852 * appended; must not be <code>null</code>.
853 * @param stroke
854 * The stroke to insert; should not be <code>null</code>.
855 * @param index
856 * The index at which to insert; must be a valid index into the
857 * list of key strokes.
858 */
859 private final KeyStroke[] insertStrokeAt(KeyStroke[] keyStrokes,
860 KeyStroke stroke, int index) {
861 int keyStrokesLength = keyStrokes.length;
862 KeyStroke currentStroke = (index >= keyStrokesLength) ? null
863 : keyStrokes[index];
864 if ((currentStroke !is null) && (!currentStroke.isComplete())) {
865 int modifierKeys = currentStroke.getModifierKeys();
866 int naturalKey = stroke.getNaturalKey();
867 modifierKeys |= stroke.getModifierKeys();
868 keyStrokes[index] = KeyStroke.getInstance(modifierKeys, naturalKey);
869 return keyStrokes;
870 }
871
872 KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokesLength + 1];
873 System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, index);
874 newKeyStrokes[index] = stroke;
875 if (index < keyStrokesLength) {
876 System.arraycopy(keyStrokes, index, newKeyStrokes, index + 1,
877 keyStrokesLength-index);
878 }
879 return newKeyStrokes;
880 }
881
882 /**
883 * Tests whether the cursor is in the last position. This means that the
884 * selection extends to the last position.
885 *
886 * @return <code>true</code> if the selection extends to the last
887 * position; <code>false</code> otherwise.
888 */
889 private bool isCursorInLastPosition() {
890 return (text.getSelection().y >= getText().length);
891 }
892
893 /**
894 * Removes the given listener from this key sequence widget.
895 *
896 * @param listener
897 * The listener to be removed; must not be <code>null</code>.
898 * @since 3.2
899 */
900 public final void removePropertyChangeListener(
901 IPropertyChangeListener listener) {
902 if ((listener is null) || (listeners is null)) {
903 return;
904 }
905
906 listeners.remove(cast(Object)listener);
907 }
908
909 /**
910 * <p>
911 * A mutator for the key sequence stored within this widget. The text and
912 * caret position are updated.
913 * </p>
914 * <p>
915 * All sequences are limited to maxStrokes number of strokes in length. If
916 * there are already that number of strokes, then it does not show
917 * incomplete strokes, and does not keep track of them.
918 * </p>
919 *
920 * @param newKeySequence
921 * The new key sequence for this widget; may be <code>null</code>
922 * if none.
923 */
924 public void setKeySequence(KeySequence newKeySequence) {
925 KeySequence oldKeySequence = keySequence;
926
927 if (newKeySequence is null) {
928 text.setText(""); //$NON-NLS-1$
929 } else {
930 keySequence = newKeySequence;
931 }
932
933 // Trim any extra strokes.
934 if (maxStrokes !is INFINITE) {
935 KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
936 if (maxStrokes < oldKeyStrokes.length) {
937 KeyStroke[] newKeyStrokes = new KeyStroke[maxStrokes];
938 System
939 .arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
940 maxStrokes);
941 keySequence = KeySequence.getInstance(newKeyStrokes);
942 }
943 }
944
945 // Check to see if the text has changed.
946 String currentString = getText();
947 String newString = keySequence.format();
948 if (!currentString.equals(newString)) {
949 // We need to update the text
950 text.removeModifyListener(updateSequenceListener);
951 text.setText(keySequence.format());
952 text.addModifyListener(updateSequenceListener);
953 text.setSelection(getText().length);
954 }
955
956 firePropertyChangeEvent(oldKeySequence);
957 }
958
959 /**
960 * Returns the maximum number of strokes that are permitted in this widget
961 * at one time.
962 *
963 * @return The maximum number of strokes; will be a positive integer or
964 * <code>INFINITE</code>.
965 */
966 public int getKeyStrokeLimit() {
967 return maxStrokes;
968 }
969
970 /**
971 * A mutator for the maximum number of strokes that are permitted in this
972 * widget at one time.
973 *
974 * @param keyStrokeLimit
975 * The maximum number of strokes; must be a positive integer or
976 * <code>INFINITE</code>.
977 */
978 public void setKeyStrokeLimit(int keyStrokeLimit) {
979 if (keyStrokeLimit > 0 || keyStrokeLimit is INFINITE) {
980 this.maxStrokes = keyStrokeLimit;
981 } else {
982 throw new IllegalArgumentException(null);
983 }
984
985 // Make sure we are obeying the new limit.
986 setKeySequence(getKeySequence());
987 }
988 }