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
;
73 import javax
.swing
.border
.Border
;
75 import java
.awt
.event
.*;
76 import java
.util
.ArrayList
;
77 import java
.util
.List
;
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));
108 myModel
= new NavBarModel(myProject
);
109 setBackground(UIUtil
.getListBackground());
112 PopupHandler
.installPopupHandler(this, IdeActions
.GROUP_PROJECT_VIEW_POPUP
, ActionPlaces
.NAVIGATION_BAR
);
114 registerKeyboardAction(new ActionListener() {
115 public void actionPerformed(ActionEvent e
) {
118 }, KeyStroke
.getKeyStroke(KeyEvent
.VK_LEFT
, 0), WHEN_FOCUSED
);
120 registerKeyboardAction(new ActionListener() {
121 public void actionPerformed(final ActionEvent e
) {
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
) {
177 public void focusLost(final FocusEvent e
) {
178 if (myProject
.isDisposed()) {
183 // required invokeLater since in current call sequence KeyboardFocusManager is not initialized yet
184 // but future focused component
185 SwingUtilities
.invokeLater(new Runnable() {
195 myCopyPasteDelegator
= new CopyPasteDelegator(myProject
, NavBarPanel
.this) {
197 protected PsiElement
[] getSelectedElements() {
198 final PsiElement element
= getSelectedElement(PsiElement
.class);
199 return element
== null ? PsiElement
.EMPTY_ARRAY
: new PsiElement
[]{element
};
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]);
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()) {
231 private void updateItems() {
232 for (MyItemLabel item
: myList
) {
237 public void select() {
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
);
261 private MyItemLabel
getItem(int index
) {
262 if (index
!= -1 && index
< myList
.size()) {
263 return myList
.get(index
);
268 private void scheduleModelUpdate() {
269 myModelUpdateAlarm
.cancelAllRequests();
270 if (!isInFloatingMode()) {
271 myModelUpdateAlarm
.addRequest(new Runnable() {
273 if (myProject
.isDisposed()) return;
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();
292 myModel
.updateModel(context
);
296 public Dimension
getPreferredSize() {
297 if (!myList
.isEmpty()) {
298 return super.getPreferredSize();
301 return new MyItemLabel(0, Icons
.DIRECTORY_OPEN_ICON
, "Sample", SimpleTextAttributes
.REGULAR_ATTRIBUTES
).getPreferredSize();
305 private void updateList() {
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
);
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);
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
) {
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);
353 private Icon
wrapIcon(final Icon openIcon
, final Icon closedIcon
, final int idx
) {
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
);
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() {
377 for (MyItemLabel item
: myList
) {
384 SwingUtilities
.invokeLater(new Runnable() {
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);
408 ListenerUtil
.addMouseListener(component
, new MouseAdapter() {
409 public void mouseReleased(final MouseEvent e
) {
410 if (SystemInfo
.isWindows
) {
415 public void mousePressed(final MouseEvent e
) {
416 if (!SystemInfo
.isWindows
) {
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);
431 ListenerUtil
.addMouseListener(component
, new MouseAdapter() {
432 public void mouseReleased(final MouseEvent e
) {
433 if (SystemInfo
.isWindows
) {
438 public void mousePressed(final MouseEvent e
) {
439 if (!SystemInfo
.isWindows
) {
444 private void click(final MouseEvent e
) {
445 if (!e
.isConsumed() && !e
.isPopupTrigger() && e
.getClickCount() == 1) {
447 myModel
.setSelectedIndex(index
);
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
) {
476 private void ctrlClick(final int index
) {
477 if (isNodePopupShowing()) {
479 if (myModel
.getSelectedIndex() == index
) {
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
));
506 public void 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();
524 myNodePopup
.registerAction("right", KeyEvent
.VK_RIGHT
, 0, new AbstractAction() {
525 public void actionPerformed(ActionEvent e
) {
526 myNodePopup
.goBack();
532 ListenerUtil
.addMouseListener(myNodePopup
.getComponent(), new MouseAdapter() {
533 public void mouseReleased(final MouseEvent e
) {
534 if (SystemInfo
.isWindows
) {
539 public void mousePressed(final MouseEvent e
) {
540 if (!SystemInfo
.isWindows
) {
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);
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
);
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() {
577 if (myModel
.hasChildren(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
);
593 popupMenu
.getComponent().show(this, item
.getX(), item
.getY() + item
.getHeight());
597 private void restorePopup() {
599 ctrlClick(myModel
.getSelectedIndex());
602 private void cancelPopup() {
603 if (myNodePopup
!= null) {
604 myNodePopup
.cancel();
609 private void hideHint() {
610 if (myHint
!= null) {
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
);
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
);
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
)) {
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
)) {
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();
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();
690 index
= modelSize
- 1;
692 if (index
> -1 && index
< modelSize
) {
693 final MyItemLabel item
= getItem(index
);
695 return new Point(item
.getX(), item
.getY() + item
.getHeight());
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() {
726 scrollSelectionToVisible();
730 if (myDetacher
!= null) uninstallListeners();
732 myDetacher
= new Runnable() {
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() {
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);
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() {
779 public void addNotify() {
784 public void removeNotify() {
785 super.removeNotify();
786 uninstallListeners();
789 public void updateState(final boolean show
) {
793 final int selectedIndex
= myModel
.getSelectedIndex();
794 if (show
&& selectedIndex
> -1 && selectedIndex
< myModel
.size()) {
795 final MyItemLabel item
= getItem(selectedIndex
);
797 IdeFocusManager
.getInstance(myProject
).requestFocus(item
, true);
803 // ------ popup NavBar ----------
804 public void showHint(@Nullable final Editor editor
, final DataContext dataContext
) {
807 if (myModel
.isEmpty()) return;
808 myHint
= new LightweightHint(this) {
814 myHint
.setForceLightweightPopup(true);
815 registerKeyboardAction(new AbstractAction() {
816 public void actionPerformed(ActionEvent e
) {
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);
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);
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)) {
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
) {
870 myText
= presentableText
;
872 myAttributes
= textAttributes
;
874 setIpad(new Insets(1, 2, 1, 2));
879 private void update() {
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()));
906 private final class MyTimerListener
implements TimerListener
{
908 public ModalityState
getModalityState() {
909 return ModalityState
.stateForComponent(NavBarPanel
.this);
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
)) {
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);
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();
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()) {
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() {
990 myListUpdateAlarm
.cancelAllRequests();
991 myListUpdateAlarm
.addRequest(new Runnable() {
993 if (myProject
.isDisposed()) return;
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();
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;
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
);
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());