blob: e0a0d946c9c5bc5c0a55f09e4d3fcf7e61f71f54 [file] [log] [blame]
/*
* 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) {}
}