| /******************************************************************************* |
| * Copyright (c) 2000, 2006 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.keys; |
| |
| import com.ibm.icu.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ResourceBundle; |
| |
| import org.eclipse.core.commands.Command; |
| import org.eclipse.core.commands.NotEnabledException; |
| import org.eclipse.core.commands.NotHandledException; |
| import org.eclipse.core.commands.ParameterizedCommand; |
| import org.eclipse.core.commands.common.CommandException; |
| import org.eclipse.core.commands.common.NotDefinedException; |
| import org.eclipse.core.commands.util.Tracing; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.bindings.Binding; |
| import org.eclipse.jface.bindings.keys.KeySequence; |
| import org.eclipse.jface.bindings.keys.KeyStroke; |
| import org.eclipse.jface.bindings.keys.ParseException; |
| import org.eclipse.jface.bindings.keys.SWTKeySupport; |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.widgets.Combo; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.swt.widgets.Widget; |
| import org.eclipse.ui.IWindowListener; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.internal.Workbench; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.internal.contexts.ContextService; |
| import org.eclipse.ui.internal.handlers.HandlerService; |
| import org.eclipse.ui.internal.misc.Policy; |
| import org.eclipse.ui.internal.util.Util; |
| import org.eclipse.ui.keys.IBindingService; |
| |
| /** |
| * <p> |
| * Controls the keyboard input into the workbench key binding architecture. This |
| * allows key events to be programmatically pushed into the key binding |
| * architecture -- potentially triggering the execution of commands. It is used |
| * by the <code>Workbench</code> to listen for events on the |
| * <code>Display</code>. |
| * </p> |
| * <p> |
| * This class is not designed to be thread-safe. It is assumed that all access |
| * to the <code>press</code> method is done through the event loop. Accessing |
| * this method outside the event loop can cause corruption of internal state. |
| * </p> |
| * |
| * @since 3.0 |
| */ |
| public final class WorkbenchKeyboard { |
| |
| /** |
| * A display filter for handling key bindings. This filter can either be |
| * enabled or disabled. If disabled, the filter does not process incoming |
| * events. The filter starts enabled. |
| * |
| * @since 3.1 |
| */ |
| public final class KeyDownFilter implements Listener { |
| |
| /** |
| * Whether the filter is enabled. |
| */ |
| private transient boolean enabled = true; |
| |
| /** |
| * Handles an incoming traverse or key down event. |
| * |
| * @param event |
| * The event to process; must not be <code>null</code>. |
| */ |
| public final void handleEvent(final Event event) { |
| if (!enabled) { |
| return; |
| } |
| |
| if (DEBUG && DEBUG_VERBOSE) { |
| final StringBuffer buffer = new StringBuffer( |
| "Listener.handleEvent(type = "); //$NON-NLS-1$ |
| switch (event.type) { |
| case SWT.KeyDown: |
| buffer.append("KeyDown"); //$NON-NLS-1$ |
| break; |
| case SWT.Traverse: |
| buffer.append("Traverse"); //$NON-NLS-1$ |
| break; |
| default: |
| buffer.append(event.type); |
| } |
| buffer.append(", stateMask = 0x" //$NON-NLS-1$ |
| + Integer.toHexString(event.stateMask) |
| + ", keyCode = 0x" //$NON-NLS-1$ |
| + Integer.toHexString(event.keyCode) + ", time = " //$NON-NLS-1$ |
| + event.time + ", character = 0x" //$NON-NLS-1$ |
| + Integer.toHexString(event.character) + ")"); //$NON-NLS-1$ |
| Tracing.printTrace("KEYS", buffer.toString()); //$NON-NLS-1$ |
| } |
| |
| filterKeySequenceBindings(event); |
| } |
| |
| /** |
| * Returns whether the key binding filter is enabled. |
| * |
| * @return Whether the key filter is enabled. |
| */ |
| public final boolean isEnabled() { |
| return enabled; |
| } |
| |
| /** |
| * Sets whether this filter should be enabled or disabled. |
| * |
| * @param enabled |
| * Whether key binding filter should be enabled. |
| */ |
| public final void setEnabled(final boolean enabled) { |
| this.enabled = enabled; |
| } |
| } |
| |
| /** |
| * Whether the keyboard should kick into debugging mode. This causes real |
| * key bindings trapped by the key binding architecture to be reported. |
| */ |
| private static final boolean DEBUG = Policy.DEBUG_KEY_BINDINGS; |
| |
| /** |
| * Whether the keyboard should report every event received by its global |
| * filter. |
| */ |
| private static final boolean DEBUG_VERBOSE = Policy.DEBUG_KEY_BINDINGS_VERBOSE; |
| |
| /** |
| * The time in milliseconds to wait after pressing a key before displaying |
| * the key assist dialog. |
| */ |
| private static final int DELAY = 1000; |
| |
| /** The collection of keys that are to be processed out-of-order. */ |
| static KeySequence outOfOrderKeys; |
| |
| /** |
| * The translation bundle in which to look up internationalized text. |
| */ |
| private final static ResourceBundle RESOURCE_BUNDLE = ResourceBundle |
| .getBundle(WorkbenchKeyboard.class.getName()); |
| |
| static { |
| |
| try { |
| outOfOrderKeys = KeySequence.getInstance("ESC DEL"); //$NON-NLS-1$ |
| } catch (ParseException e) { |
| outOfOrderKeys = KeySequence.getInstance(); |
| String message = "Could not parse out-of-order keys definition: 'ESC DEL'. Continuing with no out-of-order keys."; //$NON-NLS-1$ |
| WorkbenchPlugin.log(message, new Status(IStatus.ERROR, |
| WorkbenchPlugin.PI_WORKBENCH, 0, message, e)); |
| } |
| } |
| |
| /** |
| * Generates any key strokes that are near matches to the given event. The |
| * first such key stroke is always the exactly matching key stroke. |
| * |
| * @param event |
| * The event from which the key strokes should be generated; must |
| * not be <code>null</code>. |
| * @return The set of nearly matching key strokes. It is never |
| * <code>null</code>, but may be empty. |
| */ |
| public static List generatePossibleKeyStrokes(Event event) { |
| final List keyStrokes = new ArrayList(3); |
| |
| /* |
| * If this is not a keyboard event, then there are no key strokes. This |
| * can happen if we are listening to focus traversal events. |
| */ |
| if ((event.stateMask == 0) && (event.keyCode == 0) |
| && (event.character == 0)) { |
| return keyStrokes; |
| } |
| |
| // Add each unique key stroke to the list for consideration. |
| final int firstAccelerator = SWTKeySupport |
| .convertEventToUnmodifiedAccelerator(event); |
| keyStrokes.add(SWTKeySupport |
| .convertAcceleratorToKeyStroke(firstAccelerator)); |
| |
| // We shouldn't allow delete to undergo shift resolution. |
| if (event.character == SWT.DEL) { |
| return keyStrokes; |
| } |
| |
| final int secondAccelerator = SWTKeySupport |
| .convertEventToUnshiftedModifiedAccelerator(event); |
| if (secondAccelerator != firstAccelerator) { |
| keyStrokes.add(SWTKeySupport |
| .convertAcceleratorToKeyStroke(secondAccelerator)); |
| } |
| |
| final int thirdAccelerator = SWTKeySupport |
| .convertEventToModifiedAccelerator(event); |
| if ((thirdAccelerator != secondAccelerator) |
| && (thirdAccelerator != firstAccelerator)) { |
| keyStrokes.add(SWTKeySupport |
| .convertAcceleratorToKeyStroke(thirdAccelerator)); |
| } |
| |
| return keyStrokes; |
| } |
| |
| /** |
| * <p> |
| * Determines whether the given event represents a key press that should be |
| * handled as an out-of-order event. An out-of-order key press is one that |
| * is passed to the focus control first. Only if the focus control fails to |
| * respond will the regular key bindings get applied. |
| * </p> |
| * <p> |
| * Care must be taken in choosing which keys are chosen as out-of-order |
| * keys. This method has only been designed and test to work with the |
| * unmodified "Escape" key stroke. |
| * </p> |
| * |
| * @param keyStrokes |
| * The key stroke in which to look for out-of-order keys; must |
| * not be <code>null</code>. |
| * @return <code>true</code> if the key is an out-of-order key; |
| * <code>false</code> otherwise. |
| */ |
| private static boolean isOutOfOrderKey(List keyStrokes) { |
| // Compare to see if one of the possible key strokes is out of order. |
| final KeyStroke[] outOfOrderKeyStrokes = outOfOrderKeys.getKeyStrokes(); |
| final int outOfOrderKeyStrokesLength = outOfOrderKeyStrokes.length; |
| for (int i = 0; i < outOfOrderKeyStrokesLength; i++) { |
| if (keyStrokes.contains(outOfOrderKeyStrokes[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * The binding manager to be used to resolve key bindings. This member |
| * variable will be <code>null</code> if it has not yet been initialized. |
| */ |
| private IBindingService bindingService = null; |
| |
| /** |
| * The <code>KeyAssistDialog</code> displayed to the user to assist them |
| * in completing a multi-stroke keyboard shortcut. |
| * |
| * @since 3.1 |
| */ |
| private KeyAssistDialog keyAssistDialog = null; |
| |
| /** |
| * The listener that runs key events past the global key bindings. |
| */ |
| private final KeyDownFilter keyDownFilter = new KeyDownFilter(); |
| |
| /** |
| * The single out-of-order listener used by the workbench. This listener is |
| * attached to one widget at a time, and is used to catch key down events |
| * after all processing is done. This technique is used so that some keys |
| * will have their native behaviour happen first. |
| * |
| * @since 3.1 |
| */ |
| private final OutOfOrderListener outOfOrderListener = new OutOfOrderListener( |
| this); |
| |
| /** |
| * The single out-of-order verify listener used by the workbench. This |
| * listener is attached to one</code> StyledText</code> at a time, and is |
| * used to catch verify events after all processing is done. This technique |
| * is used so that some keys will have their native behaviour happen first. |
| * |
| * @since 3.1 |
| */ |
| private final OutOfOrderVerifyListener outOfOrderVerifyListener = new OutOfOrderVerifyListener( |
| outOfOrderListener); |
| |
| /** |
| * The time at which the last timer was started. This is used to judge if a |
| * sufficient amount of time has elapsed. This is simply the output of |
| * <code>System.currentTimeMillis()</code>. |
| */ |
| private long startTime = Long.MAX_VALUE; |
| |
| /** |
| * The mode is the current state of the key binding architecture. In the |
| * case of multi-stroke key bindings, this can be a partially complete key |
| * binding. |
| */ |
| private final KeyBindingState state; |
| |
| /** |
| * The window listener responsible for maintaining internal state as the |
| * focus moves between windows on the desktop. |
| */ |
| private final IWindowListener windowListener = new IWindowListener() { |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow) |
| */ |
| public void windowActivated(IWorkbenchWindow window) { |
| checkActiveWindow(window); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow) |
| */ |
| public void windowClosed(IWorkbenchWindow window) { |
| // Do nothing. |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow) |
| */ |
| public void windowDeactivated(IWorkbenchWindow window) { |
| // Do nothing |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow) |
| */ |
| public void windowOpened(IWorkbenchWindow window) { |
| // Do nothing. |
| } |
| }; |
| |
| /** |
| * The workbench on which this keyboard interface should act. |
| */ |
| private final IWorkbench workbench; |
| |
| /** |
| * Constructs a new instance of <code>WorkbenchKeyboard</code> associated |
| * with a particular workbench. |
| * |
| * @param associatedWorkbench |
| * The workbench with which this keyboard interface should work; |
| * must not be <code>null</code>. |
| * @since 3.1 |
| */ |
| public WorkbenchKeyboard(Workbench associatedWorkbench) { |
| workbench = associatedWorkbench; |
| state = new KeyBindingState(associatedWorkbench); |
| workbench.addWindowListener(windowListener); |
| } |
| |
| /** |
| * Verifies that the active workbench window is the same as the workbench |
| * window associated with the state. This is used to verify that the state |
| * is properly reset as focus changes. When they are not the same, the state |
| * is reset and associated with the newly activated window. |
| * |
| * @param window |
| * The activated window; must not be <code>null</code>. |
| */ |
| private void checkActiveWindow(IWorkbenchWindow window) { |
| if (!window.equals(state.getAssociatedWindow())) { |
| resetState(true); |
| state.setAssociatedWindow(window); |
| } |
| } |
| |
| /** |
| * Closes the multi-stroke key binding assistant shell, if it exists and |
| * isn't already disposed. |
| */ |
| private void closeMultiKeyAssistShell() { |
| if (keyAssistDialog != null) { |
| final Shell shell = keyAssistDialog.getShell(); |
| if ((shell != null) && (!shell.isDisposed()) && (shell.isVisible())) { |
| keyAssistDialog.close(true); |
| } |
| } |
| } |
| |
| /** |
| * Performs the actual execution of the command by looking up the current |
| * handler from the command manager. If there is a handler and it is |
| * enabled, then it tries the actual execution. Execution failures are |
| * logged. When this method completes, the key binding state is reset. |
| * |
| * @param binding |
| * The binding that should be executed; should not be |
| * <code>null</code>. |
| * @param trigger |
| * The triggering event; may be <code>null</code>. |
| * @return <code>true</code> if there was a handler; <code>false</code> |
| * otherwise. |
| * @throws CommandException |
| * if the handler does not complete execution for some reason. |
| * It is up to the caller of this method to decide whether to |
| * log the message, display a dialog, or ignore this exception |
| * entirely. |
| */ |
| final boolean executeCommand(final Binding binding, final Event trigger) |
| throws CommandException { |
| final ParameterizedCommand parameterizedCommand = binding |
| .getParameterizedCommand(); |
| |
| if (DEBUG) { |
| Tracing.printTrace("KEYS", //$NON-NLS-1$ |
| "WorkbenchKeyboard.executeCommand(commandId = '" //$NON-NLS-1$ |
| + parameterizedCommand.getId() + "', parameters = " //$NON-NLS-1$ |
| + parameterizedCommand.getParameterMap() + ')'); |
| } |
| |
| // Reset the key binding state (close window, clear status line, etc.) |
| resetState(false); |
| |
| // Dispatch to the handler. |
| final Command command = parameterizedCommand.getCommand(); |
| final boolean commandDefined = command.isDefined(); |
| final boolean commandHandled = command.isHandled(); |
| final boolean commandEnabled = command.isEnabled(); |
| |
| if (DEBUG && DEBUG_VERBOSE) { |
| if (!commandDefined) { |
| Tracing.printTrace("KEYS", " not defined"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } else if (!commandHandled) { |
| Tracing.printTrace("KEYS", " not handled"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } else if (!commandEnabled) { |
| Tracing.printTrace("KEYS", " not enabled"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| try { |
| final IHandlerService handlerService = (IHandlerService) workbench.getService(IHandlerService.class); |
| handlerService.executeCommand(parameterizedCommand, trigger); |
| } catch (final NotDefinedException e) { |
| // The command is not defined. Forwarded to the IExecutionListener. |
| } catch (final NotEnabledException e) { |
| // The command is not enabled. Forwarded to the IExecutionListener. |
| } catch (final NotHandledException e) { |
| // There is no handler. Forwarded to the IExecutionListener. |
| } |
| |
| /* |
| * Now that the command has executed (and had the opportunity to use the |
| * remembered state of the dialog), it is safe to delete that |
| * information. |
| */ |
| if (keyAssistDialog != null) { |
| keyAssistDialog.clearRememberedState(); |
| } |
| |
| return (commandDefined && commandHandled); |
| } |
| |
| /** |
| * <p> |
| * Launches the command matching a the typed key. This filter an incoming |
| * <code>SWT.KeyDown</code> or <code>SWT.Traverse</code> event at the |
| * level of the display (i.e., before it reaches the widgets). It does not |
| * allow processing in a dialog or if the key strokes does not contain a |
| * natural key. |
| * </p> |
| * <p> |
| * Some key strokes (defined as a property) are declared as out-of-order |
| * keys. This means that they are processed by the widget <em>first</em>. |
| * Only if the other widget listeners do no useful work does it try to |
| * process key bindings. For example, "ESC" can cancel the current widget |
| * action, if there is one, without triggering key bindings. |
| * </p> |
| * |
| * @param event |
| * The incoming event; must not be <code>null</code>. |
| */ |
| private void filterKeySequenceBindings(Event event) { |
| /* |
| * Only process key strokes containing natural keys to trigger key |
| * bindings. |
| */ |
| if ((event.keyCode & SWT.MODIFIER_MASK) != 0) { |
| return; |
| } |
| |
| // Allow special key out-of-order processing. |
| List keyStrokes = generatePossibleKeyStrokes(event); |
| if (isOutOfOrderKey(keyStrokes)) { |
| Widget widget = event.widget; |
| if ((event.character == SWT.DEL) |
| && ((event.stateMask & SWT.MODIFIER_MASK) == 0) |
| && ((widget instanceof Text) || (widget instanceof Combo))) { |
| /* |
| * KLUDGE. Bug 54654. The text widget relies on no listener |
| * doing any work before dispatching the native delete event. |
| * This does not work, as we are restricted to listeners. |
| * However, it can be said that pressing a delete key in a text |
| * widget will never use key bindings. This can be shown be |
| * considering how the event dispatching is expected to work in |
| * a text widget. So, we should do nothing ... ever. |
| */ |
| return; |
| |
| } else if (widget instanceof StyledText) { |
| |
| if (event.type == SWT.KeyDown) { |
| /* |
| * KLUDGE. Some people try to do useful work in verify |
| * listeners. The way verify listeners work in SWT, we need |
| * to verify the key as well; otherwise, we can't detect |
| * that useful work has been done. |
| */ |
| if (!outOfOrderVerifyListener.isActive(event.time)) { |
| ((StyledText) widget) |
| .addVerifyKeyListener(outOfOrderVerifyListener); |
| outOfOrderVerifyListener.setActive(event.time); |
| } |
| } |
| |
| } else { |
| if (!outOfOrderListener.isActive(event.time)) { |
| widget.addListener(SWT.KeyDown, outOfOrderListener); |
| outOfOrderListener.setActive(event.time); |
| } |
| |
| } |
| |
| /* |
| * Otherwise, we count on a key down arriving eventually. Expecting |
| * out of order handling on Ctrl+Tab, for example, is a bad idea |
| * (stick to keys that are not window traversal keys). |
| */ |
| |
| } else { |
| processKeyEvent(keyStrokes, event); |
| |
| } |
| } |
| |
| /** |
| * An accessor for the filter that processes key down and traverse events on |
| * the display. |
| * |
| * @return The global key down and traverse filter; never <code>null</code>. |
| */ |
| public KeyDownFilter getKeyDownFilter() { |
| return keyDownFilter; |
| } |
| |
| /** |
| * Determines whether the key sequence is a perfect match for any command. |
| * If there is a match, then the corresponding command identifier is |
| * returned. |
| * |
| * @param keySequence |
| * The key sequence to check for a match; must never be |
| * <code>null</code>. |
| * @return The binding for the perfectly matching command; <code>null</code> |
| * if no command matches. |
| */ |
| private Binding getPerfectMatch(KeySequence keySequence) { |
| if (bindingService == null) { |
| bindingService = (IBindingService) workbench.getService(IBindingService.class); |
| } |
| return bindingService.getPerfectMatch(keySequence); |
| } |
| |
| final KeySequence getBuffer() { |
| return state.getCurrentSequence(); |
| } |
| |
| /** |
| * Changes the key binding state to the given value. This should be an |
| * incremental change, but there are no checks to guarantee this is so. It |
| * also sets up a <code>Shell</code> to be displayed after one second has |
| * elapsed. This shell will show the user the possible completions for what |
| * they have typed. |
| * |
| * @param sequence |
| * The new key sequence for the state; should not be |
| * <code>null</code>. |
| */ |
| private void incrementState(KeySequence sequence) { |
| // Record the starting time. |
| startTime = System.currentTimeMillis(); |
| final long myStartTime = startTime; |
| |
| // Update the state. |
| state.setCurrentSequence(sequence); |
| state.setAssociatedWindow(workbench.getActiveWorkbenchWindow()); |
| |
| // After some time, open a shell displaying the possible completions. |
| final Display display = workbench.getDisplay(); |
| display.timerExec(DELAY, new Runnable() { |
| public void run() { |
| if ((System.currentTimeMillis() > (myStartTime - DELAY)) |
| && (startTime == myStartTime)) { |
| openMultiKeyAssistShell(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Determines whether the key sequence partially matches on of the active |
| * key bindings. |
| * |
| * @param keySequence |
| * The key sequence to check for a partial match; must never be |
| * <code>null</code>. |
| * @return <code>true</code> if there is a partial match; |
| * <code>false</code> otherwise. |
| */ |
| private boolean isPartialMatch(KeySequence keySequence) { |
| if (bindingService == null) { |
| bindingService = (IBindingService) workbench.getService(IBindingService.class); |
| } |
| return bindingService.isPartialMatch(keySequence); |
| } |
| |
| /** |
| * Determines whether the key sequence perfectly matches on of the active |
| * key bindings. |
| * |
| * @param keySequence |
| * The key sequence to check for a perfect match; must never be |
| * <code>null</code>. |
| * @return <code>true</code> if there is a perfect match; |
| * <code>false</code> otherwise. |
| */ |
| private boolean isPerfectMatch(KeySequence keySequence) { |
| if (bindingService == null) { |
| bindingService = (IBindingService) workbench.getService(IBindingService.class); |
| } |
| return bindingService.isPerfectMatch(keySequence); |
| } |
| |
| /** |
| * Logs the given exception, and opens a dialog explaining the failure. |
| * |
| * @param e |
| * The exception to log; must not be <code>null</code>. |
| * @param command |
| * The parameterized command for the binding to execute; may be |
| * <code>null</code>. |
| */ |
| final void logException(final CommandException e, |
| final ParameterizedCommand command) { |
| Throwable nestedException = e.getCause(); |
| Throwable exception = (nestedException == null) ? e : nestedException; |
| |
| // If we can, include the command name in the exception. |
| String message = null; |
| if (command != null) { |
| try { |
| final String name = command.getCommand().getName(); |
| message = MessageFormat.format(Util.translateString( |
| RESOURCE_BUNDLE, "ExecutionError.MessageCommandName"), //$NON-NLS-1$ |
| new Object[] { name }); |
| } catch (final NotDefinedException nde) { |
| // Fall through (message == null) |
| } |
| } |
| if (message == null) { |
| message = Util.translateString(RESOURCE_BUNDLE, |
| "ExecutionError.Message"); //$NON-NLS-1$ |
| } |
| |
| String title = Util.translateString(RESOURCE_BUNDLE, |
| "ExecutionError.Title"); //$NON-NLS-1$ |
| String exceptionMessage = exception.getMessage(); |
| if (exceptionMessage == null) { |
| exceptionMessage = exception.getClass().getName(); |
| } |
| IStatus status = new Status(IStatus.ERROR, |
| WorkbenchPlugin.PI_WORKBENCH, 0, exceptionMessage, exception); |
| WorkbenchPlugin.log(message, status); |
| ErrorDialog.openError(Util.getShellToParentOn(), title, message, status); |
| } |
| |
| /** |
| * Opens a <code>KeyAssistDialog</code> to assist the user in completing a |
| * multi-stroke key binding. This method lazily creates a |
| * <code>keyAssistDialog</code> and shares it between executions. |
| */ |
| public final void openMultiKeyAssistShell() { |
| if (keyAssistDialog == null) { |
| keyAssistDialog = new KeyAssistDialog(workbench, this, state); |
| } |
| if (keyAssistDialog.getShell() == null) { |
| keyAssistDialog.setParentShell(Util.getShellToParentOn()); |
| } |
| keyAssistDialog.open(); |
| } |
| |
| /** |
| * Processes a key press with respect to the key binding architecture. This |
| * updates the mode of the command manager, and runs the current handler for |
| * the command that matches the key sequence, if any. |
| * |
| * @param potentialKeyStrokes |
| * The key strokes that could potentially match, in the order of |
| * priority; must not be <code>null</code>. |
| * @param event |
| * The event; may be <code>null</code>. |
| * @return <code>true</code> if a command is executed; <code>false</code> |
| * otherwise. |
| */ |
| public boolean press(List potentialKeyStrokes, Event event) { |
| if (DEBUG && DEBUG_VERBOSE) { |
| Tracing.printTrace("KEYS", //$NON-NLS-1$ |
| "WorkbenchKeyboard.press(potentialKeyStrokes = " //$NON-NLS-1$ |
| + potentialKeyStrokes + ')'); |
| } |
| |
| /* |
| * KLUDGE. This works around a couple of specific problems in how GTK+ |
| * works. The first problem is the ordering of key press events with |
| * respect to shell activation events. If on the event thread a dialog |
| * is about to open, and the user presses a key, the key press event |
| * will arrive before the shell activation event. From the perspective |
| * of Eclipse, this means that things like two "Open Type" dialogs can |
| * appear if "Ctrl+Shift+T" is pressed twice rapidly. For more |
| * information, please see Bug 95792. The second problem is simply a bug |
| * in GTK+, for which an incomplete workaround currently exists in SWT. |
| * This makes shell activation events unreliable. Please see Bug 56231 |
| * and Bug 95222 for more information. |
| */ |
| if ("gtk".equals(SWT.getPlatform())) { //$NON-NLS-1$ |
| final Widget widget = event.widget; |
| |
| // Update the contexts. |
| final ContextService contextService = (ContextService) workbench.getService(IContextService.class); |
| if ((widget instanceof Control) && (!widget.isDisposed())) { |
| final Shell shell = ((Control) widget).getShell(); |
| contextService.updateShellKludge(shell); |
| } else { |
| contextService.updateShellKludge(); |
| } |
| |
| // Update the handlers. |
| final HandlerService handlerService = (HandlerService) workbench.getService(IHandlerService.class); |
| if ((widget instanceof Control) && (!widget.isDisposed())) { |
| final Shell shell = ((Control) widget).getShell(); |
| handlerService.updateShellKludge(shell); |
| } else { |
| handlerService.updateShellKludge(); |
| } |
| } |
| |
| KeySequence sequenceBeforeKeyStroke = state.getCurrentSequence(); |
| for (Iterator iterator = potentialKeyStrokes.iterator(); iterator |
| .hasNext();) { |
| KeySequence sequenceAfterKeyStroke = KeySequence.getInstance( |
| sequenceBeforeKeyStroke, (KeyStroke) iterator.next()); |
| if (isPartialMatch(sequenceAfterKeyStroke)) { |
| incrementState(sequenceAfterKeyStroke); |
| return true; |
| |
| } else if (isPerfectMatch(sequenceAfterKeyStroke)) { |
| final Binding binding = getPerfectMatch(sequenceAfterKeyStroke); |
| try { |
| return executeCommand(binding, event) |
| || !sequenceBeforeKeyStroke.isEmpty(); |
| } catch (final CommandException e) { |
| logException(e, binding.getParameterizedCommand()); |
| return true; |
| } |
| |
| } else if ((keyAssistDialog != null) |
| && (keyAssistDialog.getShell() != null) |
| && ((event.keyCode == SWT.ARROW_DOWN) |
| || (event.keyCode == SWT.ARROW_UP) |
| || (event.keyCode == SWT.ARROW_LEFT) |
| || (event.keyCode == SWT.ARROW_RIGHT) |
| || (event.keyCode == SWT.CR) |
| || (event.keyCode == SWT.PAGE_UP) || (event.keyCode == SWT.PAGE_DOWN))) { |
| // We don't want to swallow keyboard navigation keys. |
| return false; |
| |
| } |
| } |
| |
| resetState(true); |
| return !sequenceBeforeKeyStroke.isEmpty(); |
| } |
| |
| /** |
| * <p> |
| * Actually performs the processing of the key event by interacting with the |
| * <code>ICommandManager</code>. If work is carried out, then the event |
| * is stopped here (i.e., <code>event.doit = false</code>). It does not |
| * do any processing if there are no matching key strokes. |
| * </p> |
| * <p> |
| * If the active <code>Shell</code> is not the same as the one to which |
| * the state is associated, then a reset occurs. |
| * </p> |
| * |
| * @param keyStrokes |
| * The set of all possible matching key strokes; must not be |
| * <code>null</code>. |
| * @param event |
| * The event to process; must not be <code>null</code>. |
| */ |
| void processKeyEvent(List keyStrokes, Event event) { |
| // Dispatch the keyboard shortcut, if any. |
| boolean eatKey = false; |
| if (!keyStrokes.isEmpty()) { |
| eatKey = press(keyStrokes, event); |
| } |
| |
| if (eatKey) { |
| switch (event.type) { |
| case SWT.KeyDown: |
| event.doit = false; |
| break; |
| case SWT.Traverse: |
| event.detail = SWT.TRAVERSE_NONE; |
| event.doit = true; |
| break; |
| default: |
| } |
| event.type = SWT.NONE; |
| } |
| } |
| |
| /** |
| * Resets the state, and cancels any running timers. If there is a |
| * <code>Shell</code> currently open, then it closes it. |
| * |
| * @param clearRememberedState |
| * Whether the remembered state (dialog bounds) of the key assist |
| * should be forgotten immediately as well. |
| */ |
| private final void resetState(final boolean clearRememberedState) { |
| startTime = Long.MAX_VALUE; |
| state.reset(); |
| closeMultiKeyAssistShell(); |
| if ((keyAssistDialog != null) && clearRememberedState) { |
| keyAssistDialog.clearRememberedState(); |
| } |
| } |
| } |