2 * Copyright 2000-2009 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
.actions
;
18 import com
.intellij
.featureStatistics
.FeatureUsageTracker
;
19 import com
.intellij
.openapi
.actionSystem
.*;
20 import com
.intellij
.openapi
.editor
.markup
.EffectType
;
21 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
22 import com
.intellij
.openapi
.fileEditor
.FileEditorManager
;
23 import com
.intellij
.openapi
.fileEditor
.impl
.EditorHistoryManager
;
24 import com
.intellij
.openapi
.fileEditor
.impl
.FileEditorManagerImpl
;
25 import com
.intellij
.openapi
.project
.DumbAware
;
26 import com
.intellij
.openapi
.project
.Project
;
27 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
28 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
29 import com
.intellij
.openapi
.util
.Computable
;
30 import com
.intellij
.openapi
.util
.Iconable
;
31 import com
.intellij
.openapi
.vcs
.FileStatus
;
32 import com
.intellij
.openapi
.vcs
.FileStatusManager
;
33 import com
.intellij
.openapi
.vfs
.VirtualFile
;
34 import com
.intellij
.openapi
.wm
.ToolWindow
;
35 import com
.intellij
.openapi
.wm
.ToolWindowManager
;
36 import com
.intellij
.openapi
.wm
.ex
.WindowManagerEx
;
37 import com
.intellij
.openapi
.wm
.impl
.IdeFrameImpl
;
38 import com
.intellij
.openapi
.wm
.impl
.ToolWindowManagerImpl
;
39 import com
.intellij
.ui
.ColoredListCellRenderer
;
40 import com
.intellij
.ui
.IdeBorderFactory
;
41 import com
.intellij
.ui
.SimpleTextAttributes
;
42 import com
.intellij
.util
.ArrayUtil
;
43 import com
.intellij
.util
.IconUtil
;
44 import com
.intellij
.util
.Icons
;
45 import org
.jetbrains
.annotations
.NonNls
;
48 import javax
.swing
.FocusManager
;
49 import javax
.swing
.border
.EmptyBorder
;
50 import javax
.swing
.event
.ListSelectionEvent
;
51 import javax
.swing
.event
.ListSelectionListener
;
53 import java
.awt
.event
.*;
54 import java
.awt
.image
.BufferedImage
;
59 * @author Konstantin Bulenkov
61 public class ToolWindowSwitcher
extends AnAction
implements DumbAware
{
62 private static volatile ToolWindowSwitcherPanel SWITCHER
= null;
63 private static final Color BORDER_COLOR
= new Color(0x87, 0x87, 0x87);
64 private static final Color SEPARATOR_COLOR
= BORDER_COLOR
.brighter();
65 @NonNls private static final String SWITCHER_FEATURE_ID
= "switcher";
67 private static final KeyListener performanceProblemsSolver
= new KeyAdapter() { //IDEA-24436
69 public void keyReleased(KeyEvent e
) {
70 if (e
.getKeyCode() == KeyEvent
.VK_CONTROL
) {
71 synchronized (ToolWindowSwitcher
.class) {
72 if (ToolWindowSwitcher
.SWITCHER
!= null) {
73 ToolWindowSwitcher
.SWITCHER
.navigate();
80 private static Component focusComponent
= null;
81 @NonNls private static final String SWITCHER_TITLE
= "Switcher";
83 public void actionPerformed(AnActionEvent e
) {
84 final Project project
= PlatformDataKeys
.PROJECT
.getData(e
.getDataContext());
85 if (project
== null) return;
86 if (SWITCHER
== null) {
87 focusComponent
= FocusManager
.getCurrentManager().getFocusOwner();
88 if (focusComponent
!= null) {
89 focusComponent
.addKeyListener(performanceProblemsSolver
);
91 SWITCHER
= new ToolWindowSwitcherPanel(project
);
92 FeatureUsageTracker
.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID
);
95 if (e
.getInputEvent().isShiftDown()) {
102 private class ToolWindowSwitcherPanel
extends JPanel
implements KeyListener
, MouseListener
, MouseMotionListener
{
103 final JBPopup myPopup
;
104 final Map
<ToolWindow
, String
> ids
= new HashMap
<ToolWindow
, String
>();
105 final JList toolwindows
;
107 final JPanel separator
;
108 final ToolWindowManager twManager
;
109 final JLabel pathLabel
= new JLabel(" ");
110 final JPanel descriptions
;
111 final Project project
;
115 ToolWindowSwitcherPanel(Project project
) {
116 super(new BorderLayout(0, 0));
117 this.project
= project
;
119 addKeyListener(this);
120 setBorder(new EmptyBorder(0, 0, 0, 0));
121 setBackground(Color
.WHITE
);
122 pathLabel
.setHorizontalAlignment(SwingConstants
.RIGHT
);
124 final Font font
= pathLabel
.getFont();
125 pathLabel
.setFont(font
.deriveFont((float)10));
127 descriptions
= new JPanel(new BorderLayout()) {
129 protected void paintComponent(Graphics g
) {
130 super.paintComponent(g
);
131 g
.setColor(BORDER_COLOR
);
132 g
.drawLine(0, 0, getWidth(), 0);
136 descriptions
.setBorder(BorderFactory
.createEmptyBorder(1, 4, 1, 4));
137 descriptions
.add(pathLabel
);
138 twManager
= ToolWindowManager
.getInstance(project
);
139 final DefaultListModel twModel
= new DefaultListModel();
140 for (String id
: twManager
.getToolWindowIds()) {
141 final ToolWindow tw
= twManager
.getToolWindow(id
);
142 if (tw
.isAvailable()) {
147 final ArrayList
<ToolWindow
> windows
= new ArrayList
<ToolWindow
>(ids
.keySet());
148 Collections
.sort(windows
, new ToolWindowComparator());
149 for (ToolWindow window
: windows
) {
150 twModel
.addElement(window
);
153 toolwindows
= new JList(twModel
);
154 toolwindows
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
155 toolwindows
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
156 toolwindows
.setCellRenderer(new ToolWindowsRenderer(ids
));
157 toolwindows
.addKeyListener(this);
158 toolwindows
.addMouseListener(this);
159 toolwindows
.addMouseMotionListener(this);
160 toolwindows
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
161 public void valueChanged(ListSelectionEvent e
) {
162 if (!toolwindows
.getSelectionModel().isSelectionEmpty()) {
163 files
.getSelectionModel().clearSelection();
168 separator
= new JPanel() {
170 protected void paintComponent(Graphics g
) {
171 super.paintComponent(g
);
172 g
.setColor(SEPARATOR_COLOR
);
173 g
.drawLine(0, 0, 0, getHeight());
177 public Dimension
getMaximumSize() {
178 final Dimension max
= super.getMaximumSize();
179 return new Dimension(5, max
.height
);
182 separator
.setBackground(Color
.WHITE
);
184 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
185 final VirtualFile
[] openFiles
= editorManager
.getOpenFiles();
188 Arrays
.sort(openFiles
, new RecentFilesComparator(project
));
189 } catch (Exception e
) {// IndexNotReadyException
192 final DefaultListModel filesModel
= new DefaultListModel();
193 for (VirtualFile openFile
: openFiles
) {
194 filesModel
.addElement(openFile
);
196 files
= new JList(filesModel
);
197 files
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
198 files
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
199 files
.setCellRenderer(new VirtualFilesRenderer(project
));
200 files
.addKeyListener(this);
201 files
.addMouseListener(this);
202 files
.addMouseMotionListener(this);
203 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
204 public void valueChanged(ListSelectionEvent e
) {
205 if (!files
.getSelectionModel().isSelectionEmpty()) {
206 toolwindows
.getSelectionModel().clearSelection();
211 this.add(toolwindows
, BorderLayout
.WEST
);
212 if (filesModel
.size() > 0) {
213 files
.setAlignmentY(1f
);
214 this.add(files
, BorderLayout
.EAST
);
215 this.add(separator
, BorderLayout
.CENTER
);
217 this.add(descriptions
, BorderLayout
.SOUTH
);
219 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
220 private String
getTitle2Text(String fullText
) {
221 int labelWidth
= pathLabel
.getWidth();
222 if (fullText
== null || fullText
.length() == 0) return " ";
223 while (pathLabel
.getFontMetrics(pathLabel
.getFont()).stringWidth(fullText
) > labelWidth
) {
224 int sep
= fullText
.indexOf(File
.separatorChar
, 4);
225 if (sep
< 0) return fullText
;
226 fullText
= "..." + fullText
.substring(sep
);
232 public void valueChanged(final ListSelectionEvent e
) {
233 SwingUtilities
.invokeLater(new Runnable() {
240 private void updatePathLabel() {
241 final Object
[] values
= files
.getSelectedValues();
242 if (values
!= null && values
.length
== 1) {
243 final VirtualFile parent
= ((VirtualFile
)values
[0]).getParent();
244 if (parent
!= null) {
245 pathLabel
.setText(getTitle2Text(parent
.getPresentableUrl()));
247 pathLabel
.setText(" ");
250 pathLabel
.setText(" ");
255 final int modifiers
= getModifiers(ToolWindowSwitcher
.this.getShortcutSet());
256 if ((modifiers
& Event
.ALT_MASK
) != 0) {
257 ALT_KEY
= KeyEvent
.VK_CONTROL
;
258 CTRL_KEY
= KeyEvent
.VK_ALT
;
260 ALT_KEY
= KeyEvent
.VK_ALT
;
261 CTRL_KEY
= KeyEvent
.VK_CONTROL
;
264 final IdeFrameImpl ideFrame
= WindowManagerEx
.getInstanceEx().getFrame(project
);
265 myPopup
= JBPopupFactory
.getInstance().createComponentPopupBuilder(this, this)
267 .setModalContext(false)
269 .setRequestFocus(true)
270 .setTitle(SWITCHER_TITLE
)
272 .setCancelCallback(new Computable
<Boolean
>() {
273 public Boolean
compute() {
275 if (focusComponent
!= null) {
276 focusComponent
.removeKeyListener(performanceProblemsSolver
);
277 focusComponent
= null;
282 myPopup
.showInCenterOf(ideFrame
.getContentPane());
285 private int getModifiers(ShortcutSet shortcutSet
) {
286 if (shortcutSet
== null
287 || shortcutSet
.getShortcuts().length
== 0
288 || !(shortcutSet
.getShortcuts()[0] instanceof KeyboardShortcut
)) return Event
.CTRL_MASK
;
289 return ((KeyboardShortcut
)shortcutSet
.getShortcuts()[0]).getFirstKeyStroke().getModifiers();
292 public void keyTyped(KeyEvent e
) {
295 public void keyReleased(KeyEvent e
) {
296 if (e
.getKeyCode() == CTRL_KEY
|| e
.getKeyCode() == KeyEvent
.VK_ENTER
) {
298 } else if (e
.getKeyCode() == KeyEvent
.VK_LEFT
) {
300 } else if (e
.getKeyCode() == KeyEvent
.VK_RIGHT
) {
305 public void keyPressed(KeyEvent e
) {
306 switch (e
.getKeyCode()) {
310 case KeyEvent
.VK_DOWN
:
313 case KeyEvent
.VK_ESCAPE
:
316 case KeyEvent
.VK_DELETE
:
317 case KeyEvent
.VK_BACK_SPACE
: // Mac users
318 closeTabOrToolWindow();
321 if (e
.getKeyCode() == ALT_KEY
) {
322 if (isFilesSelected()) {
330 private void closeTabOrToolWindow() {
331 final Object value
= getSelectedList().getSelectedValue();
332 if (value
instanceof VirtualFile
) {
333 final VirtualFile virtualFile
= (VirtualFile
)value
;
334 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
335 if (editorManager
instanceof FileEditorManagerImpl
) {
336 final JList jList
= getSelectedList();
337 ((FileEditorManagerImpl
)editorManager
).closeFile(virtualFile
, false);
338 final int selectedIndex
= jList
.getSelectedIndex();
339 if (jList
.getModel().getSize() == 1) {
341 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
343 this.remove(separator
);
346 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
347 jList
.setSize(jList
.getPreferredSize());
351 } else if (value
instanceof ToolWindow
) {
352 final ToolWindow toolWindow
= (ToolWindow
)value
;
353 if (twManager
instanceof ToolWindowManagerImpl
) {
354 ToolWindowManagerImpl manager
= (ToolWindowManagerImpl
)twManager
;
355 manager
.hideToolWindow(ids
.get(toolWindow
), false, false);
357 toolWindow
.hide(null);
362 private void pack() {
363 this.setSize(this.getPreferredSize());
364 final JRootPane rootPane
= SwingUtilities
.getRootPane(this);
365 Container container
= this;
367 container
= container
.getParent();
368 container
.setSize(container
.getPreferredSize());
369 } while (container
!= rootPane
);
370 container
.getParent().setSize(container
.getPreferredSize());
373 private boolean isFilesSelected() {
374 return getSelectedList() == files
;
377 private boolean isFilesVisible() {
378 return files
.getModel().getSize() > 0;
381 private boolean isToolWindowsSelected() {
382 return getSelectedList() == toolwindows
;
385 private void goRight() {
386 if (isFilesSelected() || !isFilesVisible()) {
390 if (files
.getModel().getSize() > 0) {
391 files
.setSelectedIndex(Math
.min(toolwindows
.getSelectedIndex(), files
.getModel().getSize() - 1));
392 toolwindows
.getSelectionModel().clearSelection();
397 private void cancel() {
401 private void goLeft() {
402 if (isToolWindowsSelected()) {
406 if (toolwindows
.getModel().getSize() > 0) {
407 toolwindows
.setSelectedIndex(Math
.min(files
.getSelectedIndex(), toolwindows
.getModel().getSize() - 1));
408 files
.getSelectionModel().clearSelection();
413 public void goForward() {
414 JList list
= getSelectedList();
415 int index
= list
.getSelectedIndex() + 1;
416 if (index
>= list
.getModel().getSize()) {
418 if (isFilesVisible()) {
419 list
= isFilesSelected() ? toolwindows
: files
;
422 list
.setSelectedIndex(index
);
425 public void goBack() {
426 JList list
= getSelectedList();
427 int index
= list
.getSelectedIndex() - 1;
429 if (isFilesVisible()) {
430 list
= isFilesSelected() ? toolwindows
: files
;
432 index
= list
.getModel().getSize() - 1;
434 list
.setSelectedIndex(index
);
437 public JList
getSelectedList() {
438 if (toolwindows
.isSelectionEmpty() && files
.isSelectionEmpty()) {
439 if (files
.getModel().getSize() > 1) {
440 files
.setSelectedIndex(0);
444 toolwindows
.setSelectedIndex(0);
449 return toolwindows
.isSelectionEmpty() ? files
: toolwindows
;
453 private void navigate() {
455 final Object value
= getSelectedList().getSelectedValue();
456 if (value
instanceof ToolWindow
) {
457 ((ToolWindow
)value
).activate(null, true, true);
459 else if (value
instanceof VirtualFile
) {
460 FileEditorManager
.getInstance(project
).openFile((VirtualFile
)value
, true);
464 private class RecentFilesComparator
implements Comparator
<VirtualFile
> {
465 private final VirtualFile
[] recentFiles
;
467 public RecentFilesComparator(Project project
) {
468 recentFiles
= EditorHistoryManager
.getInstance(project
).getFiles();
471 public int compare(VirtualFile vf1
, VirtualFile vf2
) {
472 return ArrayUtil
.find(recentFiles
, vf2
) - ArrayUtil
.find(recentFiles
, vf1
);
476 private class ToolWindowComparator
implements Comparator
<ToolWindow
> {
477 public int compare(ToolWindow o1
, ToolWindow o2
) {
478 return ids
.get(o1
).compareTo(ids
.get(o2
));
482 public void mouseClicked(MouseEvent e
) {
483 final Object source
= e
.getSource();
484 if (source
instanceof JList
) {
485 JList jList
= (JList
)source
;
486 if (jList
.getSelectedIndex() == -1 && jList
.getAnchorSelectionIndex() != -1) {
487 jList
.setSelectedIndex(jList
.getAnchorSelectionIndex());
489 if (jList
.getSelectedIndex() != -1) {
495 private boolean mouseMovedFirstTime
= true;
496 public void mouseMoved(MouseEvent e
) {
497 if (mouseMovedFirstTime
) {
498 mouseMovedFirstTime
= false;
502 final Object source
= e
.getSource();
503 if (source
instanceof JList
) {
504 JList list
= (JList
)source
;
505 int index
= list
.locationToIndex(e
.getPoint());
506 if (0 <= index
&& index
< list
.getModel().getSize()) {
507 list
.setSelectedIndex(index
);
512 public void mousePressed(MouseEvent e
) {}
513 public void mouseReleased(MouseEvent e
) {}
514 public void mouseEntered(MouseEvent e
) {}
515 public void mouseExited(MouseEvent e
) {}
516 public void mouseDragged(MouseEvent e
) {}
519 private static class VirtualFilesRenderer
extends ColoredListCellRenderer
{
520 private final Project myProject
;
522 public VirtualFilesRenderer(Project project
) {
526 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
527 if (value
instanceof VirtualFile
) {
528 final VirtualFile virtualFile
= (VirtualFile
)value
;
529 final String name
= virtualFile
.getPresentableName();
530 setIcon(IconUtil
.getIcon(virtualFile
, Iconable
.ICON_FLAG_READ_STATUS
, myProject
));
532 final FileStatus fileStatus
= FileStatusManager
.getInstance(myProject
).getStatus(virtualFile
);
533 final TextAttributes attributes
= new TextAttributes(fileStatus
.getColor(), null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
534 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
539 private static class ToolWindowsRenderer
extends ColoredListCellRenderer
{
540 private static final Map
<String
, Icon
> iconCache
= new HashMap
<String
, Icon
>();
541 private final Map
<ToolWindow
, String
> ids
;
543 public ToolWindowsRenderer(Map
<ToolWindow
, String
> ids
) {
547 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
548 if (value
instanceof ToolWindow
) {
549 final ToolWindow tw
= (ToolWindow
)value
;
550 final String name
= ids
.get(tw
);
551 setIcon(getIcon(tw
));
553 final TextAttributes attributes
= new TextAttributes(Color
.BLACK
, null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
554 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
558 private Icon
getIcon(ToolWindow toolWindow
) {
559 Icon icon
= iconCache
.get(ids
.get(toolWindow
));
560 if (icon
!= null) return icon
;
562 icon
= toolWindow
.getIcon();
564 return Icons
.UI_FORM_ICON
;
567 icon
= to16x16(icon
);
568 iconCache
.put(ids
.get(toolWindow
), icon
);
572 private static Icon
to16x16(Icon icon
) {
573 if (icon
.getIconHeight() == 16 && icon
.getIconWidth() == 16) return icon
;
574 final int w
= Math
.min (icon
.getIconWidth(), 16);
575 final int h
= Math
.min(icon
.getIconHeight(), 16);
577 final BufferedImage image
= GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
578 .createCompatibleImage(16, 16, Color
.TRANSLUCENT
);
579 final Graphics2D g
= image
.createGraphics();
580 icon
.paintIcon(null, g
, 0, 0);
583 final BufferedImage img
= new BufferedImage(16, 16, BufferedImage
.TRANSLUCENT
);
584 final int offX
= Math
.max((16 - w
) / 2, 0);
585 final int offY
= Math
.max((16 - h
) / 2, 0);
586 for (int col
= 0; col
< w
; col
++) {
587 for (int row
= 0; row
< h
; row
++) {
588 img
.setRGB(col
+ offX
, row
+ offY
, image
.getRGB(col
, row
));
592 return new ImageIcon(img
);