NavBar didn't appear on the second monitor
[fedora-idea.git] / platform / lang-impl / src / com / intellij / ide / navigationToolbar / NavBarPanel.java
blobfc8898d08bd3cf043f0bca59766c9541833aad95
1 /*
2 * Copyright 2000-2005 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide.navigationToolbar;
18 import com.intellij.ProjectTopics;
19 import com.intellij.codeInsight.hint.HintManagerImpl;
20 import com.intellij.ide.CopyPasteDelegator;
21 import com.intellij.ide.DataManager;
22 import com.intellij.ide.IdeView;
23 import com.intellij.ide.projectView.ProjectView;
24 import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
25 import com.intellij.ide.projectView.impl.ProjectRootsUtil;
26 import com.intellij.ide.ui.customization.CustomActionsSchema;
27 import com.intellij.ide.util.DeleteHandler;
28 import com.intellij.ide.util.DirectoryChooserUtil;
29 import com.intellij.openapi.actionSystem.*;
30 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.application.ModalityState;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.editor.Editor;
35 import com.intellij.openapi.editor.markup.EffectType;
36 import com.intellij.openapi.editor.markup.TextAttributes;
37 import com.intellij.openapi.module.Module;
38 import com.intellij.openapi.module.ModuleManager;
39 import com.intellij.openapi.module.ModuleUtil;
40 import com.intellij.openapi.project.IndexNotReadyException;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.roots.*;
43 import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider;
44 import com.intellij.openapi.ui.popup.JBPopup;
45 import com.intellij.openapi.ui.popup.JBPopupFactory;
46 import com.intellij.openapi.ui.popup.PopupStep;
47 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
48 import com.intellij.openapi.util.*;
49 import com.intellij.openapi.vcs.FileStatus;
50 import com.intellij.openapi.vcs.FileStatusListener;
51 import com.intellij.openapi.vcs.FileStatusManager;
52 import com.intellij.openapi.vfs.VfsUtil;
53 import com.intellij.openapi.vfs.VirtualFile;
54 import com.intellij.openapi.wm.IdeFocusManager;
55 import com.intellij.openapi.wm.ToolWindowManager;
56 import com.intellij.pom.Navigatable;
57 import com.intellij.problems.WolfTheProblemSolver;
58 import com.intellij.psi.*;
59 import com.intellij.ui.*;
60 import com.intellij.ui.awt.RelativePoint;
61 import com.intellij.ui.popup.AbstractPopup;
62 import com.intellij.ui.popup.PopupOwner;
63 import com.intellij.ui.popup.list.ListPopupImpl;
64 import com.intellij.util.Alarm;
65 import com.intellij.util.Icons;
66 import com.intellij.util.messages.MessageBusConnection;
67 import com.intellij.util.ui.EmptyIcon;
68 import com.intellij.util.ui.UIUtil;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
72 import javax.swing.*;
73 import javax.swing.border.Border;
74 import java.awt.*;
75 import java.awt.event.*;
76 import java.util.ArrayList;
77 import java.util.List;
79 /**
80 * User: anna
81 * Date: 03-Nov-2005
83 public class NavBarPanel extends JPanel implements DataProvider, PopupOwner {
84 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.navigationToolbar.NavigationToolbarPanel");
85 /*private static final Icon LEFT_ICON = IconLoader.getIcon("/general/splitLeft.png");
86 private static final Icon RIGHT_ICON = IconLoader.getIcon("/general/splitRight.png");
88 private final ArrayList<MyItemLabel> myList = new ArrayList<MyItemLabel>();
90 private final NavBarModel myModel;
91 private final Project myProject;
93 private Runnable myDetacher;
94 private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider();
96 private final IdeView myIdeView = new MyIdeView();
97 private final CopyPasteDelegator myCopyPasteDelegator;
98 private LightweightHint myHint = null;
99 private ListPopupImpl myNodePopup = null;
101 private final Alarm myListUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
102 private final Alarm myModelUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
104 public NavBarPanel(final Project project) {
105 super(new FlowLayout(FlowLayout.LEFT, 5, 0));
107 myProject = project;
108 myModel = new NavBarModel(myProject);
109 setBackground(UIUtil.getListBackground());
110 setOpaque(true);
112 PopupHandler.installPopupHandler(this, IdeActions.GROUP_PROJECT_VIEW_POPUP, ActionPlaces.NAVIGATION_BAR);
114 registerKeyboardAction(new ActionListener() {
115 public void actionPerformed(ActionEvent e) {
116 shiftFocus(-1);
118 }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), WHEN_FOCUSED);
120 registerKeyboardAction(new ActionListener() {
121 public void actionPerformed(final ActionEvent e) {
122 shiftFocus(1);
124 }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), WHEN_FOCUSED);
127 registerKeyboardAction(new ActionListener() {
128 public void actionPerformed(ActionEvent e) {
129 shiftFocus(-myModel.getSelectedIndex());
131 }, KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), WHEN_FOCUSED);
133 registerKeyboardAction(new ActionListener() {
134 public void actionPerformed(final ActionEvent e) {
135 shiftFocus(myModel.size() - 1 - myModel.getSelectedIndex());
137 }, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), WHEN_FOCUSED);
140 registerKeyboardAction(new ActionListener() {
141 public void actionPerformed(ActionEvent e) {
142 if (myModel.getSelectedIndex() != -1) {
143 ctrlClick(myModel.getSelectedIndex());
146 }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), WHEN_FOCUSED);
148 final ActionListener dblClickAction = new ActionListener() {
149 public void actionPerformed(ActionEvent e) {
150 if (myModel.getSelectedIndex() != -1) {
151 doubleClick(myModel.getSelectedIndex());
156 registerKeyboardAction(dblClickAction, KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), WHEN_FOCUSED);
158 registerKeyboardAction(new AbstractAction() {
159 public void actionPerformed(ActionEvent e) {
160 final Object o = myModel.getSelectedValue();
161 navigateInsideBar(optimizeTarget(o));
163 }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED);
165 registerKeyboardAction(new AbstractAction() {
166 public void actionPerformed(ActionEvent e) {
167 myModel.setSelectedIndex(-1);
168 ToolWindowManager.getInstance(project).activateEditorComponent();
170 }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_FOCUSED);
172 addFocusListener(new FocusListener() {
173 public void focusGained(final FocusEvent e) {
174 updateItems();
177 public void focusLost(final FocusEvent e) {
178 if (myProject.isDisposed()) {
179 hideHint();
180 return;
183 // required invokeLater since in current call sequence KeyboardFocusManager is not initialized yet
184 // but future focused component
185 SwingUtilities.invokeLater(new Runnable() {
186 public void run() {
187 processFocusLost(e);
193 installBorder(-1);
195 myCopyPasteDelegator = new CopyPasteDelegator(myProject, NavBarPanel.this) {
196 @NotNull
197 protected PsiElement[] getSelectedElements() {
198 final PsiElement element = getSelectedElement(PsiElement.class);
199 return element == null ? PsiElement.EMPTY_ARRAY : new PsiElement[]{element};
203 updateModel();
204 updateList();
207 private static Object optimizeTarget(Object target) {
208 if (target instanceof PsiDirectory && ((PsiDirectory)target).getFiles().length == 0) {
209 final PsiDirectory[] subDir = ((PsiDirectory)target).getSubdirectories();
210 if (subDir.length == 1) {
211 return optimizeTarget(subDir[0]);
214 return target;
217 private void processFocusLost(final FocusEvent e) {
218 final boolean nodePopupInactive = myNodePopup == null || !myNodePopup.isVisible() || !myNodePopup.isFocused();
219 final JBPopup child = JBPopupFactory.getInstance().getChildPopup(this);
220 final boolean childPopupInactive = child == null || !child.isFocused();
221 if (nodePopupInactive && childPopupInactive) {
222 final Component opposite = e.getOppositeComponent();
223 if (opposite != null && opposite != this && !isAncestorOf(opposite) && !e.isTemporary()) {
224 hideHint();
228 updateItems();
231 private void updateItems() {
232 for (MyItemLabel item : myList) {
233 item.update();
237 public void select() {
238 updateModel();
239 updateList();
241 if (!myList.isEmpty()) {
242 myModel.setSelectedIndex(myList.size() - 1);
243 IdeFocusManager.getInstance(myProject).requestFocus(this, true);
247 private void shiftFocus(int direction) {
248 myModel.setSelectedIndex(myModel.getIndexByModel(myModel.getSelectedIndex() + direction));
251 private void scrollSelectionToVisible() {
252 final int selectedIndex = myModel.getSelectedIndex();
253 if (selectedIndex == -1 || selectedIndex >= myList.size()) return;
255 MyItemLabel selectedItem = myList.get(selectedIndex);
256 Rectangle rect = selectedItem.getBounds();
257 scrollRectToVisible(rect);
260 @Nullable
261 private MyItemLabel getItem(int index) {
262 if (index != -1 && index < myList.size()) {
263 return myList.get(index);
265 return null;
268 private void scheduleModelUpdate() {
269 myModelUpdateAlarm.cancelAllRequests();
270 if (!isInFloatingMode()) {
271 myModelUpdateAlarm.addRequest(new Runnable() {
272 public void run() {
273 if (myProject.isDisposed()) return;
274 updateModel();
276 }, 300);
280 private boolean isInFloatingMode() {
281 return myHint != null && myHint.isVisible();
284 private void updateModel() {
285 DataContext context = DataManager.getInstance().getDataContext();
287 if (context.getData(DataConstants.IDE_VIEW) == myIdeView || context.getData(DataConstants.PROJECT) != myProject || isNodePopupShowing()) {
288 scheduleModelUpdate();
289 return;
292 myModel.updateModel(context);
295 @Override
296 public Dimension getPreferredSize() {
297 if (!myList.isEmpty()) {
298 return super.getPreferredSize();
300 else {
301 return new MyItemLabel(0, Icons.DIRECTORY_OPEN_ICON, "Sample", SimpleTextAttributes.REGULAR_ATTRIBUTES).getPreferredSize();
305 private void updateList() {
306 myList.clear();
307 for (int index = 0; index < myModel.size(); index++) {
308 final Object object = myModel.get(index);
309 Icon closedIcon = getIcon(object, false);
310 Icon openIcon = getIcon(object, true);
312 if (closedIcon == null && openIcon != null) closedIcon = openIcon;
313 if (openIcon == null && closedIcon != null) openIcon = closedIcon;
314 if (openIcon == null) {
315 openIcon = closedIcon = new EmptyIcon(5, 5);
318 final MyItemLabel label =
319 new MyItemLabel(index, wrapIcon(openIcon, closedIcon, index), NavBarModel.getPresentableText(object, getWindow()),
320 myModel.getTextAttributes(object, false));
322 installActions(index, label);
323 myList.add(label);
326 rebuildComponent();
329 @Nullable
330 private static Icon getIcon(final Object object, final boolean isopen) {
331 if (!NavBarModel.checkValid(object)) return null;
332 if (object instanceof Project) return IconLoader.getIcon("/nodes/project.png");
333 if (object instanceof Module) return ((Module)object).getModuleType().getNodeIcon(false);
334 try {
335 if (object instanceof PsiElement) return ApplicationManager.getApplication().runReadAction(
336 new Computable<Icon>() {
337 public Icon compute() {
338 return ((PsiElement)object).isValid() ? ((PsiElement)object).getIcon(isopen ? Iconable.ICON_FLAG_OPEN : Iconable.ICON_FLAG_CLOSED) : null;
343 catch (IndexNotReadyException e) {
344 return null;
346 if (object instanceof JdkOrderEntry) return ((JdkOrderEntry)object).getJdk().getSdkType().getIcon();
347 if (object instanceof LibraryOrderEntry) return IconLoader.getIcon("/nodes/ppLibClosed.png");
348 if (object instanceof ModuleOrderEntry) return ((ModuleOrderEntry)object).getModule().getModuleType().getNodeIcon(false);
349 return null;
353 private Icon wrapIcon(final Icon openIcon, final Icon closedIcon, final int idx) {
354 return new Icon() {
355 public void paintIcon(Component c, Graphics g, int x, int y) {
356 if (myModel.getSelectedIndex() == idx && myNodePopup != null && myNodePopup.isVisible()) {
357 openIcon.paintIcon(c, g, x, y);
359 else {
360 closedIcon.paintIcon(c, g, x, y);
364 public int getIconWidth() {
365 return openIcon.getIconWidth();
368 public int getIconHeight() {
369 return openIcon.getIconHeight();
374 private void rebuildComponent() {
375 removeAll();
377 for (MyItemLabel item : myList) {
378 add(item);
381 revalidate();
382 repaint();
384 SwingUtilities.invokeLater(new Runnable() {
385 public void run() {
386 scrollSelectionToVisible();
391 private Window getWindow() {
392 return SwingUtilities.getWindowAncestor(this);
395 // ------ NavBar actions -------------------------
396 private void installActions(final int index, final MyItemLabel component) {
397 ListenerUtil.addMouseListener(component, new MouseAdapter() {
398 public void mouseClicked(MouseEvent e) {
399 if (!e.isConsumed() && !e.isPopupTrigger() && e.getClickCount() == 2) {
400 myModel.setSelectedIndex(index);
401 IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true);
402 doubleClick(index);
403 e.consume();
408 ListenerUtil.addMouseListener(component, new MouseAdapter() {
409 public void mouseReleased(final MouseEvent e) {
410 if (SystemInfo.isWindows) {
411 click(e);
415 public void mousePressed(final MouseEvent e) {
416 if (!SystemInfo.isWindows) {
417 click(e);
421 private void click(final MouseEvent e) {
422 if (!e.isConsumed() && e.isPopupTrigger()) {
423 myModel.setSelectedIndex(index);
424 IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true);
425 rightClick(index);
426 e.consume();
431 ListenerUtil.addMouseListener(component, new MouseAdapter() {
432 public void mouseReleased(final MouseEvent e) {
433 if (SystemInfo.isWindows) {
434 click(e);
438 public void mousePressed(final MouseEvent e) {
439 if (!SystemInfo.isWindows) {
440 click(e);
444 private void click(final MouseEvent e) {
445 if (!e.isConsumed() && !e.isPopupTrigger() && e.getClickCount() == 1) {
446 ctrlClick(index);
447 myModel.setSelectedIndex(index);
448 e.consume();
454 private void doubleClick(final int index) {
455 doubleClick(myModel.getElement(index));
458 private void doubleClick(final Object object) {
459 if (object instanceof Navigatable) {
460 final Navigatable navigatable = (Navigatable)object;
461 if (navigatable.canNavigate()) {
462 navigatable.navigate(true);
465 else if (object instanceof Module) {
466 final ProjectView projectView = ProjectView.getInstance(myProject);
467 final AbstractProjectViewPane projectViewPane = projectView.getProjectViewPaneById(projectView.getCurrentViewId());
468 projectViewPane.selectModule((Module)object, true);
470 else if (object instanceof Project) {
471 return;
473 hideHint();
476 private void ctrlClick(final int index) {
477 if (isNodePopupShowing()) {
478 cancelPopup();
479 if (myModel.getSelectedIndex() == index) {
480 return;
484 final Object object = myModel.getElement(index);
485 final List<Object> objects = myModel.calcElementChildren(object);
487 if (!objects.isEmpty()) {
488 final Object[] siblings = new Object[objects.size()];
489 final Icon[] icons = new Icon[objects.size()];
490 for (int i = 0; i < objects.size(); i++) {
491 siblings[i] = objects.get(i);
492 icons[i] = getIcon(siblings[i], false);
494 final MyItemLabel item = getItem(index);
495 LOG.assertTrue(item != null);
496 final BaseListPopupStep<Object> step = new BaseListPopupStep<Object>("", siblings, icons) {
497 public boolean isSpeedSearchEnabled() { return true; }
498 @NotNull public String getTextFor(final Object value) { return NavBarModel.getPresentableText(value, null);}
499 public boolean isSelectable(Object value) { return true; }
500 public PopupStep onChosen(final Object selectedValue, final boolean finalChoice) {
501 navigateInsideBar(optimizeTarget(selectedValue));
502 return FINAL_CHOICE;
506 public void canceled() {
507 super.canceled();
508 item.getLabel().setIcon(wrapIcon(NavBarModel.getIcon(object), index, Color.gray));
512 step.setDefaultOptionIndex(index < myModel.size() - 1 ? objects.indexOf(myModel.getElement(index + 1)) : 0);
513 myNodePopup = new ListPopupImpl(step) {
514 protected ListCellRenderer getListElementRenderer() { return new MySiblingsListCellRenderer();}
517 myNodePopup.registerAction("left", KeyEvent.VK_LEFT, 0, new AbstractAction() {
518 public void actionPerformed(ActionEvent e) {
519 myNodePopup.goBack();
520 shiftFocus(-1);
521 restorePopup();
524 myNodePopup.registerAction("right", KeyEvent.VK_RIGHT, 0, new AbstractAction() {
525 public void actionPerformed(ActionEvent e) {
526 myNodePopup.goBack();
527 shiftFocus(1);
528 restorePopup();
532 ListenerUtil.addMouseListener(myNodePopup.getComponent(), new MouseAdapter() {
533 public void mouseReleased(final MouseEvent e) {
534 if (SystemInfo.isWindows) {
535 click(e);
539 public void mousePressed(final MouseEvent e) {
540 if (!SystemInfo.isWindows) {
541 click(e);
545 private void click(final MouseEvent e) {
546 if (!e.isConsumed() && e.isPopupTrigger()) {
547 myModel.setSelectedIndex(index);
548 IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true);
549 rightClick(index);
550 e.consume();
555 myNodePopup.showUnderneathOf(item);
559 private boolean isNodePopupShowing() {
560 return myNodePopup != null && myNodePopup.isVisible();
563 private void navigateInsideBar(final Object object) {
564 myModel.updateModel(object);
565 updateList();
567 myModel.setSelectedIndex(myList.size() - 1);
569 if (myHint != null) {
570 final Dimension dimension = getPreferredSize();
571 final Rectangle bounds = myHint.getBounds();
572 myHint.setBounds(bounds.x, bounds.y, dimension.width, dimension.height);
575 SwingUtilities.invokeLater(new Runnable() {
576 public void run() {
577 if (myModel.hasChildren(object)) {
578 restorePopup();
580 else {
581 doubleClick(object);
587 private void rightClick(final int index) {
588 final ActionManager actionManager = ActionManager.getInstance();
589 final ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_NAVBAR_POPUP);
590 final ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.NAVIGATION_BAR, group);
591 final MyItemLabel item = getItem(index);
592 if (item != null) {
593 popupMenu.getComponent().show(this, item.getX(), item.getY() + item.getHeight());
597 private void restorePopup() {
598 cancelPopup();
599 ctrlClick(myModel.getSelectedIndex());
602 private void cancelPopup() {
603 if (myNodePopup != null) {
604 myNodePopup.cancel();
605 myNodePopup = null;
609 private void hideHint() {
610 if (myHint != null) {
611 myHint.hide();
612 myHint = null;
616 @Nullable
617 public Object getData(String dataId) {
618 if (dataId.equals(DataConstants.PROJECT)) {
619 return !myProject.isDisposed() ? myProject : null;
621 if (dataId.equals(DataConstants.MODULE)) {
622 final Module module = getSelectedElement(Module.class);
623 if (module != null && !module.isDisposed()) return module;
624 final PsiElement element = getSelectedElement(PsiElement.class);
625 if (element != null) {
626 return ModuleUtil.findModuleForPsiElement(element);
628 return null;
630 if (dataId.equals(DataConstants.MODULE_CONTEXT)) {
631 final PsiDirectory directory = getSelectedElement(PsiDirectory.class);
632 if (directory != null) {
633 final VirtualFile dir = directory.getVirtualFile();
634 if (ProjectRootsUtil.isModuleContentRoot(dir, myProject)) {
635 return ModuleUtil.findModuleForPsiElement(directory);
638 return null;
640 if (dataId.equals(DataConstants.PSI_ELEMENT)) {
641 final PsiElement element = getSelectedElement(PsiElement.class);
642 return element != null && element.isValid() ? element : null;
644 if (dataId.equals(DataConstants.PSI_ELEMENT_ARRAY)) {
645 final PsiElement element = getSelectedElement(PsiElement.class);
646 return element != null && element.isValid() ? new PsiElement[]{element} : null;
649 if (dataId.equals(DataConstants.CONTEXT_COMPONENT)) {
650 return this;
652 if (DataConstants.CUT_PROVIDER.equals(dataId)) {
653 return myCopyPasteDelegator.getCutProvider();
655 if (DataConstants.COPY_PROVIDER.equals(dataId)) {
656 return myCopyPasteDelegator.getCopyProvider();
658 if (DataConstants.PASTE_PROVIDER.equals(dataId)) {
659 return myCopyPasteDelegator.getPasteProvider();
661 if (DataConstants.DELETE_ELEMENT_PROVIDER.equals(dataId)) {
662 return getSelectedElement(Module.class) != null ? myDeleteModuleProvider : new DeleteHandler.DefaultDeleteProvider();
665 if (DataConstants.IDE_VIEW.equals(dataId)) {
666 return myIdeView;
669 return null;
672 @Nullable
673 @SuppressWarnings({"unchecked"})
674 private <T> T getSelectedElement(Class<T> klass) {
675 Object selectedValue1 = myModel.getSelectedValue();
676 if (selectedValue1 == null) {
677 final int modelSize = myModel.size();
678 if (modelSize > 0) {
679 selectedValue1 = myModel.getElement(modelSize - 1);
682 final Object selectedValue = selectedValue1;
683 return selectedValue != null && klass.isAssignableFrom(selectedValue.getClass()) ? (T)selectedValue : null;
686 public Point getBestPopupPosition() {
687 int index = myModel.getSelectedIndex();
688 final int modelSize = myModel.size();
689 if (index == -1) {
690 index = modelSize - 1;
692 if (index > -1 && index < modelSize) {
693 final MyItemLabel item = getItem(index);
694 if (item != null) {
695 return new Point(item.getX(), item.getY() + item.getHeight());
698 return null;
701 // ----- inplace NavBar -----------
702 public void installListeners() {
703 final MyPsiTreeChangeAdapter psiListener = new MyPsiTreeChangeAdapter();
704 final MyProblemListener problemListener = new MyProblemListener();
705 final MyFileStatusListener fileStatusListener = new MyFileStatusListener();
706 final MyTimerListener timerListener = new MyTimerListener();
709 PsiManager.getInstance(myProject).addPsiTreeChangeListener(psiListener);
710 WolfTheProblemSolver.getInstance(myProject).addProblemListener(problemListener);
711 FileStatusManager.getInstance(myProject).addFileStatusListener(fileStatusListener);
713 final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
714 actionManager.addTimerListener(10000, timerListener);
716 final MessageBusConnection busConnection = myProject.getMessageBus().connect();
717 busConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener());
718 busConnection.subscribe(NavBarModelListener.NAV_BAR, new NavBarModelListener() {
719 public void modelChanged() {
720 scheduleListUpdate();
723 public void selectionChanged() {
724 updateItems();
726 scrollSelectionToVisible();
730 if (myDetacher != null) uninstallListeners();
732 myDetacher = new Runnable() {
733 public void run() {
734 ActionManagerEx.getInstanceEx().removeTimerListener(timerListener);
735 busConnection.disconnect();
737 WolfTheProblemSolver.getInstance(myProject).removeProblemListener(problemListener);
738 PsiManager.getInstance(myProject).removePsiTreeChangeListener(psiListener);
739 FileStatusManager.getInstance(myProject).removeFileStatusListener(fileStatusListener);
744 public void uninstallListeners() {
745 myDetacher.run();
746 myDetacher = null;
748 myListUpdateAlarm.cancelAllRequests();
749 myModelUpdateAlarm.cancelAllRequests();
752 public void installBorder(final int rightOffset) {
753 setBorder(new Border() {
754 public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
755 g.setColor(c.getBackground() != null ? c.getBackground().darker() : Color.darkGray);
756 if (rightOffset == -1) {
757 g.drawLine(0, 0, width - 1, 0);
758 } else {
759 g.drawLine(0, 0, width - rightOffset + 3, 0);
761 g.drawLine(0, height - 1 , width, height - 1);
763 if (rightOffset == -1) {
764 g.drawLine(0, 0, 0, height);
765 g.drawLine(width - 1, 0, width - 1, height - 1);
769 public Insets getBorderInsets(final Component c) {
770 return new Insets(3, 4, 3, 4);
773 public boolean isBorderOpaque() {
774 return true;
779 public void addNotify() {
780 super.addNotify();
781 installListeners();
784 public void removeNotify() {
785 super.removeNotify();
786 uninstallListeners();
789 public void updateState(final boolean show) {
790 updateModel();
791 if (isShowing()) {
792 updateList();
793 final int selectedIndex = myModel.getSelectedIndex();
794 if (show && selectedIndex > -1 && selectedIndex < myModel.size()) {
795 final MyItemLabel item = getItem(selectedIndex);
796 if (item != null) {
797 IdeFocusManager.getInstance(myProject).requestFocus(item, true);
803 // ------ popup NavBar ----------
804 public void showHint(@Nullable final Editor editor, final DataContext dataContext) {
805 updateModel();
807 if (myModel.isEmpty()) return;
808 myHint = new LightweightHint(this) {
809 public void hide() {
810 super.hide();
811 cancelPopup();
814 myHint.setForceLightweightPopup(true);
815 registerKeyboardAction(new AbstractAction() {
816 public void actionPerformed(ActionEvent e) {
817 hideHint();
819 }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_FOCUSED);
820 final KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
821 final Window focusedWindow = focusManager.getFocusedWindow();
822 if (editor == null) {
823 final RelativePoint relativePoint = JBPopupFactory.getInstance().guessBestPopupLocation(dataContext);
824 final Component owner = focusManager.getFocusOwner();
825 final Component cmp = relativePoint.getComponent();
826 if (cmp instanceof JComponent && cmp.isShowing()) {
827 myHint.show((JComponent)cmp, relativePoint.getPoint().x, relativePoint.getPoint().y,
828 owner instanceof JComponent ? (JComponent)owner : null);
831 else {
832 final Container container = focusedWindow != null ? focusedWindow : editor.getContentComponent();
833 final Point p = AbstractPopup.getCenterOf(container, this);
834 p.x -= container.getLocation().x; //make NavBar visible in case of two monitors; p should be relative
835 p.y = container.getHeight() / 4;
836 HintManagerImpl.getInstanceImpl().showEditorHint(myHint, editor, p, HintManagerImpl.HIDE_BY_ESCAPE, 0, true);
838 select();
841 public static boolean wolfHasProblemFilesBeneath(final PsiElement scope) {
842 return WolfTheProblemSolver.getInstance(scope.getProject()).hasProblemFilesBeneath(new Condition<VirtualFile>() {
843 public boolean value(final VirtualFile virtualFile) {
844 if (scope instanceof PsiDirectory) {
845 final PsiDirectory directory = (PsiDirectory)scope;
846 if (!VfsUtil.isAncestor(directory.getVirtualFile(), virtualFile, false)) return false;
847 return ModuleUtil.findModuleForFile(virtualFile, scope.getProject()) == ModuleUtil.findModuleForPsiElement(scope);
849 else if (scope instanceof PsiDirectoryContainer) { // TODO: remove. It doesn't look like we'll have packages in navbar ever again
850 final PsiDirectory[] psiDirectories = ((PsiDirectoryContainer)scope).getDirectories();
851 for (PsiDirectory directory : psiDirectories) {
852 if (VfsUtil.isAncestor(directory.getVirtualFile(), virtualFile, false)) {
853 return true;
857 return false;
862 protected class MyItemLabel extends SimpleColoredComponent {
863 private final String myText;
864 private final SimpleTextAttributes myAttributes;
865 private final int myIndex;
866 private final Icon myIcon;
868 public MyItemLabel(int idx, Icon icon, String presentableText, SimpleTextAttributes textAttributes) {
869 myIndex = idx;
870 myText = presentableText;
871 myIcon = icon;
872 myAttributes = textAttributes;
874 setIpad(new Insets(1, 2, 1, 2));
876 update();
879 private void update() {
880 clear();
882 setIcon(myIcon);
883 boolean focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == NavBarPanel.this;
885 boolean selected = myModel.getSelectedIndex() == myIndex;
887 setPaintFocusBorder(selected);
888 setFocusBorderAroundIcon(selected);
890 setBackground(selected && focused ? UIUtil.getListSelectionBackground() : UIUtil.getListBackground());
892 final Color fg = selected && focused
893 ? UIUtil.getListSelectionForeground()
894 : myModel.getSelectedIndex() < myIndex && myModel.getSelectedIndex() != -1
895 ? UIUtil.getInactiveTextColor()
896 : myAttributes.getFgColor();
898 final Color bg = selected && focused ? UIUtil.getListSelectionBackground() : myAttributes.getBgColor();
900 append(myText, new SimpleTextAttributes(bg, fg, myAttributes.getWaveColor(), myAttributes.getStyle()));
902 repaint();
906 private final class MyTimerListener implements TimerListener {
908 public ModalityState getModalityState() {
909 return ModalityState.stateForComponent(NavBarPanel.this);
912 public void run() {
913 if (!isShowing()) {
914 return;
917 Window mywindow = SwingUtilities.windowForComponent(NavBarPanel.this);
918 if (mywindow != null && !mywindow.isActive()) return;
920 final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
921 if (window instanceof Dialog) {
922 final Dialog dialog = (Dialog)window;
923 if (dialog.isModal() && !SwingUtilities.isDescendingFrom(NavBarPanel.this, dialog)) {
924 return;
928 scheduleModelUpdate();
933 private final class MyIdeView implements IdeView {
935 public void selectElement(PsiElement element) {
936 myModel.updateModel(element);
938 if (element instanceof Navigatable) {
939 final Navigatable navigatable = (Navigatable)element;
940 if (navigatable.canNavigate()) {
941 ((Navigatable)element).navigate(true);
944 hideHint();
947 public PsiDirectory[] getDirectories() {
948 final PsiDirectory dir = getSelectedElement(PsiDirectory.class);
949 if (dir != null && dir.isValid()) {
950 return new PsiDirectory[]{dir};
952 final PsiElement element = getSelectedElement(PsiElement.class);
953 if (element != null && element.isValid()) {
954 final PsiFile file = element.getContainingFile();
955 if (file != null) {
956 final PsiDirectory psiDirectory = file.getContainingDirectory();
957 return psiDirectory != null ? new PsiDirectory[]{psiDirectory} : PsiDirectory.EMPTY_ARRAY;
960 final PsiDirectoryContainer directoryContainer = getSelectedElement(PsiDirectoryContainer.class);
961 if (directoryContainer != null) {
962 return directoryContainer.getDirectories();
964 final Module module = getSelectedElement(Module.class);
965 if (module != null && !module.isDisposed()) {
966 ArrayList<PsiDirectory> dirs = new ArrayList<PsiDirectory>();
967 final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots();
968 final PsiManager psiManager = PsiManager.getInstance(myProject);
969 for (VirtualFile virtualFile : sourceRoots) {
970 final PsiDirectory directory = psiManager.findDirectory(virtualFile);
971 if (directory != null && directory.isValid()) {
972 dirs.add(directory);
975 return dirs.toArray(new PsiDirectory[dirs.size()]);
977 return PsiDirectory.EMPTY_ARRAY;
980 public PsiDirectory getOrChooseDirectory() {
981 return DirectoryChooserUtil.getOrChooseDirectory(this);
986 private void scheduleListUpdate() {
987 // Can be called from other threads so invokeLater ensures we're in EDT
988 SwingUtilities.invokeLater(new Runnable() {
989 public void run() {
990 myListUpdateAlarm.cancelAllRequests();
991 myListUpdateAlarm.addRequest(new Runnable() {
992 public void run() {
993 if (myProject.isDisposed()) return;
994 updateList();
996 }, 50);
1001 private class MyPsiTreeChangeAdapter extends PsiTreeChangeAdapter {
1002 public void childAdded(PsiTreeChangeEvent event) {
1003 scheduleModelUpdate();
1006 public void childReplaced(PsiTreeChangeEvent event) {
1007 scheduleModelUpdate();
1010 public void childMoved(PsiTreeChangeEvent event) {
1011 scheduleModelUpdate();
1014 public void childrenChanged(PsiTreeChangeEvent event) {
1015 scheduleModelUpdate();
1018 public void propertyChanged(final PsiTreeChangeEvent event) {
1019 scheduleModelUpdate();
1023 private class MyModuleRootListener implements ModuleRootListener {
1024 public void beforeRootsChange(ModuleRootEvent event) {
1027 public void rootsChanged(ModuleRootEvent event) {
1028 scheduleModelUpdate();
1032 private class MyProblemListener extends WolfTheProblemSolver.ProblemListener {
1034 public void problemsAppeared(VirtualFile file) {
1035 scheduleListUpdate();
1038 public void problemsDisappeared(VirtualFile file) {
1039 scheduleListUpdate();
1044 private class MyFileStatusListener implements FileStatusListener {
1046 public void fileStatusesChanged() {
1047 scheduleListUpdate();
1050 public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
1051 scheduleListUpdate();
1055 private class MySiblingsListCellRenderer extends ColoredListCellRenderer {
1056 protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
1057 setFocusBorderAroundIcon(false);
1058 String name = NavBarModel.getPresentableText(value, getWindow());
1060 Color color = list.getForeground();
1061 boolean isProblemFile = false;
1062 if (value instanceof PsiElement) {
1063 final PsiElement psiElement = (PsiElement)value;
1064 PsiFile psiFile = psiElement.getContainingFile();
1065 if (psiFile != null) {
1066 VirtualFile vFile = psiFile.getVirtualFile();
1067 if (vFile != null) {
1068 if (WolfTheProblemSolver.getInstance(myProject).isProblemFile(vFile)) {
1069 isProblemFile = true;
1071 FileStatus status = FileStatusManager.getInstance(myProject).getStatus(vFile);
1072 color = status.getColor();
1075 else {
1076 isProblemFile = wolfHasProblemFilesBeneath(psiElement);
1079 else if (value instanceof Module) {
1080 final Module module = (Module)value;
1081 isProblemFile = WolfTheProblemSolver.getInstance(myProject).hasProblemFilesBeneath(module);
1083 else if (value instanceof Project) {
1084 final Module[] modules = ModuleManager.getInstance((Project)value).getModules();
1085 for (Module module : modules) {
1086 if (WolfTheProblemSolver.getInstance(myProject).hasProblemFilesBeneath(module)) {
1087 isProblemFile = true;
1088 break;
1092 SimpleTextAttributes nameAttributes;
1093 if (isProblemFile) {
1094 TextAttributes attributes = new TextAttributes(color, null, Color.red, EffectType.WAVE_UNDERSCORE, Font.PLAIN);
1095 nameAttributes = SimpleTextAttributes.fromTextAttributes(attributes);
1097 else {
1098 nameAttributes = new SimpleTextAttributes(Font.PLAIN, color);
1100 append(name, nameAttributes);
1101 setIcon(NavBarPanel.getIcon(value, false));
1102 setPaintFocusBorder(false);
1103 setBackground(selected ? UIUtil.getListSelectionBackground() : UIUtil.getListBackground());