KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > internal > keys > WorkbenchKeyboard


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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 package org.eclipse.ui.internal.keys;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Collection JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.ResourceBundle JavaDoc;
18
19 import org.eclipse.core.commands.Command;
20 import org.eclipse.core.commands.NotEnabledException;
21 import org.eclipse.core.commands.NotHandledException;
22 import org.eclipse.core.commands.ParameterizedCommand;
23 import org.eclipse.core.commands.common.CommandException;
24 import org.eclipse.core.commands.common.NotDefinedException;
25 import org.eclipse.core.commands.util.Tracing;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.jface.bindings.Binding;
29 import org.eclipse.jface.bindings.keys.KeySequence;
30 import org.eclipse.jface.bindings.keys.KeyStroke;
31 import org.eclipse.jface.bindings.keys.ParseException;
32 import org.eclipse.jface.bindings.keys.SWTKeySupport;
33 import org.eclipse.jface.internal.InternalPolicy;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.custom.StyledText;
36 import org.eclipse.swt.widgets.Combo;
37 import org.eclipse.swt.widgets.Control;
38 import org.eclipse.swt.widgets.Display;
39 import org.eclipse.swt.widgets.Event;
40 import org.eclipse.swt.widgets.Listener;
41 import org.eclipse.swt.widgets.Shell;
42 import org.eclipse.swt.widgets.Text;
43 import org.eclipse.swt.widgets.Widget;
44 import org.eclipse.ui.IWindowListener;
45 import org.eclipse.ui.IWorkbench;
46 import org.eclipse.ui.IWorkbenchWindow;
47 import org.eclipse.ui.contexts.IContextService;
48 import org.eclipse.ui.handlers.IHandlerService;
49 import org.eclipse.ui.internal.Workbench;
50 import org.eclipse.ui.internal.WorkbenchPlugin;
51 import org.eclipse.ui.internal.contexts.ContextService;
52 import org.eclipse.ui.internal.handlers.HandlerService;
53 import org.eclipse.ui.internal.misc.Policy;
54 import org.eclipse.ui.internal.misc.StatusUtil;
55 import org.eclipse.ui.internal.util.Util;
56 import org.eclipse.ui.keys.IBindingService;
57 import org.eclipse.ui.statushandlers.StatusManager;
58
59 import com.ibm.icu.text.MessageFormat;
60
61 /**
62  * <p>
63  * Controls the keyboard input into the workbench key binding architecture. This
64  * allows key events to be programmatically pushed into the key binding
65  * architecture -- potentially triggering the execution of commands. It is used
66  * by the <code>Workbench</code> to listen for events on the
67  * <code>Display</code>.
68  * </p>
69  * <p>
70  * This class is not designed to be thread-safe. It is assumed that all access
71  * to the <code>press</code> method is done through the event loop. Accessing
72  * this method outside the event loop can cause corruption of internal state.
73  * </p>
74  *
75  * @since 3.0
76  */

77 public final class WorkbenchKeyboard {
78
79     /**
80      * A display filter for handling key bindings. This filter can either be
81      * enabled or disabled. If disabled, the filter does not process incoming
82      * events. The filter starts enabled.
83      *
84      * @since 3.1
85      */

86     public final class KeyDownFilter implements Listener {
87
88         /**
89          * Whether the filter is enabled.
90          */

91         private transient boolean enabled = true;
92
93         /**
94          * Handles an incoming traverse or key down event.
95          *
96          * @param event
97          * The event to process; must not be <code>null</code>.
98          */

99         public final void handleEvent(final Event event) {
100             if (!enabled) {
101                 return;
102             }
103
104             if (DEBUG && DEBUG_VERBOSE) {
105                 final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(
106                         "Listener.handleEvent(type = "); //$NON-NLS-1$
107
switch (event.type) {
108                 case SWT.KeyDown:
109                     buffer.append("KeyDown"); //$NON-NLS-1$
110
break;
111                 case SWT.Traverse:
112                     buffer.append("Traverse"); //$NON-NLS-1$
113
break;
114                 default:
115                     buffer.append(event.type);
116                 }
117                 buffer.append(", stateMask = 0x" //$NON-NLS-1$
118
+ Integer.toHexString(event.stateMask)
119                         + ", keyCode = 0x" //$NON-NLS-1$
120
+ Integer.toHexString(event.keyCode) + ", time = " //$NON-NLS-1$
121
+ event.time + ", character = 0x" //$NON-NLS-1$
122
+ Integer.toHexString(event.character) + ")"); //$NON-NLS-1$
123
Tracing.printTrace("KEYS", buffer.toString()); //$NON-NLS-1$
124
}
125
126             filterKeySequenceBindings(event);
127         }
128
129         /**
130          * Returns whether the key binding filter is enabled.
131          *
132          * @return Whether the key filter is enabled.
133          */

134         public final boolean isEnabled() {
135             return enabled;
136         }
137
138         /**
139          * Sets whether this filter should be enabled or disabled.
140          *
141          * @param enabled
142          * Whether key binding filter should be enabled.
143          */

144         public final void setEnabled(final boolean enabled) {
145             this.enabled = enabled;
146         }
147     }
148
149     /**
150      * Whether the keyboard should kick into debugging mode. This causes real
151      * key bindings trapped by the key binding architecture to be reported.
152      */

153     private static final boolean DEBUG = Policy.DEBUG_KEY_BINDINGS;
154
155     /**
156      * Whether the keyboard should report every event received by its global
157      * filter.
158      */

159     private static final boolean DEBUG_VERBOSE = Policy.DEBUG_KEY_BINDINGS_VERBOSE;
160
161     /**
162      * The time in milliseconds to wait after pressing a key before displaying
163      * the key assist dialog.
164      */

165     private static final int DELAY = 1000;
166
167     /** The collection of keys that are to be processed out-of-order. */
168     static KeySequence outOfOrderKeys;
169
170     /**
171      * The translation bundle in which to look up internationalized text.
172      */

173     private final static ResourceBundle JavaDoc RESOURCE_BUNDLE = ResourceBundle
174             .getBundle(WorkbenchKeyboard.class.getName());
175
176     static {
177
178         try {
179             outOfOrderKeys = KeySequence.getInstance("ESC DEL"); //$NON-NLS-1$
180
} catch (ParseException e) {
181             outOfOrderKeys = KeySequence.getInstance();
182             String JavaDoc message = "Could not parse out-of-order keys definition: 'ESC DEL'. Continuing with no out-of-order keys."; //$NON-NLS-1$
183
WorkbenchPlugin.log(message, new Status(IStatus.ERROR,
184                     WorkbenchPlugin.PI_WORKBENCH, 0, message, e));
185         }
186     }
187
188     /**
189      * Generates any key strokes that are near matches to the given event. The
190      * first such key stroke is always the exactly matching key stroke.
191      *
192      * @param event
193      * The event from which the key strokes should be generated; must
194      * not be <code>null</code>.
195      * @return The set of nearly matching key strokes. It is never
196      * <code>null</code>, but may be empty.
197      */

198     public static List JavaDoc generatePossibleKeyStrokes(Event event) {
199         final List JavaDoc keyStrokes = new ArrayList JavaDoc(3);
200
201         /*
202          * If this is not a keyboard event, then there are no key strokes. This
203          * can happen if we are listening to focus traversal events.
204          */

205         if ((event.stateMask == 0) && (event.keyCode == 0)
206                 && (event.character == 0)) {
207             return keyStrokes;
208         }
209
210         // Add each unique key stroke to the list for consideration.
211
final int firstAccelerator = SWTKeySupport
212                 .convertEventToUnmodifiedAccelerator(event);
213         keyStrokes.add(SWTKeySupport
214                 .convertAcceleratorToKeyStroke(firstAccelerator));
215
216         // We shouldn't allow delete to undergo shift resolution.
217
if (event.character == SWT.DEL) {
218             return keyStrokes;
219         }
220
221         final int secondAccelerator = SWTKeySupport
222                 .convertEventToUnshiftedModifiedAccelerator(event);
223         if (secondAccelerator != firstAccelerator) {
224             keyStrokes.add(SWTKeySupport
225                     .convertAcceleratorToKeyStroke(secondAccelerator));
226         }
227
228         final int thirdAccelerator = SWTKeySupport
229                 .convertEventToModifiedAccelerator(event);
230         if ((thirdAccelerator != secondAccelerator)
231                 && (thirdAccelerator != firstAccelerator)) {
232             keyStrokes.add(SWTKeySupport
233                     .convertAcceleratorToKeyStroke(thirdAccelerator));
234         }
235
236         return keyStrokes;
237     }
238
239     /**
240      * <p>
241      * Determines whether the given event represents a key press that should be
242      * handled as an out-of-order event. An out-of-order key press is one that
243      * is passed to the focus control first. Only if the focus control fails to
244      * respond will the regular key bindings get applied.
245      * </p>
246      * <p>
247      * Care must be taken in choosing which keys are chosen as out-of-order
248      * keys. This method has only been designed and test to work with the
249      * unmodified "Escape" key stroke.
250      * </p>
251      *
252      * @param keyStrokes
253      * The key stroke in which to look for out-of-order keys; must
254      * not be <code>null</code>.
255      * @return <code>true</code> if the key is an out-of-order key;
256      * <code>false</code> otherwise.
257      */

258     private static boolean isOutOfOrderKey(List JavaDoc keyStrokes) {
259         // Compare to see if one of the possible key strokes is out of order.
260
final KeyStroke[] outOfOrderKeyStrokes = outOfOrderKeys.getKeyStrokes();
261         final int outOfOrderKeyStrokesLength = outOfOrderKeyStrokes.length;
262         for (int i = 0; i < outOfOrderKeyStrokesLength; i++) {
263             if (keyStrokes.contains(outOfOrderKeyStrokes[i])) {
264                 return true;
265             }
266         }
267         return false;
268     }
269
270     /**
271      * The binding manager to be used to resolve key bindings. This member
272      * variable will be <code>null</code> if it has not yet been initialized.
273      */

274     private IBindingService bindingService = null;
275
276     /**
277      * The <code>KeyAssistDialog</code> displayed to the user to assist them
278      * in completing a multi-stroke keyboard shortcut.
279      *
280      * @since 3.1
281      */

282     private KeyAssistDialog keyAssistDialog = null;
283
284     /**
285      * The listener that runs key events past the global key bindings.
286      */

287     private final KeyDownFilter keyDownFilter = new KeyDownFilter();
288
289     /**
290      * The single out-of-order listener used by the workbench. This listener is
291      * attached to one widget at a time, and is used to catch key down events
292      * after all processing is done. This technique is used so that some keys
293      * will have their native behaviour happen first.
294      *
295      * @since 3.1
296      */

297     private final OutOfOrderListener outOfOrderListener = new OutOfOrderListener(
298             this);
299
300     /**
301      * The single out-of-order verify listener used by the workbench. This
302      * listener is attached to one</code> StyledText</code> at a time, and is
303      * used to catch verify events after all processing is done. This technique
304      * is used so that some keys will have their native behaviour happen first.
305      *
306      * @since 3.1
307      */

308     private final OutOfOrderVerifyListener outOfOrderVerifyListener = new OutOfOrderVerifyListener(
309             outOfOrderListener);
310
311     /**
312      * The time at which the last timer was started. This is used to judge if a
313      * sufficient amount of time has elapsed. This is simply the output of
314      * <code>System.currentTimeMillis()</code>.
315      */

316     private long startTime = Long.MAX_VALUE;
317
318     /**
319      * The mode is the current state of the key binding architecture. In the
320      * case of multi-stroke key bindings, this can be a partially complete key
321      * binding.
322      */

323     private final KeyBindingState state;
324
325     /**
326      * The window listener responsible for maintaining internal state as the
327      * focus moves between windows on the desktop.
328      */

329     private final IWindowListener windowListener = new IWindowListener() {
330
331         /*
332          * (non-Javadoc)
333          *
334          * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow)
335          */

336         public void windowActivated(IWorkbenchWindow window) {
337             checkActiveWindow(window);
338         }
339
340         /*
341          * (non-Javadoc)
342          *
343          * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow)
344          */

345         public void windowClosed(IWorkbenchWindow window) {
346             // Do nothing.
347
}
348
349         /*
350          * (non-Javadoc)
351          *
352          * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow)
353          */

354         public void windowDeactivated(IWorkbenchWindow window) {
355             // Do nothing
356
}
357
358         /*
359          * (non-Javadoc)
360          *
361          * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow)
362          */

363         public void windowOpened(IWorkbenchWindow window) {
364             // Do nothing.
365
}
366     };
367
368     /**
369      * The workbench on which this keyboard interface should act.
370      */

371     private final IWorkbench workbench;
372
373     /**
374      * Constructs a new instance of <code>WorkbenchKeyboard</code> associated
375      * with a particular workbench.
376      *
377      * @param associatedWorkbench
378      * The workbench with which this keyboard interface should work;
379      * must not be <code>null</code>.
380      * @since 3.1
381      */

382     public WorkbenchKeyboard(Workbench associatedWorkbench) {
383         workbench = associatedWorkbench;
384         state = new KeyBindingState(associatedWorkbench);
385         workbench.addWindowListener(windowListener);
386     }
387
388     /**
389      * Verifies that the active workbench window is the same as the workbench
390      * window associated with the state. This is used to verify that the state
391      * is properly reset as focus changes. When they are not the same, the state
392      * is reset and associated with the newly activated window.
393      *
394      * @param window
395      * The activated window; must not be <code>null</code>.
396      */

397     private void checkActiveWindow(IWorkbenchWindow window) {
398         if (!window.equals(state.getAssociatedWindow())) {
399             resetState(true);
400             state.setAssociatedWindow(window);
401         }
402     }
403
404     /**
405      * Closes the multi-stroke key binding assistant shell, if it exists and
406      * isn't already disposed.
407      */

408     private void closeMultiKeyAssistShell() {
409         if (keyAssistDialog != null) {
410             final Shell shell = keyAssistDialog.getShell();
411             if ((shell != null) && (!shell.isDisposed()) && (shell.isVisible())) {
412                 keyAssistDialog.close(true);
413             }
414         }
415     }
416
417     /**
418      * Performs the actual execution of the command by looking up the current
419      * handler from the command manager. If there is a handler and it is
420      * enabled, then it tries the actual execution. Execution failures are
421      * logged. When this method completes, the key binding state is reset.
422      *
423      * @param binding
424      * The binding that should be executed; should not be
425      * <code>null</code>.
426      * @param trigger
427      * The triggering event; may be <code>null</code>.
428      * @return <code>true</code> if there was a handler; <code>false</code>
429      * otherwise.
430      * @throws CommandException
431      * if the handler does not complete execution for some reason.
432      * It is up to the caller of this method to decide whether to
433      * log the message, display a dialog, or ignore this exception
434      * entirely.
435      */

436     final boolean executeCommand(final Binding binding, final Event trigger)
437             throws CommandException {
438         final ParameterizedCommand parameterizedCommand = binding
439                 .getParameterizedCommand();
440
441         if (DEBUG) {
442             Tracing.printTrace("KEYS", //$NON-NLS-1$
443
"WorkbenchKeyboard.executeCommand(commandId = '" //$NON-NLS-1$
444
+ parameterizedCommand.getId() + "', parameters = " //$NON-NLS-1$
445
+ parameterizedCommand.getParameterMap() + ')');
446         }
447
448         // Reset the key binding state (close window, clear status line, etc.)
449
resetState(false);
450
451         // Dispatch to the handler.
452
final Command command = parameterizedCommand.getCommand();
453         final boolean commandDefined = command.isDefined();
454         final boolean commandHandled = command.isHandled();
455         final boolean commandEnabled = command.isEnabled();
456
457         if (DEBUG && DEBUG_VERBOSE) {
458             if (!commandDefined) {
459                 Tracing.printTrace("KEYS", " not defined"); //$NON-NLS-1$ //$NON-NLS-2$
460
} else if (!commandHandled) {
461                 Tracing.printTrace("KEYS", " not handled"); //$NON-NLS-1$ //$NON-NLS-2$
462
} else if (!commandEnabled) {
463                 Tracing.printTrace("KEYS", " not enabled"); //$NON-NLS-1$ //$NON-NLS-2$
464
}
465         }
466
467         try {
468             final IHandlerService handlerService = (IHandlerService) workbench
469                     .getService(IHandlerService.class);
470             handlerService.executeCommand(parameterizedCommand, trigger);
471         } catch (final NotDefinedException e) {
472             // The command is not defined. Forwarded to the IExecutionListener.
473
} catch (final NotEnabledException e) {
474             // The command is not enabled. Forwarded to the IExecutionListener.
475
} catch (final NotHandledException e) {
476             // There is no handler. Forwarded to the IExecutionListener.
477
}
478
479         /*
480          * Now that the command has executed (and had the opportunity to use the
481          * remembered state of the dialog), it is safe to delete that
482          * information.
483          */

484         if (keyAssistDialog != null) {
485             keyAssistDialog.clearRememberedState();
486         }
487
488         return (commandDefined && commandHandled);
489     }
490
491     /**
492      * <p>
493      * Launches the command matching a the typed key. This filter an incoming
494      * <code>SWT.KeyDown</code> or <code>SWT.Traverse</code> event at the
495      * level of the display (i.e., before it reaches the widgets). It does not
496      * allow processing in a dialog or if the key strokes does not contain a
497      * natural key.
498      * </p>
499      * <p>
500      * Some key strokes (defined as a property) are declared as out-of-order
501      * keys. This means that they are processed by the widget <em>first</em>.
502      * Only if the other widget listeners do no useful work does it try to
503      * process key bindings. For example, "ESC" can cancel the current widget
504      * action, if there is one, without triggering key bindings.
505      * </p>
506      *
507      * @param event
508      * The incoming event; must not be <code>null</code>.
509      */

510     private void filterKeySequenceBindings(Event event) {
511         /*
512          * Only process key strokes containing natural keys to trigger key
513          * bindings.
514          */

515         if ((event.keyCode & SWT.MODIFIER_MASK) != 0) {
516             return;
517         }
518
519         // Allow special key out-of-order processing.
520
List JavaDoc keyStrokes = generatePossibleKeyStrokes(event);
521         if (isOutOfOrderKey(keyStrokes)) {
522             Widget widget = event.widget;
523             if ((event.character == SWT.DEL)
524                     && ((event.stateMask & SWT.MODIFIER_MASK) == 0)
525                     && ((widget instanceof Text) || (widget instanceof Combo))) {
526                 /*
527                  * KLUDGE. Bug 54654. The text widget relies on no listener
528                  * doing any work before dispatching the native delete event.
529                  * This does not work, as we are restricted to listeners.
530                  * However, it can be said that pressing a delete key in a text
531                  * widget will never use key bindings. This can be shown be
532                  * considering how the event dispatching is expected to work in
533                  * a text widget. So, we should do nothing ... ever.
534                  */

535                 return;
536
537             } else if (widget instanceof StyledText) {
538
539                 if (event.type == SWT.KeyDown) {
540                     /*
541                      * KLUDGE. Some people try to do useful work in verify
542                      * listeners. The way verify listeners work in SWT, we need
543                      * to verify the key as well; otherwise, we can't detect
544                      * that useful work has been done.
545                      */

546                     if (!outOfOrderVerifyListener.isActive(event.time)) {
547                         ((StyledText) widget)
548                                 .addVerifyKeyListener(outOfOrderVerifyListener);
549                         outOfOrderVerifyListener.setActive(event.time);
550                     }
551                 }
552
553             } else {
554                 if (!outOfOrderListener.isActive(event.time)) {
555                     widget.addListener(SWT.KeyDown, outOfOrderListener);
556                     outOfOrderListener.setActive(event.time);
557                 }
558
559             }
560
561             /*
562              * Otherwise, we count on a key down arriving eventually. Expecting
563              * out of order handling on Ctrl+Tab, for example, is a bad idea
564              * (stick to keys that are not window traversal keys).
565              */

566
567         } else {
568             processKeyEvent(keyStrokes, event);
569
570         }
571     }
572
573     /**
574      * An accessor for the filter that processes key down and traverse events on
575      * the display.
576      *
577      * @return The global key down and traverse filter; never <code>null</code>.
578      */

579     public KeyDownFilter getKeyDownFilter() {
580         return keyDownFilter;
581     }
582
583     /**
584      * Determines whether the key sequence is a perfect match for any command.
585      * If there is a match, then the corresponding command identifier is
586      * returned.
587      *
588      * @param keySequence
589      * The key sequence to check for a match; must never be
590      * <code>null</code>.
591      * @return The binding for the perfectly matching command; <code>null</code>
592      * if no command matches.
593      */

594     private Binding getPerfectMatch(KeySequence keySequence) {
595         if (bindingService == null) {
596             bindingService = (IBindingService) workbench
597                     .getService(IBindingService.class);
598         }
599         return bindingService.getPerfectMatch(keySequence);
600     }
601
602     final KeySequence getBuffer() {
603         return state.getCurrentSequence();
604     }
605
606     /**
607      * Changes the key binding state to the given value. This should be an
608      * incremental change, but there are no checks to guarantee this is so. It
609      * also sets up a <code>Shell</code> to be displayed after one second has
610      * elapsed. This shell will show the user the possible completions for what
611      * they have typed.
612      *
613      * @param sequence
614      * The new key sequence for the state; should not be
615      * <code>null</code>.
616      */

617     private void incrementState(KeySequence sequence) {
618         // Record the starting time.
619
startTime = System.currentTimeMillis();
620         final long myStartTime = startTime;
621
622         // Update the state.
623
state.setCurrentSequence(sequence);
624         state.setAssociatedWindow(workbench.getActiveWorkbenchWindow());
625
626         // After some time, open a shell displaying the possible completions.
627
final Display display = workbench.getDisplay();
628         display.timerExec(DELAY, new Runnable JavaDoc() {
629             public void run() {
630                 if ((System.currentTimeMillis() > (myStartTime - DELAY))
631                         && (startTime == myStartTime)) {
632                     openMultiKeyAssistShell();
633                 }
634             }
635         });
636     }
637
638     /**
639      * Determines whether the key sequence partially matches on of the active
640      * key bindings.
641      *
642      * @param keySequence
643      * The key sequence to check for a partial match; must never be
644      * <code>null</code>.
645      * @return <code>true</code> if there is a partial match;
646      * <code>false</code> otherwise.
647      */

648     private boolean isPartialMatch(KeySequence keySequence) {
649         if (bindingService == null) {
650             bindingService = (IBindingService) workbench
651                     .getService(IBindingService.class);
652         }
653         return bindingService.isPartialMatch(keySequence);
654     }
655
656     /**
657      * Determines whether the key sequence perfectly matches on of the active
658      * key bindings.
659      *
660      * @param keySequence
661      * The key sequence to check for a perfect match; must never be
662      * <code>null</code>.
663      * @return <code>true</code> if there is a perfect match;
664      * <code>false</code> otherwise.
665      */

666     private boolean isPerfectMatch(KeySequence keySequence) {
667         if (bindingService == null) {
668             bindingService = (IBindingService) workbench
669                     .getService(IBindingService.class);
670         }
671         return bindingService.isPerfectMatch(keySequence);
672     }
673
674     /**
675      * Logs the given exception, and opens a dialog explaining the failure.
676      *
677      * @param e
678      * The exception to log; must not be <code>null</code>.
679      * @param command
680      * The parameterized command for the binding to execute; may be
681      * <code>null</code>.
682      */

683     final void logException(final CommandException e,
684             final ParameterizedCommand command) {
685         Throwable JavaDoc nestedException = e.getCause();
686         Throwable JavaDoc exception = (nestedException == null) ? e : nestedException;
687
688         // If we can, include the command name in the exception.
689
String JavaDoc message = null;
690         if (command != null) {
691             try {
692                 final String JavaDoc name = command.getCommand().getName();
693                 message = MessageFormat.format(Util.translateString(
694                         RESOURCE_BUNDLE, "ExecutionError.MessageCommandName"), //$NON-NLS-1$
695
new Object JavaDoc[] { name });
696             } catch (final NotDefinedException nde) {
697                 // Fall through (message == null)
698
}
699         }
700         if (message == null) {
701             message = Util.translateString(RESOURCE_BUNDLE,
702                     "ExecutionError.Message"); //$NON-NLS-1$
703
}
704
705         String JavaDoc exceptionMessage = exception.getMessage();
706         if (exceptionMessage == null) {
707             exceptionMessage = exception.getClass().getName();
708         }
709         IStatus status = new Status(IStatus.ERROR,
710                 WorkbenchPlugin.PI_WORKBENCH, 0, exceptionMessage, exception);
711         WorkbenchPlugin.log(message, status);
712         StatusUtil.handleStatus(message, exception, StatusManager.SHOW);
713     }
714
715     /**
716      * Opens a <code>KeyAssistDialog</code> to assist the user in completing a
717      * multi-stroke key binding. This method lazily creates a
718      * <code>keyAssistDialog</code> and shares it between executions.
719      */

720     public final void openMultiKeyAssistShell() {
721         if (keyAssistDialog == null) {
722             keyAssistDialog = new KeyAssistDialog(workbench, this, state);
723         }
724         if (keyAssistDialog.getShell() == null) {
725             keyAssistDialog.setParentShell(Util.getShellToParentOn());
726         }
727         keyAssistDialog.open();
728     }
729
730     /**
731      * Opens the key assist dialog to offer the user the choice of a binding to
732      * pick from the collection of bindings.
733      *
734      * @param bindings
735      * a collection of Binding objects
736      * @since 3.3
737      */

738     public final void openKeyAssistShell(final Collection JavaDoc bindings) {
739         if (keyAssistDialog == null) {
740             keyAssistDialog = new KeyAssistDialog(workbench,
741                     WorkbenchKeyboard.this, state);
742         }
743         if (keyAssistDialog.getShell() == null) {
744             keyAssistDialog.setParentShell(Util.getShellToParentOn());
745         }
746         keyAssistDialog.open(bindings);
747     }
748
749     /**
750      * Processes a key press with respect to the key binding architecture. This
751      * updates the mode of the command manager, and runs the current handler for
752      * the command that matches the key sequence, if any.
753      *
754      * @param potentialKeyStrokes
755      * The key strokes that could potentially match, in the order of
756      * priority; must not be <code>null</code>.
757      * @param event
758      * The event; may be <code>null</code>.
759      * @return <code>true</code> if a command is executed; <code>false</code>
760      * otherwise.
761      */

762     public boolean press(List JavaDoc potentialKeyStrokes, Event event) {
763         if (DEBUG && DEBUG_VERBOSE) {
764             Tracing.printTrace("KEYS", //$NON-NLS-1$
765
"WorkbenchKeyboard.press(potentialKeyStrokes = " //$NON-NLS-1$
766
+ potentialKeyStrokes + ')');
767         }
768
769         /*
770          * KLUDGE. This works around a couple of specific problems in how GTK+
771          * works. The first problem is the ordering of key press events with
772          * respect to shell activation events. If on the event thread a dialog
773          * is about to open, and the user presses a key, the key press event
774          * will arrive before the shell activation event. From the perspective
775          * of Eclipse, this means that things like two "Open Type" dialogs can
776          * appear if "Ctrl+Shift+T" is pressed twice rapidly. For more
777          * information, please see Bug 95792. The second problem is simply a bug
778          * in GTK+, for which an incomplete workaround currently exists in SWT.
779          * This makes shell activation events unreliable. Please see Bug 56231
780          * and Bug 95222 for more information.
781          */

782         if ("gtk".equals(SWT.getPlatform())) { //$NON-NLS-1$
783
final Widget widget = event.widget;
784
785             // Update the contexts.
786
final ContextService contextService = (ContextService) workbench
787                     .getService(IContextService.class);
788             if ((widget instanceof Control) && (!widget.isDisposed())) {
789                 final Shell shell = ((Control) widget).getShell();
790                 contextService.updateShellKludge(shell);
791             } else {
792                 contextService.updateShellKludge();
793             }
794
795             // Update the handlers.
796
final HandlerService handlerService = (HandlerService) workbench
797                     .getService(IHandlerService.class);
798             if ((widget instanceof Control) && (!widget.isDisposed())) {
799                 final Shell shell = ((Control) widget).getShell();
800                 handlerService.updateShellKludge(shell);
801             } else {
802                 handlerService.updateShellKludge();
803             }
804         }
805
806         KeySequence errorSequence = null;
807         Collection JavaDoc errorMatch = null;
808
809         KeySequence sequenceBeforeKeyStroke = state.getCurrentSequence();
810         for (Iterator JavaDoc iterator = potentialKeyStrokes.iterator(); iterator
811                 .hasNext();) {
812             KeySequence sequenceAfterKeyStroke = KeySequence.getInstance(
813                     sequenceBeforeKeyStroke, (KeyStroke) iterator.next());
814             if (isPartialMatch(sequenceAfterKeyStroke)) {
815                 incrementState(sequenceAfterKeyStroke);
816                 return true;
817
818             } else if (isPerfectMatch(sequenceAfterKeyStroke)) {
819                 final Binding binding = getPerfectMatch(sequenceAfterKeyStroke);
820                 try {
821                     return executeCommand(binding, event)
822                             || !sequenceBeforeKeyStroke.isEmpty();
823                 } catch (final CommandException e) {
824                     logException(e, binding.getParameterizedCommand());
825                     return true;
826                 }
827
828             } else if ((keyAssistDialog != null)
829                     && (keyAssistDialog.getShell() != null)
830                     && ((event.keyCode == SWT.ARROW_DOWN)
831                             || (event.keyCode == SWT.ARROW_UP)
832                             || (event.keyCode == SWT.ARROW_LEFT)
833                             || (event.keyCode == SWT.ARROW_RIGHT)
834                             || (event.keyCode == SWT.CR)
835                             || (event.keyCode == SWT.PAGE_UP) || (event.keyCode == SWT.PAGE_DOWN))) {
836                 // We don't want to swallow keyboard navigation keys.
837
return false;
838
839             } else {
840                 Collection JavaDoc match = (InternalPolicy.currentConflicts == null ? null
841                         : (Collection JavaDoc) InternalPolicy.currentConflicts
842                                 .get(sequenceAfterKeyStroke));
843                 if (match != null) {
844                     errorSequence = sequenceAfterKeyStroke;
845                     errorMatch = match;
846                 }
847             }
848         }
849
850         resetState(true);
851         if (sequenceBeforeKeyStroke.isEmpty() && errorSequence != null) {
852             openKeyAssistShell(errorMatch);
853         }
854         return !sequenceBeforeKeyStroke.isEmpty();
855     }
856
857     /**
858      * <p>
859      * Actually performs the processing of the key event by interacting with the
860      * <code>ICommandManager</code>. If work is carried out, then the event
861      * is stopped here (i.e., <code>event.doit = false</code>). It does not
862      * do any processing if there are no matching key strokes.
863      * </p>
864      * <p>
865      * If the active <code>Shell</code> is not the same as the one to which
866      * the state is associated, then a reset occurs.
867      * </p>
868      *
869      * @param keyStrokes
870      * The set of all possible matching key strokes; must not be
871      * <code>null</code>.
872      * @param event
873      * The event to process; must not be <code>null</code>.
874      */

875     void processKeyEvent(List JavaDoc keyStrokes, Event event) {
876         // Dispatch the keyboard shortcut, if any.
877
boolean eatKey = false;
878         if (!keyStrokes.isEmpty()) {
879             eatKey = press(keyStrokes, event);
880         }
881
882         if (eatKey) {
883             switch (event.type) {
884             case SWT.KeyDown:
885                 event.doit = false;
886                 break;
887             case SWT.Traverse:
888                 event.detail = SWT.TRAVERSE_NONE;
889                 event.doit = true;
890                 break;
891             default:
892             }
893             event.type = SWT.NONE;
894         }
895     }
896
897     /**
898      * Resets the state, and cancels any running timers. If there is a
899      * <code>Shell</code> currently open, then it closes it.
900      *
901      * @param clearRememberedState
902      * Whether the remembered state (dialog bounds) of the key assist
903      * should be forgotten immediately as well.
904      */

905     private final void resetState(final boolean clearRememberedState) {
906         startTime = Long.MAX_VALUE;
907         state.reset();
908         closeMultiKeyAssistShell();
909         if ((keyAssistDialog != null) && clearRememberedState) {
910             keyAssistDialog.clearRememberedState();
911         }
912     }
913 }
914
Popular Tags