| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ide.navigationToolbar; |
| |
| import com.intellij.ProjectTopics; |
| import com.intellij.ide.actions.CopyAction; |
| import com.intellij.ide.actions.CutAction; |
| import com.intellij.ide.ui.LafManager; |
| import com.intellij.ide.ui.LafManagerListener; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.actionSystem.ex.AnActionListener; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.FileEditorManagerEvent; |
| import com.intellij.openapi.fileEditor.FileEditorManagerListener; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ModuleRootEvent; |
| import com.intellij.openapi.roots.ModuleRootListener; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.util.ActionCallback; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.vcs.FileStatusListener; |
| import com.intellij.openapi.vcs.FileStatusManager; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.openapi.wm.ToolWindowManager; |
| import com.intellij.problems.WolfTheProblemSolver; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.psi.PsiTreeChangeEvent; |
| import com.intellij.psi.PsiTreeChangeListener; |
| import com.intellij.ui.ListScrollingUtil; |
| import com.intellij.util.messages.MessageBusConnection; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.List; |
| |
| /** |
| * @author Konstantin Bulenkov |
| */ |
| public class NavBarListener extends WolfTheProblemSolver.ProblemListener |
| implements ActionListener, FocusListener, FileStatusListener, AnActionListener, FileEditorManagerListener, |
| PsiTreeChangeListener, ModuleRootListener, NavBarModelListener, PropertyChangeListener, KeyListener, WindowFocusListener, |
| LafManagerListener { |
| private static final String LISTENER = "NavBarListener"; |
| private static final String BUS = "NavBarMessageBus"; |
| private final NavBarPanel myPanel; |
| private boolean shouldFocusEditor = false; |
| |
| static void subscribeTo(NavBarPanel panel) { |
| if (panel.getClientProperty(LISTENER) != null) { |
| unsubscribeFrom(panel); |
| } |
| |
| final NavBarListener listener = new NavBarListener(panel); |
| final Project project = panel.getProject(); |
| panel.putClientProperty(LISTENER, listener); |
| KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(listener); |
| FileStatusManager.getInstance(project).addFileStatusListener(listener); |
| PsiManager.getInstance(project).addPsiTreeChangeListener(listener); |
| WolfTheProblemSolver.getInstance(project).addProblemListener(listener); |
| ActionManager.getInstance().addAnActionListener(listener); |
| |
| final MessageBusConnection connection = project.getMessageBus().connect(); |
| connection.subscribe(ProjectTopics.PROJECT_ROOTS, listener); |
| connection.subscribe(NavBarModelListener.NAV_BAR, listener); |
| connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener); |
| panel.putClientProperty(BUS, connection); |
| panel.addKeyListener(listener); |
| |
| if (panel.isInFloatingMode()) { |
| final Window window = SwingUtilities.windowForComponent(panel); |
| if (window != null) { |
| window.addWindowFocusListener(listener); |
| } |
| } else { |
| LafManager.getInstance().addLafManagerListener(listener); |
| } |
| } |
| |
| static void unsubscribeFrom(NavBarPanel panel) { |
| final NavBarListener listener = (NavBarListener)panel.getClientProperty(LISTENER); |
| panel.putClientProperty(LISTENER, null); |
| if (listener != null) { |
| final Project project = panel.getProject(); |
| KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(listener); |
| FileStatusManager.getInstance(project).removeFileStatusListener(listener); |
| PsiManager.getInstance(project).removePsiTreeChangeListener(listener); |
| WolfTheProblemSolver.getInstance(project).removeProblemListener(listener); |
| ActionManager.getInstance().removeAnActionListener(listener); |
| final MessageBusConnection connection = (MessageBusConnection)panel.getClientProperty(BUS); |
| panel.putClientProperty(BUS, null); |
| if (connection != null) { |
| connection.disconnect(); |
| } |
| LafManager.getInstance().removeLafManagerListener(listener); |
| } |
| } |
| |
| NavBarListener(NavBarPanel panel) { |
| myPanel = panel; |
| for (NavBarKeyboardCommand command : NavBarKeyboardCommand.values()) { |
| registerKey(command); |
| } |
| myPanel.addFocusListener(this); |
| } |
| |
| private void registerKey(NavBarKeyboardCommand cmd) { |
| myPanel.registerKeyboardAction(this, cmd.name(), cmd.getKeyStroke(), JComponent.WHEN_FOCUSED); |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| final NavBarKeyboardCommand cmd = NavBarKeyboardCommand.fromString(e.getActionCommand()); |
| if (cmd != null) { |
| switch (cmd) { |
| case LEFT: myPanel.moveLeft(); break; |
| case RIGHT: myPanel.moveRight(); break; |
| case HOME: myPanel.moveHome(); break; |
| case END: myPanel.moveEnd(); break; |
| case DOWN: myPanel.moveDown(); break; |
| case UP: myPanel.moveDown(); break; |
| case ENTER: myPanel.enter(); break; |
| case ESCAPE: myPanel.escape(); break; |
| case NAVIGATE: myPanel.navigate(); break; |
| } |
| } |
| } |
| |
| @Override |
| public void focusGained(final FocusEvent e) { |
| if (e.getOppositeComponent() == null && shouldFocusEditor) { |
| shouldFocusEditor = false; |
| ToolWindowManager.getInstance(myPanel.getProject()).activateEditorComponent(); |
| return; |
| } |
| myPanel.updateItems(); |
| final List<NavBarItem> items = myPanel.getItems(); |
| if (!myPanel.isInFloatingMode() && items.size() > 0) { |
| myPanel.setContextComponent(items.get(items.size() - 1)); |
| } else { |
| myPanel.setContextComponent(null); |
| } |
| } |
| |
| @Override |
| public void focusLost(final FocusEvent e) { |
| if (myPanel.getProject().isDisposed()) { |
| myPanel.setContextComponent(null); |
| myPanel.hideHint(); |
| return; |
| } |
| final DialogWrapper dialog = DialogWrapper.findInstance(e.getOppositeComponent()); |
| shouldFocusEditor = dialog != null; |
| if (dialog != null) { |
| Disposer.register(dialog.getDisposable(), new Disposable() { |
| @Override |
| public void dispose() { |
| if (dialog.getExitCode() == DialogWrapper.CANCEL_EXIT_CODE) { |
| shouldFocusEditor = false; |
| } |
| } |
| }); |
| } |
| |
| // required invokeLater since in current call sequence KeyboardFocusManager is not initialized yet |
| // but future focused component |
| //noinspection SSBasedInspection |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| processFocusLost(e); |
| } |
| }); |
| } |
| |
| private void processFocusLost(FocusEvent e) { |
| final Component opposite = e.getOppositeComponent(); |
| |
| if (myPanel.isInFloatingMode() && opposite != null && DialogWrapper.findInstance(opposite) != null) { |
| myPanel.hideHint(); |
| return; |
| } |
| |
| final boolean nodePopupInactive = !myPanel.isNodePopupActive(); |
| boolean childPopupInactive = !JBPopupFactory.getInstance().isChildPopupFocused(myPanel); |
| if (nodePopupInactive && childPopupInactive) { |
| if (opposite != null && opposite != myPanel && !myPanel.isAncestorOf(opposite) && !e.isTemporary()) { |
| myPanel.setContextComponent(null); |
| myPanel.hideHint(); |
| } |
| } |
| |
| myPanel.updateItems(); |
| } |
| |
| private void rebuildUI() { |
| if (myPanel.isShowing()) { |
| myPanel.getUpdateQueue().queueRebuildUi(); |
| } |
| } |
| |
| private void updateModel() { |
| if (myPanel.isShowing()) { |
| myPanel.getModel().setChanged(true); |
| myPanel.getUpdateQueue().queueModelUpdateFromFocus(); |
| } |
| } |
| |
| @Override |
| public void fileStatusesChanged() { |
| rebuildUI(); |
| } |
| |
| @Override |
| public void fileStatusChanged(@NotNull VirtualFile virtualFile) { |
| rebuildUI(); |
| } |
| |
| @Override |
| public void childAdded(@NotNull PsiTreeChangeEvent event) { |
| updateModel(); |
| } |
| |
| @Override |
| public void childReplaced(@NotNull PsiTreeChangeEvent event) { |
| updateModel(); |
| } |
| |
| @Override |
| public void childMoved(@NotNull PsiTreeChangeEvent event) { |
| updateModel(); |
| } |
| |
| @Override |
| public void childrenChanged(@NotNull PsiTreeChangeEvent event) { |
| updateModel(); |
| } |
| |
| @Override |
| public void propertyChanged(@NotNull final PsiTreeChangeEvent event) { |
| updateModel(); |
| } |
| |
| @Override |
| public void rootsChanged(ModuleRootEvent event) { |
| updateModel(); |
| } |
| |
| @Override |
| public void problemsAppeared(@NotNull VirtualFile file) { |
| updateModel(); |
| } |
| |
| @Override |
| public void problemsDisappeared(@NotNull VirtualFile file) { |
| updateModel(); |
| } |
| |
| @Override |
| public void modelChanged() { |
| rebuildUI(); |
| } |
| |
| @Override |
| public void selectionChanged() { |
| myPanel.updateItems(); |
| myPanel.scrollSelectionToVisible(); |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (myPanel.isShowing()) { |
| final String name = evt.getPropertyName(); |
| if ("focusOwner".equals(name) || "permanentFocusOwner".equals(name)) { |
| myPanel.getUpdateQueue().restartRebuild(); |
| } |
| } |
| } |
| @Override |
| public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { |
| if (shouldSkipAction(action)) return; |
| |
| if (myPanel.isInFloatingMode()) { |
| myPanel.hideHint(); |
| } else { |
| myPanel.cancelPopup(); |
| } |
| } |
| |
| private static boolean shouldSkipAction(AnAction action) { |
| return action instanceof PopupAction |
| || action instanceof CopyAction |
| || action instanceof CutAction |
| || action instanceof ListScrollingUtil.ListScrollAction; |
| } |
| |
| @Override |
| public void keyPressed(final KeyEvent e) { |
| if (!(e.isAltDown() || e.isMetaDown() || e.isControlDown() || myPanel.isNodePopupActive())) { |
| if (!Character.isLetter(e.getKeyChar())) { |
| return; |
| } |
| |
| final IdeFocusManager focusManager = IdeFocusManager.getInstance(myPanel.getProject()); |
| final ActionCallback firstCharTyped = new ActionCallback(); |
| focusManager.typeAheadUntil(firstCharTyped); |
| myPanel.moveDown(); |
| //noinspection SSBasedInspection |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| final Robot robot = new Robot(); |
| final boolean shiftOn = e.isShiftDown(); |
| final int code = e.getKeyCode(); |
| if (shiftOn) { |
| robot.keyPress(KeyEvent.VK_SHIFT); |
| } |
| robot.keyPress(code); |
| robot.keyRelease(code); |
| |
| //don't release Shift |
| firstCharTyped.setDone(); |
| } |
| catch (AWTException ignored) { |
| } |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void fileOpened(@NotNull final FileEditorManager manager, @NotNull final VirtualFile file) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (myPanel.hasFocus()) { |
| manager.openFile(file, true); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void lookAndFeelChanged(LafManager source) { |
| myPanel.getNavBarUI().clearItems(); |
| myPanel.revalidate(); |
| myPanel.repaint(); |
| } |
| |
| |
| //---- Ignored |
| @Override |
| public void windowLostFocus(WindowEvent e) {} |
| |
| @Override |
| public void windowGainedFocus(WindowEvent e) {} |
| |
| @Override |
| public void keyTyped(KeyEvent e) {} |
| |
| @Override |
| public void keyReleased(KeyEvent e) {} |
| |
| @Override |
| public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {} |
| |
| @Override |
| public void beforeEditorTyping(char c, DataContext dataContext) {} |
| |
| @Override |
| public void beforeRootsChange(ModuleRootEvent event) {} |
| |
| @Override |
| public void beforeChildAddition(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void beforeChildRemoval(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void beforeChildReplacement(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void beforeChildMovement(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void beforeChildrenChange(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void beforePropertyChange(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void childRemoved(@NotNull PsiTreeChangeEvent event) {} |
| |
| @Override |
| public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {} |
| |
| @Override |
| public void selectionChanged(@NotNull FileEditorManagerEvent event) {} |
| } |