Mercurial > projects > dwt2
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 } |