Package org.eclipse.ui.internal.keys

Source Code of org.eclipse.ui.internal.keys.WorkbenchKeyboard$KeyDownFilter

/*******************************************************************************
* Copyright (c) 2000, 2007 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 java.util.ArrayList;
import java.util.Collection;
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.internal.InternalPolicy;
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.misc.StatusUtil;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.statushandlers.StatusManager;

import com.ibm.icu.text.MessageFormat;

/**
* <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 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);
    StatusUtil.handleStatus(message, exception, StatusManager.SHOW);
  }

  /**
   * 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();
  }

  /**
   * Opens the key assist dialog to offer the user the choice of a binding to
   * pick from the collection of bindings.
   *
   * @param bindings
   *            a collection of Binding objects
   * @since 3.3
   */
  public final void openKeyAssistShell(final Collection bindings) {
    if (keyAssistDialog == null) {
      keyAssistDialog = new KeyAssistDialog(workbench,
          WorkbenchKeyboard.this, state);
    }
    if (keyAssistDialog.getShell() == null) {
      keyAssistDialog.setParentShell(Util.getShellToParentOn());
    }
    keyAssistDialog.open(bindings);
  }

  /**
   * 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 errorSequence = null;
    Collection errorMatch = null;

    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;

      } else {
        Collection match = (InternalPolicy.currentConflicts == null ? null
            : (Collection) InternalPolicy.currentConflicts
                .get(sequenceAfterKeyStroke));
        if (match != null) {
          errorSequence = sequenceAfterKeyStroke;
          errorMatch = match;
        }
      }
    }

    resetState(true);
    if (sequenceBeforeKeyStroke.isEmpty() && errorSequence != null) {
      openKeyAssistShell(errorMatch);
    }
    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();
    }
  }
}
TOP

Related Classes of org.eclipse.ui.internal.keys.WorkbenchKeyboard$KeyDownFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.