@Nullable
 public static KeyStroke getKeyStroke(@NotNull final ShortcutSet shortcutSet) {
   final Shortcut[] shortcuts = shortcutSet.getShortcuts();
   if (shortcuts.length == 0 || !(shortcuts[0] instanceof KeyboardShortcut)) return null;
   final KeyboardShortcut shortcut = (KeyboardShortcut) shortcuts[0];
   if (shortcut.getSecondKeyStroke() != null) {
     return null;
   }
   return shortcut.getFirstKeyStroke();
 }
 private ArrayList<Pair<AnAction, KeyStroke>> getSecondKeystrokeActions() {
   ArrayList<Pair<AnAction, KeyStroke>> secondKeyStrokes =
       new ArrayList<Pair<AnAction, KeyStroke>>();
   for (AnAction action : myContext.getActions()) {
     Shortcut[] shortcuts = action.getShortcutSet().getShortcuts();
     for (Shortcut shortcut : shortcuts) {
       if (shortcut instanceof KeyboardShortcut) {
         KeyboardShortcut keyShortcut = (KeyboardShortcut) shortcut;
         if (keyShortcut.getFirstKeyStroke().equals(myFirstKeyStroke)) {
           secondKeyStrokes.add(
               new Pair<AnAction, KeyStroke>(action, keyShortcut.getSecondKeyStroke()));
         }
       }
     }
   }
   return secondKeyStrokes;
 }
  private int getMnemonicCharIndex(String text) {
    final int mnemonicIndex = myPresentation.getDisplayedMnemonicIndex();
    if (mnemonicIndex != -1) {
      return mnemonicIndex;
    }
    final ShortcutSet shortcutSet = myAction.getShortcutSet();
    final Shortcut[] shortcuts = shortcutSet.getShortcuts();
    for (Shortcut shortcut : shortcuts) {
      if (!(shortcut instanceof KeyboardShortcut)) continue;

      KeyboardShortcut keyboardShortcut = (KeyboardShortcut) shortcut;
      if (keyboardShortcut.getSecondKeyStroke()
          == null) { // we are interested only in "mnemonic-like" shortcuts
        final KeyStroke keyStroke = keyboardShortcut.getFirstKeyStroke();
        final int modifiers = keyStroke.getModifiers();
        if (BitUtil.isSet(modifiers, InputEvent.ALT_MASK)) {
          return (keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED)
              ? text.indexOf(keyStroke.getKeyChar())
              : text.indexOf(KeyEvent.getKeyText(keyStroke.getKeyCode()));
        }
      }
    }
    return -1;
  }
  public static String getShortcutText(@NotNull Shortcut shortcut) {
    String s = "";

    if (shortcut instanceof KeyboardShortcut) {
      KeyboardShortcut keyboardShortcut = (KeyboardShortcut) shortcut;

      String acceleratorText = getKeystrokeText(keyboardShortcut.getFirstKeyStroke());
      if (!acceleratorText.isEmpty()) {
        s = acceleratorText;
      }

      acceleratorText = getKeystrokeText(keyboardShortcut.getSecondKeyStroke());
      if (!acceleratorText.isEmpty()) {
        s += ", " + acceleratorText;
      }
    } else if (shortcut instanceof MouseShortcut) {
      MouseShortcut mouseShortcut = (MouseShortcut) shortcut;
      s =
          getMouseShortcutText(
              mouseShortcut.getButton(),
              mouseShortcut.getModifiers(),
              mouseShortcut.getClickCount());
    } else if (shortcut instanceof KeyboardModifierGestureShortcut) {
      final KeyboardModifierGestureShortcut gestureShortcut =
          (KeyboardModifierGestureShortcut) shortcut;
      s =
          gestureShortcut.getType() == KeyboardGestureAction.ModifierType.dblClick
              ? "Press, release and hold "
              : "Hold ";
      s += getKeystrokeText(gestureShortcut.getStroke());
    } else {
      throw new IllegalArgumentException(
          "unknown shortcut class: " + shortcut.getClass().getCanonicalName());
    }
    return s;
  }
  @NotNull
  private JBPopup createUsagePopup(
      @NotNull final List<Usage> usages,
      @NotNull final UsageInfoToUsageConverter.TargetElementsDescriptor descriptor,
      @NotNull Set<UsageNode> visibleNodes,
      @NotNull final FindUsagesHandler handler,
      final Editor editor,
      @NotNull final RelativePoint popupPosition,
      final int maxUsages,
      @NotNull final UsageViewImpl usageView,
      @NotNull final FindUsagesOptions options,
      @NotNull final JTable table,
      @NotNull final UsageViewPresentation presentation,
      @NotNull final AsyncProcessIcon processIcon,
      boolean hadMoreSeparator) {
    table.setRowHeight(PlatformIcons.CLASS_ICON.getIconHeight() + 2);
    table.setShowGrid(false);
    table.setShowVerticalLines(false);
    table.setShowHorizontalLines(false);
    table.setTableHeader(null);
    table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
    table.setIntercellSpacing(new Dimension(0, 0));

    PopupChooserBuilder builder = new PopupChooserBuilder(table);
    final String title = presentation.getTabText();
    if (title != null) {
      String result = getFullTitle(usages, title, hadMoreSeparator, visibleNodes.size() - 1, true);
      builder.setTitle(result);
      builder.setAdText(getSecondInvocationTitle(options, handler));
    }

    builder.setMovable(true).setResizable(true);
    builder.setItemChoosenCallback(
        new Runnable() {
          @Override
          public void run() {
            int[] selected = table.getSelectedRows();
            for (int i : selected) {
              Object value = table.getValueAt(i, 0);
              if (value instanceof UsageNode) {
                Usage usage = ((UsageNode) value).getUsage();
                if (usage == MORE_USAGES_SEPARATOR) {
                  appendMoreUsages(editor, popupPosition, handler, maxUsages);
                  return;
                }
                navigateAndHint(usage, null, handler, popupPosition, maxUsages, options);
              }
            }
          }
        });
    final JBPopup[] popup = new JBPopup[1];

    KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut();
    if (shortcut != null) {
      new DumbAwareAction() {
        @Override
        public void actionPerformed(AnActionEvent e) {
          popup[0].cancel();
          showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
        }
      }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table);
    }
    shortcut = getShowUsagesShortcut();
    if (shortcut != null) {
      new DumbAwareAction() {
        @Override
        public void actionPerformed(AnActionEvent e) {
          popup[0].cancel();
          searchEverywhere(options, handler, editor, popupPosition, maxUsages);
        }
      }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table);
    }

    InplaceButton settingsButton =
        createSettingsButton(
            handler,
            popupPosition,
            editor,
            maxUsages,
            new Runnable() {
              @Override
              public void run() {
                popup[0].cancel();
              }
            });

    ActiveComponent spinningProgress =
        new ActiveComponent() {
          @Override
          public void setActive(boolean active) {}

          @Override
          public JComponent getComponent() {
            return processIcon;
          }
        };
    builder.setCommandButton(new CompositeActiveComponent(spinningProgress, settingsButton));

    DefaultActionGroup toolbar = new DefaultActionGroup();
    usageView.addFilteringActions(toolbar);

    toolbar.add(UsageGroupingRuleProviderImpl.createGroupByFileStructureAction(usageView));
    toolbar.add(
        new AnAction(
            "Open Find Usages Toolwindow",
            "Show all usages in a separate toolwindow",
            AllIcons.Toolwindows.ToolWindowFind) {
          {
            AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_USAGES);
            setShortcutSet(action.getShortcutSet());
          }

          @Override
          public void actionPerformed(AnActionEvent e) {
            hideHints();
            popup[0].cancel();
            FindUsagesManager findUsagesManager =
                ((FindManagerImpl) FindManager.getInstance(usageView.getProject()))
                    .getFindUsagesManager();

            findUsagesManager.findUsages(
                handler.getPrimaryElements(),
                handler.getSecondaryElements(),
                handler,
                options,
                FindSettings.getInstance().isSkipResultsWithOneUsage());
          }
        });

    ActionToolbar actionToolbar =
        ActionManager.getInstance()
            .createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, toolbar, true);
    actionToolbar.setReservePlaceAutoPopupIcon(false);
    final JComponent toolBar = actionToolbar.getComponent();
    toolBar.setOpaque(false);
    builder.setSettingButton(toolBar);

    popup[0] = builder.createPopup();
    JComponent content = popup[0].getContent();

    myWidth =
        (int)
            (toolBar.getPreferredSize().getWidth()
                + new JLabel(
                        getFullTitle(
                            usages, title, hadMoreSeparator, visibleNodes.size() - 1, true))
                    .getPreferredSize()
                    .getWidth()
                + settingsButton.getPreferredSize().getWidth());
    myWidth = -1;
    for (AnAction action : toolbar.getChildren(null)) {
      action.unregisterCustomShortcutSet(usageView.getComponent());
      action.registerCustomShortcutSet(action.getShortcutSet(), content);
    }

    return popup[0];
  }
/**
 * This class is automaton with finite number of state.
 *
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public final class IdeKeyEventDispatcher implements Disposable {
  @NonNls private static final String GET_CACHED_STROKE_METHOD_NAME = "getCachedStroke";

  private KeyStroke myFirstKeyStroke;
  /**
   * When we "dispatch" key event via keymap, i.e. when registered action has been executed instead
   * of event dispatching, then we have to consume all following KEY_RELEASED and KEY_TYPED event
   * because they are not valid.
   */
  private boolean myPressedWasProcessed;

  private KeyState myState = KeyState.STATE_INIT;

  private final PresentationFactory myPresentationFactory = new PresentationFactory();
  private boolean myDisposed = false;
  private boolean myLeftCtrlPressed = false;
  private boolean myRightAltPressed = false;

  private final KeyboardGestureProcessor myKeyGestureProcessor = new KeyboardGestureProcessor(this);

  private final KeyProcessorContext myContext = new KeyProcessorContext();
  private final IdeEventQueue myQueue;

  private final Alarm mySecondStrokeTimeout = new Alarm();
  private final Runnable mySecondStrokeTimeoutRunnable =
      new Runnable() {
        public void run() {
          if (myState == KeyState.STATE_WAIT_FOR_SECOND_KEYSTROKE) {
            resetState();
            if (myContext != null) {
              final DataContext dataContext = myContext.getDataContext();
              StatusBar.Info.set(
                  null, dataContext == null ? null : PlatformDataKeys.PROJECT.getData(dataContext));
            }
          }
        }
      };

  private final Alarm mySecondKeystrokePopupTimeout = new Alarm();

  public IdeKeyEventDispatcher(IdeEventQueue queue) {
    myQueue = queue;
    Application parent =
        ApplicationManager
            .getApplication(); // Application is null on early start when e.g. license dialog is
    // shown
    if (parent != null) Disposer.register(parent, this);
  }

  public boolean isWaitingForSecondKeyStroke() {
    return getState() == KeyState.STATE_WAIT_FOR_SECOND_KEYSTROKE || isPressedWasProcessed();
  }

  /**
   * @return <code>true</code> if and only if the passed event is already dispatched by the <code>
   *     IdeKeyEventDispatcher</code> and there is no need for any other processing of the event.
   */
  public boolean dispatchKeyEvent(final KeyEvent e) {
    if (myDisposed) return false;

    if (e.isConsumed()) {
      return false;
    }

    // http://www.jetbrains.net/jira/browse/IDEADEV-12372
    if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
      if (e.getID() == KeyEvent.KEY_PRESSED) {
        myLeftCtrlPressed = e.getKeyLocation() == KeyEvent.KEY_LOCATION_LEFT;
      } else if (e.getID() == KeyEvent.KEY_RELEASED) {
        myLeftCtrlPressed = false;
      }
    } else if (e.getKeyCode() == KeyEvent.VK_ALT) {
      if (e.getID() == KeyEvent.KEY_PRESSED) {
        myRightAltPressed = e.getKeyLocation() == KeyEvent.KEY_LOCATION_RIGHT;
      } else if (e.getID() == KeyEvent.KEY_RELEASED) {
        myRightAltPressed = false;
      }
    }

    KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
    Component focusOwner = focusManager.getFocusOwner();

    // shortcuts should not work in shortcut setup fields
    if (focusOwner instanceof ShortcutTextField) {
      return false;
    }
    if (focusOwner instanceof JTextComponent && ((JTextComponent) focusOwner).isEditable()) {
      if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED && e.getKeyChar() != KeyEvent.VK_ESCAPE) {
        MacUIUtil.hideCursor();
      }
    }

    MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
    MenuElement[] selectedPath = menuSelectionManager.getSelectedPath();
    if (selectedPath.length > 0) {
      if (!(selectedPath[0] instanceof ComboPopup)) {
        // The following couple of lines of code is a PATCH!!!
        // It is needed to ignore ENTER KEY_TYPED events which sometimes can reach editor when an
        // action
        // is invoked from main menu via Enter key.
        setState(KeyState.STATE_PROCESSED);
        setPressedWasProcessed(true);
        return false;
      }
    }

    // Keymap shortcuts (i.e. not local shortcuts) should work only in:
    // - main frame
    // - floating focusedWindow
    // - when there's an editor in contexts
    Window focusedWindow = focusManager.getFocusedWindow();
    boolean isModalContext = focusedWindow != null && isModalContext(focusedWindow);

    final DataManager dataManager = DataManager.getInstance();
    if (dataManager == null) return false;

    DataContext dataContext = dataManager.getDataContext();

    myContext.setDataContext(dataContext);
    myContext.setFocusOwner(focusOwner);
    myContext.setModalContext(isModalContext);
    myContext.setInputEvent(e);

    try {
      if (getState() == KeyState.STATE_INIT) {
        return inInitState();
      } else if (getState() == KeyState.STATE_PROCESSED) {
        return inProcessedState();
      } else if (getState() == KeyState.STATE_WAIT_FOR_SECOND_KEYSTROKE) {
        return inWaitForSecondStrokeState();
      } else if (getState() == KeyState.STATE_SECOND_STROKE_IN_PROGRESS) {
        return inSecondStrokeInProgressState();
      } else if (getState() == KeyState.STATE_KEY_GESTURE_PROCESSOR) {
        return myKeyGestureProcessor.process();
      } else {
        throw new IllegalStateException("state = " + getState());
      }
    } finally {
      myContext.clear();
    }
  }

  /**
   * @return <code>true</code> if and only if the <code>component</code> represents modal context.
   * @throws IllegalArgumentException if <code>component</code> is <code>null</code>.
   */
  public static boolean isModalContext(@NotNull Component component) {
    Window window;
    if (component instanceof Window) {
      window = (Window) component;
    } else {
      window = SwingUtilities.getWindowAncestor(component);
    }

    if (window instanceof IdeFrameImpl) {
      final Component pane = ((IdeFrameImpl) window).getGlassPane();
      if (pane instanceof IdeGlassPaneEx) {
        return ((IdeGlassPaneEx) pane).isInModalContext();
      }
    }

    if (window instanceof JDialog) {
      final JDialog dialog = (JDialog) window;
      if (!dialog.isModal()) {
        return isModalContext(dialog.getOwner());
      }
    }

    if (window instanceof JFrame) {
      return false;
    }

    boolean isMainFrame = window instanceof IdeFrameImpl;
    boolean isFloatingDecorator = window instanceof FloatingDecorator;

    boolean isPopup = !(component instanceof JFrame) && !(component instanceof JDialog);
    if (isPopup) {
      if (component instanceof JWindow) {
        JBPopup popup =
            (JBPopup) ((JWindow) component).getRootPane().getClientProperty(AbstractPopup.KEY);
        if (popup != null) {
          return popup.isModalContext();
        }
      }
    }

    return !isMainFrame && !isFloatingDecorator;
  }

  private boolean inWaitForSecondStrokeState() {
    // a key pressed means that the user starts to enter the second stroke...
    if (KeyEvent.KEY_PRESSED == myContext.getInputEvent().getID()) {
      setState(KeyState.STATE_SECOND_STROKE_IN_PROGRESS);
      return inSecondStrokeInProgressState();
    }
    // looks like RELEASEs (from the first stroke) go here...  skip them
    return true;
  }

  /**
   * This is hack. AWT doesn't allow to create KeyStroke with specified key code and key char
   * simultaneously. Therefore we are using reflection.
   */
  private static KeyStroke getKeyStrokeWithoutMouseModifiers(KeyStroke originalKeyStroke) {
    int modifier =
        originalKeyStroke.getModifiers()
            & ~InputEvent.BUTTON1_DOWN_MASK
            & ~InputEvent.BUTTON1_MASK
            & ~InputEvent.BUTTON2_DOWN_MASK
            & ~InputEvent.BUTTON2_MASK
            & ~InputEvent.BUTTON3_DOWN_MASK
            & ~InputEvent.BUTTON3_MASK;
    try {
      Method[] methods = AWTKeyStroke.class.getDeclaredMethods();
      Method getCachedStrokeMethod = null;
      for (Method method : methods) {
        if (GET_CACHED_STROKE_METHOD_NAME.equals(method.getName())) {
          getCachedStrokeMethod = method;
          getCachedStrokeMethod.setAccessible(true);
          break;
        }
      }
      if (getCachedStrokeMethod == null) {
        throw new IllegalStateException("not found method with name getCachedStrokeMethod");
      }
      Object[] getCachedStrokeMethodArgs =
          new Object[] {
            originalKeyStroke.getKeyChar(),
            originalKeyStroke.getKeyCode(),
            modifier,
            originalKeyStroke.isOnKeyRelease()
          };
      return (KeyStroke) getCachedStrokeMethod.invoke(originalKeyStroke, getCachedStrokeMethodArgs);
    } catch (Exception exc) {
      throw new IllegalStateException(exc.getMessage());
    }
  }

  private boolean inSecondStrokeInProgressState() {
    KeyEvent e = myContext.getInputEvent();

    // when any key is released, we stop waiting for the second stroke
    if (KeyEvent.KEY_RELEASED == e.getID()) {
      myFirstKeyStroke = null;
      setState(KeyState.STATE_INIT);
      Project project = PlatformDataKeys.PROJECT.getData(myContext.getDataContext());
      StatusBar.Info.set(null, project);
      return false;
    }

    KeyStroke originalKeyStroke = KeyStroke.getKeyStrokeForEvent(e);
    KeyStroke keyStroke = getKeyStrokeWithoutMouseModifiers(originalKeyStroke);

    updateCurrentContext(
        myContext.getFoundComponent(),
        new KeyboardShortcut(myFirstKeyStroke, keyStroke),
        myContext.isModalContext());

    // consume the wrong second stroke and keep on waiting
    if (myContext.getActions().isEmpty()) {
      return true;
    }

    // finally user had managed to enter the second keystroke, so let it be processed
    Project project = PlatformDataKeys.PROJECT.getData(myContext.getDataContext());
    StatusBarEx statusBar = (StatusBarEx) WindowManager.getInstance().getStatusBar(project);
    if (processAction(e, myActionProcessor)) {
      if (statusBar != null) {
        statusBar.setInfo(null);
      }
      return true;
    } else {
      return false;
    }
  }

  private boolean inProcessedState() {
    KeyEvent e = myContext.getInputEvent();

    // ignore typed events which come after processed pressed event
    if (KeyEvent.KEY_TYPED == e.getID() && isPressedWasProcessed()) {
      return true;
    }
    if (KeyEvent.KEY_RELEASED == e.getID()
        && KeyEvent.VK_ALT == e.getKeyCode()
        && isPressedWasProcessed()) {
      // see IDEADEV-8615
      return true;
    }
    setState(KeyState.STATE_INIT);
    setPressedWasProcessed(false);
    return inInitState();
  }

  private static final Set<String> ALT_GR_LAYOUTS =
      new HashSet<String>(
          Arrays.asList(
              "pl", "de", "fi", "fr", "no", "da", "se", "pt", "nl", "tr", "sl", "hu", "bs", "hr",
              "sr", "sk", "lv"));

  private boolean inInitState() {
    Component focusOwner = myContext.getFocusOwner();
    boolean isModalContext = myContext.isModalContext();
    DataContext dataContext = myContext.getDataContext();
    KeyEvent e = myContext.getInputEvent();

    // http://www.jetbrains.net/jira/browse/IDEADEV-12372
    if (myLeftCtrlPressed
        && myRightAltPressed
        && focusOwner != null
        && e.getModifiers() == (InputEvent.CTRL_MASK | InputEvent.ALT_MASK)) {
      if (Registry.is("actionSystem.force.alt.gr")) {
        return false;
      }
      final InputContext inputContext = focusOwner.getInputContext();
      if (inputContext != null) {
        Locale locale = inputContext.getLocale();
        if (locale != null) {
          @NonNls final String language = locale.getLanguage();
          if (ALT_GR_LAYOUTS.contains(language)) {
            // don't search for shortcuts
            return false;
          }
        }
      }
    }

    KeyStroke originalKeyStroke = KeyStroke.getKeyStrokeForEvent(e);
    KeyStroke keyStroke = getKeyStrokeWithoutMouseModifiers(originalKeyStroke);

    if (myKeyGestureProcessor.processInitState()) {
      return true;
    }

    if (SystemInfo.isMac) {
      boolean keyTyped = e.getID() == KeyEvent.KEY_TYPED;
      boolean hasMnemonicsInWindow =
          e.getID() == KeyEvent.KEY_PRESSED && hasMnemonicInWindow(focusOwner, e.getKeyCode())
              || keyTyped && hasMnemonicInWindow(focusOwner, e.getKeyChar());
      boolean imEnabled = IdeEventQueue.getInstance().isInputMethodEnabled();

      if (e.getModifiersEx() == InputEvent.ALT_DOWN_MASK
          && (hasMnemonicsInWindow || (!imEnabled && keyTyped))) {
        setPressedWasProcessed(true);
        setState(KeyState.STATE_PROCESSED);
        return false;
      }
    }

    updateCurrentContext(focusOwner, new KeyboardShortcut(keyStroke, null), isModalContext);
    if (myContext.getActions().isEmpty()) {
      // there's nothing mapped for this stroke
      return false;
    }

    if (myContext.isHasSecondStroke()) {
      myFirstKeyStroke = keyStroke;
      final ArrayList<Pair<AnAction, KeyStroke>> secondKeyStrokes = getSecondKeystrokeActions();

      final Project project = PlatformDataKeys.PROJECT.getData(dataContext);
      StringBuilder message = new StringBuilder();
      message.append(KeyMapBundle.message("prefix.key.pressed.message"));
      message.append(' ');
      for (int i = 0; i < secondKeyStrokes.size(); i++) {
        Pair<AnAction, KeyStroke> pair = secondKeyStrokes.get(i);
        if (i > 0) message.append(", ");
        message.append(pair.getFirst().getTemplatePresentation().getText());
        message.append(" (");
        message.append(KeymapUtil.getKeystrokeText(pair.getSecond()));
        message.append(")");
      }

      StatusBar.Info.set(message.toString(), project);

      mySecondStrokeTimeout.cancelAllRequests();
      mySecondStrokeTimeout.addRequest(
          mySecondStrokeTimeoutRunnable, Registry.intValue("actionSystem.secondKeystrokeTimeout"));

      if (Registry.is("actionSystem.secondKeystrokeAutoPopupEnabled")) {
        mySecondKeystrokePopupTimeout.cancelAllRequests();
        if (secondKeyStrokes.size() > 1) {
          final DataContext oldContext = myContext.getDataContext();
          mySecondKeystrokePopupTimeout.addRequest(
              new Runnable() {
                @Override
                public void run() {
                  if (myState == KeyState.STATE_WAIT_FOR_SECOND_KEYSTROKE) {
                    StatusBar.Info.set(null, PlatformDataKeys.PROJECT.getData(oldContext));
                    new SecondaryKeystrokePopup(myFirstKeyStroke, secondKeyStrokes, oldContext)
                        .showInBestPositionFor(oldContext);
                  }
                }
              },
              Registry.intValue("actionSystem.secondKeystrokePopupTimeout"));
        }
      }

      setState(KeyState.STATE_WAIT_FOR_SECOND_KEYSTROKE);
      return true;
    } else {
      return processAction(e, myActionProcessor);
    }
  }

  private ArrayList<Pair<AnAction, KeyStroke>> getSecondKeystrokeActions() {
    ArrayList<Pair<AnAction, KeyStroke>> secondKeyStrokes =
        new ArrayList<Pair<AnAction, KeyStroke>>();
    for (AnAction action : myContext.getActions()) {
      Shortcut[] shortcuts = action.getShortcutSet().getShortcuts();
      for (Shortcut shortcut : shortcuts) {
        if (shortcut instanceof KeyboardShortcut) {
          KeyboardShortcut keyShortcut = (KeyboardShortcut) shortcut;
          if (keyShortcut.getFirstKeyStroke().equals(myFirstKeyStroke)) {
            secondKeyStrokes.add(
                new Pair<AnAction, KeyStroke>(action, keyShortcut.getSecondKeyStroke()));
          }
        }
      }
    }
    return secondKeyStrokes;
  }

  private static boolean hasMnemonicInWindow(Component focusOwner, int keyCode) {
    if (keyCode == KeyEvent.VK_ALT || keyCode == 0) return false; // Optimization
    final Container container = getContainer(focusOwner);
    return hasMnemonic(container, keyCode) || hasMnemonicInBalloons(container, keyCode);
  }

  @Nullable
  private static Container getContainer(@Nullable final Component focusOwner) {
    if (focusOwner == null) return null;
    if (focusOwner.isLightweight()) {
      Container container = focusOwner.getParent();
      while (container != null) {
        final Container parent = container.getParent();
        if (parent instanceof JLayeredPane) break;
        if (parent != null && parent.isLightweight()) {
          container = parent;
        } else {
          break;
        }
      }
      return container;
    }

    return SwingUtilities.windowForComponent(focusOwner);
  }

  private static boolean hasMnemonic(final Container container, final int keyCode) {
    if (container == null) return false;

    final Component[] components = container.getComponents();
    for (Component component : components) {
      if (component instanceof AbstractButton) {
        final AbstractButton button = (AbstractButton) component;
        if (button instanceof JBOptionButton) {
          if (((JBOptionButton) button).isOkToProcessDefaultMnemonics()) return true;
        } else {
          if (button.getMnemonic() == keyCode) return true;
        }
      }
      if (component instanceof JLabel) {
        final JLabel label = (JLabel) component;
        if (label.getDisplayedMnemonic() == keyCode) return true;
      }
      if (component instanceof Container) {
        if (hasMnemonic((Container) component, keyCode)) return true;
      }
    }
    return false;
  }

  private static boolean hasMnemonicInBalloons(Container container, int code) {
    final Component parent = UIUtil.findUltimateParent(container);
    if (parent instanceof RootPaneContainer) {
      final JLayeredPane pane = ((RootPaneContainer) parent).getLayeredPane();
      for (Component component : pane.getComponents()) {
        if (component instanceof ComponentWithMnemonics
            && component instanceof Container
            && hasMnemonic((Container) component, code)) {
          return true;
        }
      }
    }
    return false;
  }

  private final ActionProcessor myActionProcessor =
      new ActionProcessor() {
        public AnActionEvent createEvent(
            final InputEvent inputEvent,
            final DataContext context,
            final String place,
            final Presentation presentation,
            final ActionManager manager) {
          return new AnActionEvent(inputEvent, context, place, presentation, manager, 0);
        }

        public void onUpdatePassed(
            final InputEvent inputEvent, final AnAction action, final AnActionEvent actionEvent) {
          setState(KeyState.STATE_PROCESSED);
          setPressedWasProcessed(inputEvent.getID() == KeyEvent.KEY_PRESSED);
        }

        public void performAction(
            final InputEvent e, final AnAction action, final AnActionEvent actionEvent) {
          e.consume();
          action.actionPerformed(actionEvent);
          if (Registry.is("actionSystem.fixLostTyping")) {
            IdeEventQueue.getInstance()
                .doWhenReady(
                    new Runnable() {
                      @Override
                      public void run() {
                        IdeEventQueue.getInstance().getKeyEventDispatcher().resetState();
                      }
                    });
          }
        }
      };

  public boolean processAction(final InputEvent e, ActionProcessor processor) {
    ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
    final Project project = PlatformDataKeys.PROJECT.getData(myContext.getDataContext());
    final boolean dumb = project != null && DumbService.getInstance(project).isDumb();
    List<AnActionEvent> nonDumbAwareAction = new ArrayList<AnActionEvent>();
    for (final AnAction action : myContext.getActions()) {
      final Presentation presentation = myPresentationFactory.getPresentation(action);

      // Mouse modifiers are 0 because they have no any sense when action is invoked via keyboard
      final AnActionEvent actionEvent =
          processor.createEvent(
              e,
              myContext.getDataContext(),
              ActionPlaces.MAIN_MENU,
              presentation,
              ActionManager.getInstance());

      ActionUtil.performDumbAwareUpdate(action, actionEvent, true);

      if (dumb && !action.isDumbAware()) {
        if (Boolean.FALSE.equals(
            presentation.getClientProperty(ActionUtil.WOULD_BE_ENABLED_IF_NOT_DUMB_MODE))) {
          continue;
        }

        nonDumbAwareAction.add(actionEvent);
        continue;
      }

      if (!presentation.isEnabled()) {
        continue;
      }

      processor.onUpdatePassed(e, action, actionEvent);

      ((DataManagerImpl.MyDataContext) myContext.getDataContext())
          .setEventCount(IdeEventQueue.getInstance().getEventCount(), this);
      actionManager.fireBeforeActionPerformed(action, actionEvent.getDataContext(), actionEvent);
      Component component =
          PlatformDataKeys.CONTEXT_COMPONENT.getData(actionEvent.getDataContext());
      if (component != null && !component.isShowing()) {
        return true;
      }

      processor.performAction(e, action, actionEvent);
      actionManager.fireAfterActionPerformed(action, actionEvent.getDataContext(), actionEvent);
      return true;
    }

    if (!nonDumbAwareAction.isEmpty()) {
      showDumbModeWarningLaterIfNobodyConsumesEvent(
          e, nonDumbAwareAction.toArray(new AnActionEvent[nonDumbAwareAction.size()]));
    }

    return false;
  }

  private static void showDumbModeWarningLaterIfNobodyConsumesEvent(
      final InputEvent e, final AnActionEvent... actionEvents) {
    if (ModalityState.current() == ModalityState.NON_MODAL) {
      ApplicationManager.getApplication()
          .invokeLater(
              new Runnable() {
                public void run() {
                  if (e.isConsumed()) return;

                  ActionUtil.showDumbModeWarning(actionEvents);
                }
              });
    }
  }

  /**
   * This method fills <code>myActions</code> list.
   *
   * @return true if there is a shortcut with second stroke found.
   */
  public KeyProcessorContext updateCurrentContext(
      Component component, Shortcut sc, boolean isModalContext) {
    myContext.setFoundComponent(null);
    myContext.getActions().clear();

    if (isControlEnterOnDialog(component, sc)) return myContext;

    boolean hasSecondStroke = false;

    // here we try to find "local" shortcuts

    for (; component != null; component = component.getParent()) {
      if (!(component instanceof JComponent)) {
        continue;
      }
      ArrayList listOfActions =
          (ArrayList) ((JComponent) component).getClientProperty(AnAction.ourClientProperty);
      if (listOfActions == null) {
        continue;
      }
      for (Object listOfAction : listOfActions) {
        if (!(listOfAction instanceof AnAction)) {
          continue;
        }
        AnAction action = (AnAction) listOfAction;
        hasSecondStroke |= addAction(action, sc);
      }
      // once we've found a proper local shortcut(s), we continue with non-local shortcuts
      if (!myContext.getActions().isEmpty()) {
        myContext.setFoundComponent((JComponent) component);
        break;
      }
    }

    // search in main keymap

    Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
    String[] actionIds = keymap.getActionIds(sc);

    ActionManager actionManager = ActionManager.getInstance();
    for (String actionId : actionIds) {
      AnAction action = actionManager.getAction(actionId);
      if (action != null) {
        if (isModalContext && !action.isEnabledInModalContext()) {
          continue;
        }
        hasSecondStroke |= addAction(action, sc);
      }
    }

    if (!hasSecondStroke && sc instanceof KeyboardShortcut) {
      // little trick to invoke action which second stroke is a key w/o modifiers, but user still
      // holds the modifier key(s) of the first stroke

      final KeyboardShortcut keyboardShortcut = (KeyboardShortcut) sc;
      final KeyStroke firstKeyStroke = keyboardShortcut.getFirstKeyStroke();
      final KeyStroke secondKeyStroke = keyboardShortcut.getSecondKeyStroke();

      if (secondKeyStroke != null
          && secondKeyStroke.getModifiers() != 0
          && firstKeyStroke.getModifiers() != 0) {
        final KeyboardShortcut altShortCut =
            new KeyboardShortcut(
                firstKeyStroke, KeyStroke.getKeyStroke(secondKeyStroke.getKeyCode(), 0));
        final String[] additionalActions = keymap.getActionIds(altShortCut);

        for (final String actionId : additionalActions) {
          AnAction action = actionManager.getAction(actionId);
          if (action != null) {
            if (isModalContext && !action.isEnabledInModalContext()) {
              continue;
            }
            hasSecondStroke |= addAction(action, altShortCut);
          }
        }
      }
    }

    myContext.setHasSecondStroke(hasSecondStroke);

    Comparator<? super AnAction> comparator =
        PlatformDataKeys.ACTIONS_SORTER.getData(myContext.getDataContext());
    if (comparator != null) {
      Collections.sort(myContext.getActions(), comparator);
    }

    return myContext;
  }

  private static KeyboardShortcut CONTROL_ENTER = KeyboardShortcut.fromString("control ENTER");

  private static boolean isControlEnterOnDialog(Component component, Shortcut sc) {
    return CONTROL_ENTER.equals(sc)
        && !IdeEventQueue.getInstance().isPopupActive() // avoid Control+Enter in completion
        && DialogWrapper.findInstance(component) != null;
  }

  /** @return true if action is added and has second stroke */
  private boolean addAction(AnAction action, Shortcut sc) {
    boolean hasSecondStroke = false;

    Shortcut[] shortcuts = action.getShortcutSet().getShortcuts();
    for (Shortcut each : shortcuts) {
      if (!each.isKeyboard()) continue;

      if (each.startsWith(sc)) {
        if (!myContext.getActions().contains(action)) {
          myContext.getActions().add(action);
        }

        if (each instanceof KeyboardShortcut) {
          hasSecondStroke |= ((KeyboardShortcut) each).getSecondKeyStroke() != null;
        }
      }
    }

    return hasSecondStroke;
  }

  public KeyProcessorContext getContext() {
    return myContext;
  }

  public void dispose() {
    myDisposed = true;
  }

  public KeyState getState() {
    return myState;
  }

  public void setState(final KeyState state) {
    myState = state;
    if (myQueue != null) {
      myQueue.maybeReady();
    }
  }

  public void resetState() {
    setState(KeyState.STATE_INIT);
    setPressedWasProcessed(false);
  }

  public boolean isPressedWasProcessed() {
    return myPressedWasProcessed;
  }

  public void setPressedWasProcessed(boolean pressedWasProcessed) {
    myPressedWasProcessed = pressedWasProcessed;
  }

  public boolean isReady() {
    return myState == KeyState.STATE_INIT || myState == KeyState.STATE_PROCESSED;
  }

  private static class SecondaryKeystrokePopup extends ListPopupImpl {
    private SecondaryKeystrokePopup(
        @NotNull final KeyStroke firstKeystroke,
        @NotNull final List<Pair<AnAction, KeyStroke>> actions,
        final DataContext context) {
      super(buildStep(actions, context));
      registerActions(firstKeystroke, actions, context);
    }

    private void registerActions(
        @NotNull final KeyStroke firstKeyStroke,
        @NotNull final List<Pair<AnAction, KeyStroke>> actions,
        final DataContext ctx) {
      ContainerUtil.process(
          actions,
          new Processor<Pair<AnAction, KeyStroke>>() {
            @Override
            public boolean process(final Pair<AnAction, KeyStroke> pair) {
              final String actionText = pair.getFirst().getTemplatePresentation().getText();
              final AbstractAction a =
                  new AbstractAction() {
                    @Override
                    public void actionPerformed(final ActionEvent e) {
                      cancel();
                      invokeAction(pair.getFirst(), ctx);
                    }
                  };

              final KeyStroke keyStroke = pair.getSecond();
              registerAction(actionText, keyStroke, a);

              if (keyStroke.getModifiers() == 0) {
                // do a little trick here, so if I will press Command+R and the second keystroke is
                // just 'R',
                // I want to be able to hold the Command while pressing 'R'

                final KeyStroke additionalKeyStroke =
                    KeyStroke.getKeyStroke(keyStroke.getKeyCode(), firstKeyStroke.getModifiers());
                final String _existing = getActionForKeyStroke(additionalKeyStroke);
                if (_existing == null)
                  registerAction("__additional__" + actionText, additionalKeyStroke, a);
              }

              return true;
            }
          });
    }

    private static void invokeAction(@NotNull final AnAction action, final DataContext ctx) {
      ApplicationManager.getApplication()
          .invokeLater(
              new Runnable() {
                @Override
                public void run() {
                  final AnActionEvent event =
                      new AnActionEvent(
                          null,
                          ctx,
                          ActionPlaces.UNKNOWN,
                          (Presentation) action.getTemplatePresentation().clone(),
                          ActionManager.getInstance(),
                          0);
                  if (ActionUtil.lastUpdateAndCheckDumb(action, event, true)) {
                    action.actionPerformed(event);
                  }
                }
              });
    }

    @Override
    protected ListCellRenderer getListElementRenderer() {
      return new ActionListCellRenderer();
    }

    private static ListPopupStep buildStep(
        @NotNull final List<Pair<AnAction, KeyStroke>> actions, final DataContext ctx) {
      return new BaseListPopupStep<Pair<AnAction, KeyStroke>>(
          "Choose an action",
          ContainerUtil.findAll(
              actions,
              new Condition<Pair<AnAction, KeyStroke>>() {
                @Override
                public boolean value(Pair<AnAction, KeyStroke> pair) {
                  final AnAction action = pair.getFirst();
                  final Presentation presentation =
                      (Presentation) action.getTemplatePresentation().clone();
                  AnActionEvent event =
                      new AnActionEvent(
                          null,
                          ctx,
                          ActionPlaces.UNKNOWN,
                          presentation,
                          ActionManager.getInstance(),
                          0);

                  ActionUtil.performDumbAwareUpdate(action, event, true);
                  return presentation.isEnabled() && presentation.isVisible();
                }
              })) {
        @Override
        public PopupStep onChosen(Pair<AnAction, KeyStroke> selectedValue, boolean finalChoice) {
          invokeAction(selectedValue.getFirst(), ctx);
          return FINAL_CHOICE;
        }
      };
    }

    private static class ActionListCellRenderer extends ColoredListCellRenderer {
      @Override
      protected void customizeCellRenderer(
          final JList list,
          final Object value,
          final int index,
          final boolean selected,
          final boolean hasFocus) {
        if (value == null) return;
        if (value instanceof Pair) {
          final Pair<AnAction, KeyStroke> pair = (Pair<AnAction, KeyStroke>) value;
          append(
              KeymapUtil.getShortcutText(new KeyboardShortcut(pair.getSecond(), null)),
              SimpleTextAttributes.GRAY_ATTRIBUTES);
          appendAlign(30);
          final String text = pair.getFirst().getTemplatePresentation().getText();
          append(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
        }
      }
    }
  }
}
 private static boolean isControlEnterOnDialog(Component component, Shortcut sc) {
   return CONTROL_ENTER.equals(sc)
       && !IdeEventQueue.getInstance().isPopupActive() // avoid Control+Enter in completion
       && DialogWrapper.findInstance(component) != null;
 }
  /**
   * This method fills <code>myActions</code> list.
   *
   * @return true if there is a shortcut with second stroke found.
   */
  public KeyProcessorContext updateCurrentContext(
      Component component, Shortcut sc, boolean isModalContext) {
    myContext.setFoundComponent(null);
    myContext.getActions().clear();

    if (isControlEnterOnDialog(component, sc)) return myContext;

    boolean hasSecondStroke = false;

    // here we try to find "local" shortcuts

    for (; component != null; component = component.getParent()) {
      if (!(component instanceof JComponent)) {
        continue;
      }
      ArrayList listOfActions =
          (ArrayList) ((JComponent) component).getClientProperty(AnAction.ourClientProperty);
      if (listOfActions == null) {
        continue;
      }
      for (Object listOfAction : listOfActions) {
        if (!(listOfAction instanceof AnAction)) {
          continue;
        }
        AnAction action = (AnAction) listOfAction;
        hasSecondStroke |= addAction(action, sc);
      }
      // once we've found a proper local shortcut(s), we continue with non-local shortcuts
      if (!myContext.getActions().isEmpty()) {
        myContext.setFoundComponent((JComponent) component);
        break;
      }
    }

    // search in main keymap

    Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
    String[] actionIds = keymap.getActionIds(sc);

    ActionManager actionManager = ActionManager.getInstance();
    for (String actionId : actionIds) {
      AnAction action = actionManager.getAction(actionId);
      if (action != null) {
        if (isModalContext && !action.isEnabledInModalContext()) {
          continue;
        }
        hasSecondStroke |= addAction(action, sc);
      }
    }

    if (!hasSecondStroke && sc instanceof KeyboardShortcut) {
      // little trick to invoke action which second stroke is a key w/o modifiers, but user still
      // holds the modifier key(s) of the first stroke

      final KeyboardShortcut keyboardShortcut = (KeyboardShortcut) sc;
      final KeyStroke firstKeyStroke = keyboardShortcut.getFirstKeyStroke();
      final KeyStroke secondKeyStroke = keyboardShortcut.getSecondKeyStroke();

      if (secondKeyStroke != null
          && secondKeyStroke.getModifiers() != 0
          && firstKeyStroke.getModifiers() != 0) {
        final KeyboardShortcut altShortCut =
            new KeyboardShortcut(
                firstKeyStroke, KeyStroke.getKeyStroke(secondKeyStroke.getKeyCode(), 0));
        final String[] additionalActions = keymap.getActionIds(altShortCut);

        for (final String actionId : additionalActions) {
          AnAction action = actionManager.getAction(actionId);
          if (action != null) {
            if (isModalContext && !action.isEnabledInModalContext()) {
              continue;
            }
            hasSecondStroke |= addAction(action, altShortCut);
          }
        }
      }
    }

    myContext.setHasSecondStroke(hasSecondStroke);

    Comparator<? super AnAction> comparator =
        PlatformDataKeys.ACTIONS_SORTER.getData(myContext.getDataContext());
    if (comparator != null) {
      Collections.sort(myContext.getActions(), comparator);
    }

    return myContext;
  }