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
.border
.EmptyBorder
;
34 import javax
.swing
.event
.ListSelectionEvent
;
35 import javax
.swing
.event
.ListSelectionListener
;
37 import java
.awt
.event
.*;
38 import java
.awt
.image
.BufferedImage
;
43 * @author Konstantin Bulenkov
45 public class ToolWindowSwitcher
extends AnAction
implements DumbAware
{
46 private static volatile ToolWindowSwitcherPanel SWITCHER
= null;
47 private static final Color BORDER_COLOR
= new Color(0x87, 0x87, 0x87);
48 private static final Color SEPARATOR_COLOR
= BORDER_COLOR
.brighter();
49 @NonNls private static final String SWITCHER_FEATURE_ID
= "switcher";
51 private static final KeyListener performanceProblemsSolver
= new KeyAdapter() { //IDEA-24436
53 public void keyReleased(KeyEvent e
) {
54 if (e
.getKeyCode() == KeyEvent
.VK_CONTROL
) {
55 synchronized (ToolWindowSwitcher
.class) {
56 if (ToolWindowSwitcher
.SWITCHER
!= null) {
57 ToolWindowSwitcher
.SWITCHER
.navigate();
64 private static Component focusComponent
= null;
65 @NonNls private static final String SWITCHER_TITLE
= "Switcher";
67 public void actionPerformed(AnActionEvent e
) {
68 final Project project
= PlatformDataKeys
.PROJECT
.getData(e
.getDataContext());
69 if (project
== null) return;
70 if (SWITCHER
== null) {
71 SWITCHER
= new ToolWindowSwitcherPanel(project
);
72 focusComponent
= FocusManager
.getCurrentManager().getFocusOwner();
73 focusComponent
.addKeyListener(performanceProblemsSolver
);
74 FeatureUsageTracker
.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID
);
77 if (e
.getInputEvent().isShiftDown()) {
84 private class ToolWindowSwitcherPanel
extends JPanel
implements KeyListener
, MouseListener
, MouseMotionListener
{
85 final JBPopup myPopup
;
86 final Map
<ToolWindow
, String
> ids
= new HashMap
<ToolWindow
, String
>();
87 final JList toolwindows
;
89 final JPanel separator
;
90 final ToolWindowManager twManager
;
91 final JLabel pathLabel
= new JLabel(" ");
92 final JPanel descriptions
;
93 final Project project
;
97 ToolWindowSwitcherPanel(Project project
) {
98 super(new BorderLayout(0, 0));
99 this.project
= project
;
101 addKeyListener(this);
102 setBorder(new EmptyBorder(0, 0, 0, 0));
103 setBackground(Color
.WHITE
);
104 pathLabel
.setHorizontalAlignment(SwingConstants
.RIGHT
);
106 final Font font
= pathLabel
.getFont();
107 pathLabel
.setFont(font
.deriveFont((float)10));
109 descriptions
= new JPanel(new BorderLayout()) {
111 protected void paintComponent(Graphics g
) {
112 super.paintComponent(g
);
113 g
.setColor(BORDER_COLOR
);
114 g
.drawLine(0, 0, getWidth(), 0);
118 descriptions
.setBorder(BorderFactory
.createEmptyBorder(1, 4, 1, 4));
119 descriptions
.add(pathLabel
);
120 twManager
= ToolWindowManager
.getInstance(project
);
121 final DefaultListModel twModel
= new DefaultListModel();
122 for (String id
: twManager
.getToolWindowIds()) {
123 final ToolWindow tw
= twManager
.getToolWindow(id
);
124 if (tw
.isAvailable()) {
129 final ArrayList
<ToolWindow
> windows
= new ArrayList
<ToolWindow
>(ids
.keySet());
130 Collections
.sort(windows
, new ToolWindowComparator());
131 for (ToolWindow window
: windows
) {
132 twModel
.addElement(window
);
135 toolwindows
= new JList(twModel
);
136 toolwindows
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
137 toolwindows
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
138 toolwindows
.setCellRenderer(new ToolWindowsRenderer(ids
));
139 toolwindows
.addKeyListener(this);
140 toolwindows
.addMouseListener(this);
141 toolwindows
.addMouseMotionListener(this);
142 toolwindows
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
143 public void valueChanged(ListSelectionEvent e
) {
144 if (!toolwindows
.getSelectionModel().isSelectionEmpty()) {
145 files
.getSelectionModel().clearSelection();
150 separator
= new JPanel() {
152 protected void paintComponent(Graphics g
) {
153 super.paintComponent(g
);
154 g
.setColor(SEPARATOR_COLOR
);
155 g
.drawLine(0, 0, 0, getHeight());
159 public Dimension
getMaximumSize() {
160 final Dimension max
= super.getMaximumSize();
161 return new Dimension(5, max
.height
);
164 separator
.setBackground(Color
.WHITE
);
166 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
167 final VirtualFile
[] openFiles
= editorManager
.getOpenFiles();
170 Arrays
.sort(openFiles
, new RecentFilesComparator(project
));
171 } catch (Exception e
) {// IndexNotReadyException
174 final DefaultListModel filesModel
= new DefaultListModel();
175 for (VirtualFile openFile
: openFiles
) {
176 filesModel
.addElement(openFile
);
178 files
= new JList(filesModel
);
179 files
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
180 files
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
181 files
.setCellRenderer(new VirtualFilesRenderer(project
));
182 files
.addKeyListener(this);
183 files
.addMouseListener(this);
184 files
.addMouseMotionListener(this);
185 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
186 public void valueChanged(ListSelectionEvent e
) {
187 if (!files
.getSelectionModel().isSelectionEmpty()) {
188 toolwindows
.getSelectionModel().clearSelection();
193 this.add(toolwindows
, BorderLayout
.WEST
);
194 if (filesModel
.size() > 0) {
195 files
.setAlignmentY(1f
);
196 this.add(files
, BorderLayout
.EAST
);
197 this.add(separator
, BorderLayout
.CENTER
);
199 this.add(descriptions
, BorderLayout
.SOUTH
);
201 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
202 private String
getTitle2Text(String fullText
) {
203 int labelWidth
= pathLabel
.getWidth();
204 if (fullText
== null || fullText
.length() == 0) return " ";
205 while (pathLabel
.getFontMetrics(pathLabel
.getFont()).stringWidth(fullText
) > labelWidth
) {
206 int sep
= fullText
.indexOf(File
.separatorChar
, 4);
207 if (sep
< 0) return fullText
;
208 fullText
= "..." + fullText
.substring(sep
);
214 public void valueChanged(final ListSelectionEvent e
) {
215 SwingUtilities
.invokeLater(new Runnable() {
222 private void updatePathLabel() {
223 final Object
[] values
= files
.getSelectedValues();
224 if (values
!= null && values
.length
== 1) {
225 final VirtualFile parent
= ((VirtualFile
)values
[0]).getParent();
226 if (parent
!= null) {
227 pathLabel
.setText(getTitle2Text(parent
.getPresentableUrl()));
229 pathLabel
.setText(" ");
232 pathLabel
.setText(" ");
237 final int modifiers
= getModifiers(ToolWindowSwitcher
.this.getShortcutSet());
238 if ((modifiers
& Event
.ALT_MASK
) != 0) {
239 ALT_KEY
= KeyEvent
.VK_CONTROL
;
240 CTRL_KEY
= KeyEvent
.VK_ALT
;
242 ALT_KEY
= KeyEvent
.VK_ALT
;
243 CTRL_KEY
= KeyEvent
.VK_CONTROL
;
246 final IdeFrameImpl ideFrame
= WindowManagerEx
.getInstanceEx().getFrame(project
);
247 myPopup
= JBPopupFactory
.getInstance().createComponentPopupBuilder(this, this)
249 .setModalContext(false)
251 .setRequestFocus(true)
252 .setTitle(SWITCHER_TITLE
)
254 .setCancelCallback(new Computable
<Boolean
>() {
255 public Boolean
compute() {
257 if (focusComponent
!= null) {
258 focusComponent
.removeKeyListener(performanceProblemsSolver
);
259 focusComponent
= null;
264 myPopup
.showInCenterOf(ideFrame
.getContentPane());
267 private int getModifiers(ShortcutSet shortcutSet
) {
268 if (shortcutSet
== null
269 || shortcutSet
.getShortcuts().length
== 0
270 || !(shortcutSet
.getShortcuts()[0] instanceof KeyboardShortcut
)) return Event
.CTRL_MASK
;
271 return ((KeyboardShortcut
)shortcutSet
.getShortcuts()[0]).getFirstKeyStroke().getModifiers();
274 public void keyTyped(KeyEvent e
) {
277 public void keyReleased(KeyEvent e
) {
278 if (e
.getKeyCode() == CTRL_KEY
|| e
.getKeyCode() == KeyEvent
.VK_ENTER
) {
280 } else if (e
.getKeyCode() == KeyEvent
.VK_LEFT
) {
282 } else if (e
.getKeyCode() == KeyEvent
.VK_RIGHT
) {
287 public void keyPressed(KeyEvent e
) {
288 switch (e
.getKeyCode()) {
292 case KeyEvent
.VK_DOWN
:
295 case KeyEvent
.VK_ESCAPE
:
298 case KeyEvent
.VK_DELETE
:
299 case KeyEvent
.VK_BACK_SPACE
: // Mac users
300 closeTabOrToolWindow();
303 if (e
.getKeyCode() == ALT_KEY
) {
304 if (isFilesSelected()) {
312 private void closeTabOrToolWindow() {
313 final Object value
= getSelectedList().getSelectedValue();
314 if (value
instanceof VirtualFile
) {
315 final VirtualFile virtualFile
= (VirtualFile
)value
;
316 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
317 if (editorManager
instanceof FileEditorManagerImpl
) {
318 final JList jList
= getSelectedList();
319 ((FileEditorManagerImpl
)editorManager
).closeFile(virtualFile
, false);
320 final int selectedIndex
= jList
.getSelectedIndex();
321 if (jList
.getModel().getSize() == 1) {
323 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
325 this.remove(separator
);
328 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
329 jList
.setSize(jList
.getPreferredSize());
333 } else if (value
instanceof ToolWindow
) {
334 final ToolWindow toolWindow
= (ToolWindow
)value
;
335 if (twManager
instanceof ToolWindowManagerImpl
) {
336 ToolWindowManagerImpl manager
= (ToolWindowManagerImpl
)twManager
;
337 manager
.hideToolWindow(ids
.get(toolWindow
), false, false);
339 toolWindow
.hide(null);
344 private void pack() {
345 this.setSize(this.getPreferredSize());
346 final JRootPane rootPane
= SwingUtilities
.getRootPane(this);
347 Container container
= this;
349 container
= container
.getParent();
350 container
.setSize(container
.getPreferredSize());
351 } while (container
!= rootPane
);
352 container
.getParent().setSize(container
.getPreferredSize());
355 private boolean isFilesSelected() {
356 return getSelectedList() == files
;
359 private boolean isFilesVisible() {
360 return files
.getModel().getSize() > 0;
363 private boolean isToolWindowsSelected() {
364 return getSelectedList() == toolwindows
;
367 private void goRight() {
368 if (isFilesSelected() || !isFilesVisible()) {
372 if (files
.getModel().getSize() > 0) {
373 files
.setSelectedIndex(Math
.min(toolwindows
.getSelectedIndex(), files
.getModel().getSize() - 1));
374 toolwindows
.getSelectionModel().clearSelection();
379 private void cancel() {
383 private void goLeft() {
384 if (isToolWindowsSelected()) {
388 if (toolwindows
.getModel().getSize() > 0) {
389 toolwindows
.setSelectedIndex(Math
.min(files
.getSelectedIndex(), toolwindows
.getModel().getSize() - 1));
390 files
.getSelectionModel().clearSelection();
395 public void goForward() {
396 JList list
= getSelectedList();
397 int index
= list
.getSelectedIndex() + 1;
398 if (index
>= list
.getModel().getSize()) {
400 if (isFilesVisible()) {
401 list
= isFilesSelected() ? toolwindows
: files
;
404 list
.setSelectedIndex(index
);
407 public void goBack() {
408 JList list
= getSelectedList();
409 int index
= list
.getSelectedIndex() - 1;
411 if (isFilesVisible()) {
412 list
= isFilesSelected() ? toolwindows
: files
;
414 index
= list
.getModel().getSize() - 1;
416 list
.setSelectedIndex(index
);
419 public JList
getSelectedList() {
420 if (toolwindows
.isSelectionEmpty() && files
.isSelectionEmpty()) {
421 if (files
.getModel().getSize() > 1) {
422 files
.setSelectedIndex(0);
426 toolwindows
.setSelectedIndex(0);
431 return toolwindows
.isSelectionEmpty() ? files
: toolwindows
;
435 private void navigate() {
437 final Object value
= getSelectedList().getSelectedValue();
438 if (value
instanceof ToolWindow
) {
439 ((ToolWindow
)value
).activate(null, true, true);
441 else if (value
instanceof VirtualFile
) {
442 FileEditorManager
.getInstance(project
).openFile((VirtualFile
)value
, true);
446 private class RecentFilesComparator
implements Comparator
<VirtualFile
> {
447 private final VirtualFile
[] recentFiles
;
449 public RecentFilesComparator(Project project
) {
450 recentFiles
= EditorHistoryManager
.getInstance(project
).getFiles();
453 public int compare(VirtualFile vf1
, VirtualFile vf2
) {
454 return ArrayUtil
.find(recentFiles
, vf2
) - ArrayUtil
.find(recentFiles
, vf1
);
458 private class ToolWindowComparator
implements Comparator
<ToolWindow
> {
459 public int compare(ToolWindow o1
, ToolWindow o2
) {
460 return ids
.get(o1
).compareTo(ids
.get(o2
));
464 public void mouseClicked(MouseEvent e
) {
465 final Object source
= e
.getSource();
466 if (source
instanceof JList
) {
467 JList jList
= (JList
)source
;
468 if (jList
.getSelectedIndex() == -1 && jList
.getAnchorSelectionIndex() != -1) {
469 jList
.setSelectedIndex(jList
.getAnchorSelectionIndex());
471 if (jList
.getSelectedIndex() != -1) {
477 private boolean mouseMovedFirstTime
= true;
478 public void mouseMoved(MouseEvent e
) {
479 if (mouseMovedFirstTime
) {
480 mouseMovedFirstTime
= false;
484 final Object source
= e
.getSource();
485 if (source
instanceof JList
) {
486 JList list
= (JList
)source
;
487 int index
= list
.locationToIndex(e
.getPoint());
488 if (0 <= index
&& index
< list
.getModel().getSize()) {
489 list
.setSelectedIndex(index
);
494 public void mousePressed(MouseEvent e
) {}
495 public void mouseReleased(MouseEvent e
) {}
496 public void mouseEntered(MouseEvent e
) {}
497 public void mouseExited(MouseEvent e
) {}
498 public void mouseDragged(MouseEvent e
) {}
501 private static class VirtualFilesRenderer
extends ColoredListCellRenderer
{
502 private final Project myProject
;
504 public VirtualFilesRenderer(Project project
) {
508 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
509 if (value
instanceof VirtualFile
) {
510 final VirtualFile virtualFile
= (VirtualFile
)value
;
511 final String name
= virtualFile
.getPresentableName();
512 setIcon(IconUtil
.getIcon(virtualFile
, Iconable
.ICON_FLAG_READ_STATUS
, myProject
));
514 final FileStatus fileStatus
= FileStatusManager
.getInstance(myProject
).getStatus(virtualFile
);
515 final TextAttributes attributes
= new TextAttributes(fileStatus
.getColor(), null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
516 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
521 private static class ToolWindowsRenderer
extends ColoredListCellRenderer
{
522 private static final Map
<String
, Icon
> iconCache
= new HashMap
<String
, Icon
>();
523 private final Map
<ToolWindow
, String
> ids
;
525 public ToolWindowsRenderer(Map
<ToolWindow
, String
> ids
) {
529 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
530 if (value
instanceof ToolWindow
) {
531 final ToolWindow tw
= (ToolWindow
)value
;
532 final String name
= ids
.get(tw
);
533 setIcon(getIcon(tw
));
535 final TextAttributes attributes
= new TextAttributes(Color
.BLACK
, null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
536 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
540 private Icon
getIcon(ToolWindow toolWindow
) {
541 Icon icon
= iconCache
.get(ids
.get(toolWindow
));
542 if (icon
!= null) return icon
;
544 icon
= toolWindow
.getIcon();
546 return Icons
.UI_FORM_ICON
;
549 icon
= to16x16(icon
);
550 iconCache
.put(ids
.get(toolWindow
), icon
);
554 private static Icon
to16x16(Icon icon
) {
555 if (icon
.getIconHeight() == 16 && icon
.getIconWidth() == 16) return icon
;
556 final int w
= icon
.getIconWidth();
557 final int h
= icon
.getIconHeight();
559 final BufferedImage image
= GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
560 .createCompatibleImage(16, 16, Color
.TRANSLUCENT
);
561 final Graphics2D g
= image
.createGraphics();
562 icon
.paintIcon(null, g
, 0, 0);
565 final BufferedImage img
= new BufferedImage(16, 16, BufferedImage
.TRANSLUCENT
);
566 final int offX
= Math
.max((16 - w
) / 2, 0);
567 final int offY
= Math
.max((16 - h
) / 2, 0);
568 for (int col
= 0; col
< w
; col
++) {
569 for (int row
= 0; row
< h
; row
++) {
570 img
.setRGB(col
+ offX
, row
+ offY
, image
.getRGB(col
, row
));
574 return new ImageIcon(img
);