1 package com
.intellij
.ide
.actions
;
3 import com
.intellij
.featureStatistics
.FeatureUsageTracker
;
4 import com
.intellij
.openapi
.actionSystem
.*;
5 import com
.intellij
.openapi
.editor
.markup
.EffectType
;
6 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
7 import com
.intellij
.openapi
.fileEditor
.FileEditorManager
;
8 import com
.intellij
.openapi
.fileEditor
.impl
.EditorHistoryManager
;
9 import com
.intellij
.openapi
.fileEditor
.impl
.FileEditorManagerImpl
;
10 import com
.intellij
.openapi
.project
.DumbAware
;
11 import com
.intellij
.openapi
.project
.Project
;
12 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
13 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
14 import com
.intellij
.openapi
.util
.Computable
;
15 import com
.intellij
.openapi
.util
.Iconable
;
16 import com
.intellij
.openapi
.vcs
.FileStatus
;
17 import com
.intellij
.openapi
.vcs
.FileStatusManager
;
18 import com
.intellij
.openapi
.vfs
.VirtualFile
;
19 import com
.intellij
.openapi
.wm
.ToolWindow
;
20 import com
.intellij
.openapi
.wm
.ToolWindowManager
;
21 import com
.intellij
.openapi
.wm
.ex
.WindowManagerEx
;
22 import com
.intellij
.openapi
.wm
.impl
.IdeFrameImpl
;
23 import com
.intellij
.openapi
.wm
.impl
.ToolWindowManagerImpl
;
24 import com
.intellij
.ui
.ColoredListCellRenderer
;
25 import com
.intellij
.ui
.IdeBorderFactory
;
26 import com
.intellij
.ui
.SimpleTextAttributes
;
27 import com
.intellij
.util
.ArrayUtil
;
28 import com
.intellij
.util
.IconUtil
;
29 import com
.intellij
.util
.Icons
;
30 import org
.jetbrains
.annotations
.NonNls
;
33 import javax
.swing
.FocusManager
;
34 import javax
.swing
.border
.EmptyBorder
;
35 import javax
.swing
.event
.ListSelectionEvent
;
36 import javax
.swing
.event
.ListSelectionListener
;
38 import java
.awt
.event
.*;
39 import java
.awt
.image
.BufferedImage
;
44 * @author Konstantin Bulenkov
46 public class ToolWindowSwitcher
extends AnAction
implements DumbAware
{
47 private static volatile ToolWindowSwitcherPanel SWITCHER
= null;
48 private static final Color BORDER_COLOR
= new Color(0x87, 0x87, 0x87);
49 private static final Color SEPARATOR_COLOR
= BORDER_COLOR
.brighter();
50 @NonNls private static final String SWITCHER_FEATURE_ID
= "switcher";
52 private static final KeyListener performanceProblemsSolver
= new KeyAdapter() { //IDEA-24436
54 public void keyReleased(KeyEvent e
) {
55 if (e
.getKeyCode() == KeyEvent
.VK_CONTROL
) {
56 synchronized (ToolWindowSwitcher
.class) {
57 if (ToolWindowSwitcher
.SWITCHER
!= null) {
58 ToolWindowSwitcher
.SWITCHER
.navigate();
65 private static Component focusComponent
= null;
66 @NonNls private static final String SWITCHER_TITLE
= "Switcher";
68 public void actionPerformed(AnActionEvent e
) {
69 final Project project
= PlatformDataKeys
.PROJECT
.getData(e
.getDataContext());
70 if (project
== null) return;
71 if (SWITCHER
== null) {
72 focusComponent
= FocusManager
.getCurrentManager().getFocusOwner();
73 if (focusComponent
!= null) {
74 focusComponent
.addKeyListener(performanceProblemsSolver
);
76 SWITCHER
= new ToolWindowSwitcherPanel(project
);
77 FeatureUsageTracker
.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID
);
80 if (e
.getInputEvent().isShiftDown()) {
87 private class ToolWindowSwitcherPanel
extends JPanel
implements KeyListener
, MouseListener
, MouseMotionListener
{
88 final JBPopup myPopup
;
89 final Map
<ToolWindow
, String
> ids
= new HashMap
<ToolWindow
, String
>();
90 final JList toolwindows
;
92 final JPanel separator
;
93 final ToolWindowManager twManager
;
94 final JLabel pathLabel
= new JLabel(" ");
95 final JPanel descriptions
;
96 final Project project
;
100 ToolWindowSwitcherPanel(Project project
) {
101 super(new BorderLayout(0, 0));
102 this.project
= project
;
104 addKeyListener(this);
105 setBorder(new EmptyBorder(0, 0, 0, 0));
106 setBackground(Color
.WHITE
);
107 pathLabel
.setHorizontalAlignment(SwingConstants
.RIGHT
);
109 final Font font
= pathLabel
.getFont();
110 pathLabel
.setFont(font
.deriveFont((float)10));
112 descriptions
= new JPanel(new BorderLayout()) {
114 protected void paintComponent(Graphics g
) {
115 super.paintComponent(g
);
116 g
.setColor(BORDER_COLOR
);
117 g
.drawLine(0, 0, getWidth(), 0);
121 descriptions
.setBorder(BorderFactory
.createEmptyBorder(1, 4, 1, 4));
122 descriptions
.add(pathLabel
);
123 twManager
= ToolWindowManager
.getInstance(project
);
124 final DefaultListModel twModel
= new DefaultListModel();
125 for (String id
: twManager
.getToolWindowIds()) {
126 final ToolWindow tw
= twManager
.getToolWindow(id
);
127 if (tw
.isAvailable()) {
132 final ArrayList
<ToolWindow
> windows
= new ArrayList
<ToolWindow
>(ids
.keySet());
133 Collections
.sort(windows
, new ToolWindowComparator());
134 for (ToolWindow window
: windows
) {
135 twModel
.addElement(window
);
138 toolwindows
= new JList(twModel
);
139 toolwindows
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
140 toolwindows
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
141 toolwindows
.setCellRenderer(new ToolWindowsRenderer(ids
));
142 toolwindows
.addKeyListener(this);
143 toolwindows
.addMouseListener(this);
144 toolwindows
.addMouseMotionListener(this);
145 toolwindows
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
146 public void valueChanged(ListSelectionEvent e
) {
147 if (!toolwindows
.getSelectionModel().isSelectionEmpty()) {
148 files
.getSelectionModel().clearSelection();
153 separator
= new JPanel() {
155 protected void paintComponent(Graphics g
) {
156 super.paintComponent(g
);
157 g
.setColor(SEPARATOR_COLOR
);
158 g
.drawLine(0, 0, 0, getHeight());
162 public Dimension
getMaximumSize() {
163 final Dimension max
= super.getMaximumSize();
164 return new Dimension(5, max
.height
);
167 separator
.setBackground(Color
.WHITE
);
169 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
170 final VirtualFile
[] openFiles
= editorManager
.getOpenFiles();
173 Arrays
.sort(openFiles
, new RecentFilesComparator(project
));
174 } catch (Exception e
) {// IndexNotReadyException
177 final DefaultListModel filesModel
= new DefaultListModel();
178 for (VirtualFile openFile
: openFiles
) {
179 filesModel
.addElement(openFile
);
181 files
= new JList(filesModel
);
182 files
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
183 files
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
184 files
.setCellRenderer(new VirtualFilesRenderer(project
));
185 files
.addKeyListener(this);
186 files
.addMouseListener(this);
187 files
.addMouseMotionListener(this);
188 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
189 public void valueChanged(ListSelectionEvent e
) {
190 if (!files
.getSelectionModel().isSelectionEmpty()) {
191 toolwindows
.getSelectionModel().clearSelection();
196 this.add(toolwindows
, BorderLayout
.WEST
);
197 if (filesModel
.size() > 0) {
198 files
.setAlignmentY(1f
);
199 this.add(files
, BorderLayout
.EAST
);
200 this.add(separator
, BorderLayout
.CENTER
);
202 this.add(descriptions
, BorderLayout
.SOUTH
);
204 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
205 private String
getTitle2Text(String fullText
) {
206 int labelWidth
= pathLabel
.getWidth();
207 if (fullText
== null || fullText
.length() == 0) return " ";
208 while (pathLabel
.getFontMetrics(pathLabel
.getFont()).stringWidth(fullText
) > labelWidth
) {
209 int sep
= fullText
.indexOf(File
.separatorChar
, 4);
210 if (sep
< 0) return fullText
;
211 fullText
= "..." + fullText
.substring(sep
);
217 public void valueChanged(final ListSelectionEvent e
) {
218 SwingUtilities
.invokeLater(new Runnable() {
225 private void updatePathLabel() {
226 final Object
[] values
= files
.getSelectedValues();
227 if (values
!= null && values
.length
== 1) {
228 final VirtualFile parent
= ((VirtualFile
)values
[0]).getParent();
229 if (parent
!= null) {
230 pathLabel
.setText(getTitle2Text(parent
.getPresentableUrl()));
232 pathLabel
.setText(" ");
235 pathLabel
.setText(" ");
240 final int modifiers
= getModifiers(ToolWindowSwitcher
.this.getShortcutSet());
241 if ((modifiers
& Event
.ALT_MASK
) != 0) {
242 ALT_KEY
= KeyEvent
.VK_CONTROL
;
243 CTRL_KEY
= KeyEvent
.VK_ALT
;
245 ALT_KEY
= KeyEvent
.VK_ALT
;
246 CTRL_KEY
= KeyEvent
.VK_CONTROL
;
249 final IdeFrameImpl ideFrame
= WindowManagerEx
.getInstanceEx().getFrame(project
);
250 myPopup
= JBPopupFactory
.getInstance().createComponentPopupBuilder(this, this)
252 .setModalContext(false)
254 .setRequestFocus(true)
255 .setTitle(SWITCHER_TITLE
)
257 .setCancelCallback(new Computable
<Boolean
>() {
258 public Boolean
compute() {
260 if (focusComponent
!= null) {
261 focusComponent
.removeKeyListener(performanceProblemsSolver
);
262 focusComponent
= null;
267 myPopup
.showInCenterOf(ideFrame
.getContentPane());
270 private int getModifiers(ShortcutSet shortcutSet
) {
271 if (shortcutSet
== null
272 || shortcutSet
.getShortcuts().length
== 0
273 || !(shortcutSet
.getShortcuts()[0] instanceof KeyboardShortcut
)) return Event
.CTRL_MASK
;
274 return ((KeyboardShortcut
)shortcutSet
.getShortcuts()[0]).getFirstKeyStroke().getModifiers();
277 public void keyTyped(KeyEvent e
) {
280 public void keyReleased(KeyEvent e
) {
281 if (e
.getKeyCode() == CTRL_KEY
|| e
.getKeyCode() == KeyEvent
.VK_ENTER
) {
283 } else if (e
.getKeyCode() == KeyEvent
.VK_LEFT
) {
285 } else if (e
.getKeyCode() == KeyEvent
.VK_RIGHT
) {
290 public void keyPressed(KeyEvent e
) {
291 switch (e
.getKeyCode()) {
295 case KeyEvent
.VK_DOWN
:
298 case KeyEvent
.VK_ESCAPE
:
301 case KeyEvent
.VK_DELETE
:
302 case KeyEvent
.VK_BACK_SPACE
: // Mac users
303 closeTabOrToolWindow();
306 if (e
.getKeyCode() == ALT_KEY
) {
307 if (isFilesSelected()) {
315 private void closeTabOrToolWindow() {
316 final Object value
= getSelectedList().getSelectedValue();
317 if (value
instanceof VirtualFile
) {
318 final VirtualFile virtualFile
= (VirtualFile
)value
;
319 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
320 if (editorManager
instanceof FileEditorManagerImpl
) {
321 final JList jList
= getSelectedList();
322 ((FileEditorManagerImpl
)editorManager
).closeFile(virtualFile
, false);
323 final int selectedIndex
= jList
.getSelectedIndex();
324 if (jList
.getModel().getSize() == 1) {
326 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
328 this.remove(separator
);
331 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
332 jList
.setSize(jList
.getPreferredSize());
336 } else if (value
instanceof ToolWindow
) {
337 final ToolWindow toolWindow
= (ToolWindow
)value
;
338 if (twManager
instanceof ToolWindowManagerImpl
) {
339 ToolWindowManagerImpl manager
= (ToolWindowManagerImpl
)twManager
;
340 manager
.hideToolWindow(ids
.get(toolWindow
), false, false);
342 toolWindow
.hide(null);
347 private void pack() {
348 this.setSize(this.getPreferredSize());
349 final JRootPane rootPane
= SwingUtilities
.getRootPane(this);
350 Container container
= this;
352 container
= container
.getParent();
353 container
.setSize(container
.getPreferredSize());
354 } while (container
!= rootPane
);
355 container
.getParent().setSize(container
.getPreferredSize());
358 private boolean isFilesSelected() {
359 return getSelectedList() == files
;
362 private boolean isFilesVisible() {
363 return files
.getModel().getSize() > 0;
366 private boolean isToolWindowsSelected() {
367 return getSelectedList() == toolwindows
;
370 private void goRight() {
371 if (isFilesSelected() || !isFilesVisible()) {
375 if (files
.getModel().getSize() > 0) {
376 files
.setSelectedIndex(Math
.min(toolwindows
.getSelectedIndex(), files
.getModel().getSize() - 1));
377 toolwindows
.getSelectionModel().clearSelection();
382 private void cancel() {
386 private void goLeft() {
387 if (isToolWindowsSelected()) {
391 if (toolwindows
.getModel().getSize() > 0) {
392 toolwindows
.setSelectedIndex(Math
.min(files
.getSelectedIndex(), toolwindows
.getModel().getSize() - 1));
393 files
.getSelectionModel().clearSelection();
398 public void goForward() {
399 JList list
= getSelectedList();
400 int index
= list
.getSelectedIndex() + 1;
401 if (index
>= list
.getModel().getSize()) {
403 if (isFilesVisible()) {
404 list
= isFilesSelected() ? toolwindows
: files
;
407 list
.setSelectedIndex(index
);
410 public void goBack() {
411 JList list
= getSelectedList();
412 int index
= list
.getSelectedIndex() - 1;
414 if (isFilesVisible()) {
415 list
= isFilesSelected() ? toolwindows
: files
;
417 index
= list
.getModel().getSize() - 1;
419 list
.setSelectedIndex(index
);
422 public JList
getSelectedList() {
423 if (toolwindows
.isSelectionEmpty() && files
.isSelectionEmpty()) {
424 if (files
.getModel().getSize() > 1) {
425 files
.setSelectedIndex(0);
429 toolwindows
.setSelectedIndex(0);
434 return toolwindows
.isSelectionEmpty() ? files
: toolwindows
;
438 private void navigate() {
440 final Object value
= getSelectedList().getSelectedValue();
441 if (value
instanceof ToolWindow
) {
442 ((ToolWindow
)value
).activate(null, true, true);
444 else if (value
instanceof VirtualFile
) {
445 FileEditorManager
.getInstance(project
).openFile((VirtualFile
)value
, true);
449 private class RecentFilesComparator
implements Comparator
<VirtualFile
> {
450 private final VirtualFile
[] recentFiles
;
452 public RecentFilesComparator(Project project
) {
453 recentFiles
= EditorHistoryManager
.getInstance(project
).getFiles();
456 public int compare(VirtualFile vf1
, VirtualFile vf2
) {
457 return ArrayUtil
.find(recentFiles
, vf2
) - ArrayUtil
.find(recentFiles
, vf1
);
461 private class ToolWindowComparator
implements Comparator
<ToolWindow
> {
462 public int compare(ToolWindow o1
, ToolWindow o2
) {
463 return ids
.get(o1
).compareTo(ids
.get(o2
));
467 public void mouseClicked(MouseEvent e
) {
468 final Object source
= e
.getSource();
469 if (source
instanceof JList
) {
470 JList jList
= (JList
)source
;
471 if (jList
.getSelectedIndex() == -1 && jList
.getAnchorSelectionIndex() != -1) {
472 jList
.setSelectedIndex(jList
.getAnchorSelectionIndex());
474 if (jList
.getSelectedIndex() != -1) {
480 private boolean mouseMovedFirstTime
= true;
481 public void mouseMoved(MouseEvent e
) {
482 if (mouseMovedFirstTime
) {
483 mouseMovedFirstTime
= false;
487 final Object source
= e
.getSource();
488 if (source
instanceof JList
) {
489 JList list
= (JList
)source
;
490 int index
= list
.locationToIndex(e
.getPoint());
491 if (0 <= index
&& index
< list
.getModel().getSize()) {
492 list
.setSelectedIndex(index
);
497 public void mousePressed(MouseEvent e
) {}
498 public void mouseReleased(MouseEvent e
) {}
499 public void mouseEntered(MouseEvent e
) {}
500 public void mouseExited(MouseEvent e
) {}
501 public void mouseDragged(MouseEvent e
) {}
504 private static class VirtualFilesRenderer
extends ColoredListCellRenderer
{
505 private final Project myProject
;
507 public VirtualFilesRenderer(Project project
) {
511 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
512 if (value
instanceof VirtualFile
) {
513 final VirtualFile virtualFile
= (VirtualFile
)value
;
514 final String name
= virtualFile
.getPresentableName();
515 setIcon(IconUtil
.getIcon(virtualFile
, Iconable
.ICON_FLAG_READ_STATUS
, myProject
));
517 final FileStatus fileStatus
= FileStatusManager
.getInstance(myProject
).getStatus(virtualFile
);
518 final TextAttributes attributes
= new TextAttributes(fileStatus
.getColor(), null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
519 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
524 private static class ToolWindowsRenderer
extends ColoredListCellRenderer
{
525 private static final Map
<String
, Icon
> iconCache
= new HashMap
<String
, Icon
>();
526 private final Map
<ToolWindow
, String
> ids
;
528 public ToolWindowsRenderer(Map
<ToolWindow
, String
> ids
) {
532 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
533 if (value
instanceof ToolWindow
) {
534 final ToolWindow tw
= (ToolWindow
)value
;
535 final String name
= ids
.get(tw
);
536 setIcon(getIcon(tw
));
538 final TextAttributes attributes
= new TextAttributes(Color
.BLACK
, null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
539 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
543 private Icon
getIcon(ToolWindow toolWindow
) {
544 Icon icon
= iconCache
.get(ids
.get(toolWindow
));
545 if (icon
!= null) return icon
;
547 icon
= toolWindow
.getIcon();
549 return Icons
.UI_FORM_ICON
;
552 icon
= to16x16(icon
);
553 iconCache
.put(ids
.get(toolWindow
), icon
);
557 private static Icon
to16x16(Icon icon
) {
558 if (icon
.getIconHeight() == 16 && icon
.getIconWidth() == 16) return icon
;
559 final int w
= icon
.getIconWidth();
560 final int h
= icon
.getIconHeight();
562 final BufferedImage image
= GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
563 .createCompatibleImage(16, 16, Color
.TRANSLUCENT
);
564 final Graphics2D g
= image
.createGraphics();
565 icon
.paintIcon(null, g
, 0, 0);
568 final BufferedImage img
= new BufferedImage(16, 16, BufferedImage
.TRANSLUCENT
);
569 final int offX
= Math
.max((16 - w
) / 2, 0);
570 final int offY
= Math
.max((16 - h
) / 2, 0);
571 for (int col
= 0; col
< w
; col
++) {
572 for (int row
= 0; row
< h
; row
++) {
573 img
.setRGB(col
+ offX
, row
+ offY
, image
.getRGB(col
, row
));
577 return new ImageIcon(img
);