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
.ui
.ColoredListCellRenderer
;
24 import com
.intellij
.ui
.IdeBorderFactory
;
25 import com
.intellij
.ui
.SimpleTextAttributes
;
26 import com
.intellij
.util
.ArrayUtil
;
27 import com
.intellij
.util
.IconUtil
;
28 import com
.intellij
.util
.Icons
;
29 import org
.jetbrains
.annotations
.NonNls
;
32 import javax
.swing
.border
.EmptyBorder
;
33 import javax
.swing
.event
.ListSelectionEvent
;
34 import javax
.swing
.event
.ListSelectionListener
;
36 import java
.awt
.event
.*;
37 import java
.awt
.image
.BufferedImage
;
42 * @author Konstantin Bulenkov
44 public class ToolWindowSwitcher
extends AnAction
implements DumbAware
{
45 private static volatile ToolWindowSwitcherPanel SWITCHER
= null;
46 private static final Color BORDER_COLOR
= new Color(0x87, 0x87, 0x87);
47 private static final Color SEPARATOR_COLOR
= BORDER_COLOR
.brighter();
48 @NonNls private static final String SWITCHER_FEATURE_ID
= "switcher";
50 public void actionPerformed(AnActionEvent e
) {
51 final Project project
= PlatformDataKeys
.PROJECT
.getData(e
.getDataContext());
52 if (project
== null) return;
53 if (SWITCHER
== null) {
54 SWITCHER
= new ToolWindowSwitcherPanel(project
);
55 FeatureUsageTracker
.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID
);
57 if (e
.getInputEvent().isShiftDown()) {
65 private class ToolWindowSwitcherPanel
extends JPanel
implements KeyListener
, MouseListener
, MouseMotionListener
{
66 final JBPopup myPopup
;
67 final Map
<ToolWindow
, String
> ids
= new HashMap
<ToolWindow
, String
>();
68 final JList toolwindows
;
70 final JPanel separator
;
71 final ToolWindowManager twManager
;
72 final JLabel pathLabel
= new JLabel(" ");
73 final JPanel descriptions
;
74 final Project project
;
78 ToolWindowSwitcherPanel(Project project
) {
79 super(new BorderLayout(0, 0));
80 this.project
= project
;
83 setBorder(new EmptyBorder(0, 0, 0, 0));
84 setBackground(Color
.WHITE
);
85 pathLabel
.setHorizontalAlignment(SwingConstants
.RIGHT
);
87 final Font font
= pathLabel
.getFont();
88 pathLabel
.setFont(font
.deriveFont((float)10));
90 descriptions
= new JPanel(new BorderLayout()) {
92 protected void paintComponent(Graphics g
) {
93 super.paintComponent(g
);
94 g
.setColor(BORDER_COLOR
);
95 g
.drawLine(0, 0, getWidth(), 0);
99 descriptions
.setBorder(BorderFactory
.createEmptyBorder(1, 4, 1, 4));
100 descriptions
.add(pathLabel
);
101 twManager
= ToolWindowManager
.getInstance(project
);
102 final DefaultListModel twModel
= new DefaultListModel();
103 for (String id
: twManager
.getToolWindowIds()) {
104 final ToolWindow tw
= twManager
.getToolWindow(id
);
105 if (tw
.isAvailable()) {
110 final ArrayList
<ToolWindow
> windows
= new ArrayList
<ToolWindow
>(ids
.keySet());
111 Collections
.sort(windows
, new ToolWindowComparator());
112 for (ToolWindow window
: windows
) {
113 twModel
.addElement(window
);
116 toolwindows
= new JList(twModel
);
117 toolwindows
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
118 toolwindows
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
119 toolwindows
.setCellRenderer(new ToolWindowsRenderer(ids
));
120 toolwindows
.addKeyListener(this);
121 toolwindows
.addMouseListener(this);
122 toolwindows
.addMouseMotionListener(this);
123 toolwindows
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
124 public void valueChanged(ListSelectionEvent e
) {
125 if (!toolwindows
.getSelectionModel().isSelectionEmpty()) {
126 files
.getSelectionModel().clearSelection();
131 separator
= new JPanel() {
133 protected void paintComponent(Graphics g
) {
134 super.paintComponent(g
);
135 g
.setColor(SEPARATOR_COLOR
);
136 g
.drawLine(0, 0, 0, getHeight());
140 public Dimension
getMaximumSize() {
141 final Dimension max
= super.getMaximumSize();
142 return new Dimension(5, max
.height
);
145 separator
.setBackground(Color
.WHITE
);
147 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
148 final VirtualFile
[] openFiles
= editorManager
.getOpenFiles();
151 Arrays
.sort(openFiles
, new RecentFilesComparator(project
));
152 } catch (Exception e
) {// IndexNotReadyException
155 final DefaultListModel filesModel
= new DefaultListModel();
156 for (VirtualFile openFile
: openFiles
) {
157 filesModel
.addElement(openFile
);
159 files
= new JList(filesModel
);
160 files
.setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
161 files
.setBorder(IdeBorderFactory
.createEmptyBorder(5, 5, 5, 20));
162 files
.setCellRenderer(new VirtualFilesRenderer(project
));
163 files
.addKeyListener(this);
164 files
.addMouseListener(this);
165 files
.addMouseMotionListener(this);
166 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
167 public void valueChanged(ListSelectionEvent e
) {
168 if (!files
.getSelectionModel().isSelectionEmpty()) {
169 toolwindows
.getSelectionModel().clearSelection();
174 this.add(toolwindows
, BorderLayout
.WEST
);
175 if (filesModel
.size() > 0) {
176 files
.setAlignmentY(1f
);
177 this.add(files
, BorderLayout
.EAST
);
178 this.add(separator
, BorderLayout
.CENTER
);
180 this.add(descriptions
, BorderLayout
.SOUTH
);
182 files
.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
183 private String
getTitle2Text(String fullText
) {
184 int labelWidth
= pathLabel
.getWidth();
185 if (fullText
== null || fullText
.length() == 0) return " ";
186 while (pathLabel
.getFontMetrics(pathLabel
.getFont()).stringWidth(fullText
) > labelWidth
) {
187 int sep
= fullText
.indexOf(File
.separatorChar
, 4);
188 if (sep
< 0) return fullText
;
189 fullText
= "..." + fullText
.substring(sep
);
195 public void valueChanged(final ListSelectionEvent e
) {
196 SwingUtilities
.invokeLater(new Runnable() {
203 private void updatePathLabel() {
204 final Object
[] values
= files
.getSelectedValues();
205 if (values
!= null && values
.length
== 1) {
206 final VirtualFile parent
= ((VirtualFile
)values
[0]).getParent();
207 if (parent
!= null) {
208 pathLabel
.setText(getTitle2Text(parent
.getPresentableUrl()));
210 pathLabel
.setText(" ");
213 pathLabel
.setText(" ");
218 final int modifiers
= getModifiers(ToolWindowSwitcher
.this.getShortcutSet());
219 if ((modifiers
& Event
.ALT_MASK
) != 0) {
220 ALT_KEY
= KeyEvent
.VK_CONTROL
;
221 CTRL_KEY
= KeyEvent
.VK_ALT
;
223 ALT_KEY
= KeyEvent
.VK_ALT
;
224 CTRL_KEY
= KeyEvent
.VK_CONTROL
;
227 final IdeFrameImpl ideFrame
= WindowManagerEx
.getInstanceEx().getFrame(project
);
228 myPopup
= JBPopupFactory
.getInstance().createComponentPopupBuilder(this, this)
230 .setModalContext(false)
232 .setRequestFocus(true)
233 .setTitle("Switcher")
235 .setCancelCallback(new Computable
<Boolean
>() {
236 public Boolean
compute() {
241 myPopup
.showInCenterOf(ideFrame
.getContentPane());
244 private int getModifiers(ShortcutSet shortcutSet
) {
245 if (shortcutSet
== null
246 || shortcutSet
.getShortcuts().length
== 0
247 || !(shortcutSet
.getShortcuts()[0] instanceof KeyboardShortcut
)) return Event
.CTRL_MASK
;
248 return ((KeyboardShortcut
)shortcutSet
.getShortcuts()[0]).getFirstKeyStroke().getModifiers();
251 public void keyTyped(KeyEvent e
) {
254 public void keyReleased(KeyEvent e
) {
255 if (e
.getKeyCode() == CTRL_KEY
|| e
.getKeyCode() == KeyEvent
.VK_ENTER
) {
257 } else if (e
.getKeyCode() == KeyEvent
.VK_LEFT
) {
259 } else if (e
.getKeyCode() == KeyEvent
.VK_RIGHT
) {
264 public void keyPressed(KeyEvent e
) {
265 switch (e
.getKeyCode()) {
269 case KeyEvent
.VK_DOWN
:
272 case KeyEvent
.VK_ESCAPE
:
275 case KeyEvent
.VK_DELETE
:
276 case KeyEvent
.VK_BACK_SPACE
: // Mac users
277 closeTabOrToolWindow();
280 if (e
.getKeyCode() == ALT_KEY
) {
281 if (isFilesSelected()) {
289 private void closeTabOrToolWindow() {
290 final Object value
= getSelectedList().getSelectedValue();
291 if (value
instanceof VirtualFile
) {
292 final VirtualFile virtualFile
= (VirtualFile
)value
;
293 final FileEditorManager editorManager
= FileEditorManager
.getInstance(project
);
294 if (editorManager
instanceof FileEditorManagerImpl
) {
295 final JList jList
= getSelectedList();
296 ((FileEditorManagerImpl
)editorManager
).closeFile(virtualFile
, false);
297 final int selectedIndex
= jList
.getSelectedIndex();
298 if (jList
.getModel().getSize() == 1) {
300 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
302 this.remove(separator
);
305 ((DefaultListModel
)jList
.getModel()).removeElementAt(selectedIndex
);
306 jList
.setSize(jList
.getPreferredSize());
310 } else if (value
instanceof ToolWindow
) {
311 ToolWindow toolWindow
= (ToolWindow
)value
;
312 toolWindow
.hide(null);
316 private void pack() {
317 this.setSize(this.getPreferredSize());
318 final JRootPane rootPane
= SwingUtilities
.getRootPane(this);
319 Container container
= this;
321 container
= container
.getParent();
322 container
.setSize(container
.getPreferredSize());
323 } while (container
!= rootPane
);
324 container
.getParent().setSize(container
.getPreferredSize());
327 private boolean isFilesSelected() {
328 return getSelectedList() == files
;
331 private boolean isFilesVisible() {
332 return files
.getModel().getSize() > 0;
335 private boolean isToolWindowsSelected() {
336 return getSelectedList() == toolwindows
;
339 private void goRight() {
340 if (isFilesSelected() || !isFilesVisible()) {
344 if (files
.getModel().getSize() > 0) {
345 files
.setSelectedIndex(Math
.min(toolwindows
.getSelectedIndex(), files
.getModel().getSize() - 1));
346 toolwindows
.getSelectionModel().clearSelection();
351 private void cancel() {
355 private void goLeft() {
356 if (isToolWindowsSelected()) {
360 if (toolwindows
.getModel().getSize() > 0) {
361 toolwindows
.setSelectedIndex(Math
.min(files
.getSelectedIndex(), toolwindows
.getModel().getSize() - 1));
362 files
.getSelectionModel().clearSelection();
367 public void goForward() {
368 JList list
= getSelectedList();
369 int index
= list
.getSelectedIndex() + 1;
370 if (index
>= list
.getModel().getSize()) {
372 if (isFilesVisible()) {
373 list
= isFilesSelected() ? toolwindows
: files
;
376 list
.setSelectedIndex(index
);
379 public void goBack() {
380 JList list
= getSelectedList();
381 int index
= list
.getSelectedIndex() - 1;
383 if (isFilesVisible()) {
384 list
= isFilesSelected() ? toolwindows
: files
;
386 index
= list
.getModel().getSize() - 1;
388 list
.setSelectedIndex(index
);
391 public JList
getSelectedList() {
392 if (toolwindows
.isSelectionEmpty() && files
.isSelectionEmpty()) {
393 if (files
.getModel().getSize() > 1) {
394 files
.setSelectedIndex(0);
398 toolwindows
.setSelectedIndex(0);
403 return toolwindows
.isSelectionEmpty() ? files
: toolwindows
;
407 private void navigate() {
409 final Object value
= getSelectedList().getSelectedValue();
410 if (value
instanceof ToolWindow
) {
411 ((ToolWindow
)value
).activate(null, true, true);
413 else if (value
instanceof VirtualFile
) {
414 FileEditorManager
.getInstance(project
).openFile((VirtualFile
)value
, true);
418 private class RecentFilesComparator
implements Comparator
<VirtualFile
> {
419 private final VirtualFile
[] recentFiles
;
421 public RecentFilesComparator(Project project
) {
422 recentFiles
= EditorHistoryManager
.getInstance(project
).getFiles();
425 public int compare(VirtualFile vf1
, VirtualFile vf2
) {
426 return ArrayUtil
.find(recentFiles
, vf2
) - ArrayUtil
.find(recentFiles
, vf1
);
430 private class ToolWindowComparator
implements Comparator
<ToolWindow
> {
431 public int compare(ToolWindow o1
, ToolWindow o2
) {
432 return ids
.get(o1
).compareTo(ids
.get(o2
));
436 public void mouseClicked(MouseEvent e
) {
437 final Object source
= e
.getSource();
438 if (source
instanceof JList
) {
439 JList jList
= (JList
)source
;
440 if (jList
.getSelectedIndex() == -1 && jList
.getAnchorSelectionIndex() != -1) {
441 jList
.setSelectedIndex(jList
.getAnchorSelectionIndex());
443 if (jList
.getSelectedIndex() != -1) {
449 private boolean mouseMovedFirstTime
= true;
450 public void mouseMoved(MouseEvent e
) {
451 if (mouseMovedFirstTime
) {
452 mouseMovedFirstTime
= false;
456 final Object source
= e
.getSource();
457 if (source
instanceof JList
) {
458 JList list
= (JList
)source
;
459 int index
= list
.locationToIndex(e
.getPoint());
460 if (0 <= index
&& index
< list
.getModel().getSize()) {
461 list
.setSelectedIndex(index
);
466 public void mousePressed(MouseEvent e
) {}
467 public void mouseReleased(MouseEvent e
) {}
468 public void mouseEntered(MouseEvent e
) {}
469 public void mouseExited(MouseEvent e
) {}
470 public void mouseDragged(MouseEvent e
) {}
473 private static class VirtualFilesRenderer
extends ColoredListCellRenderer
{
474 private final Project myProject
;
476 public VirtualFilesRenderer(Project project
) {
480 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
481 if (value
instanceof VirtualFile
) {
482 final VirtualFile virtualFile
= (VirtualFile
)value
;
483 final String name
= virtualFile
.getPresentableName();
484 setIcon(IconUtil
.getIcon(virtualFile
, Iconable
.ICON_FLAG_READ_STATUS
, myProject
));
486 final FileStatus fileStatus
= FileStatusManager
.getInstance(myProject
).getStatus(virtualFile
);
487 final TextAttributes attributes
= new TextAttributes(fileStatus
.getColor(), null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
488 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
493 private static class ToolWindowsRenderer
extends ColoredListCellRenderer
{
494 private static final Map
<String
, Icon
> iconCache
= new HashMap
<String
, Icon
>();
495 private final Map
<ToolWindow
, String
> ids
;
497 public ToolWindowsRenderer(Map
<ToolWindow
, String
> ids
) {
501 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
502 if (value
instanceof ToolWindow
) {
503 final ToolWindow tw
= (ToolWindow
)value
;
504 final String name
= ids
.get(tw
);
505 setIcon(getIcon(tw
));
507 final TextAttributes attributes
= new TextAttributes(Color
.BLACK
, null, null, EffectType
.LINE_UNDERSCORE
, Font
.PLAIN
);
508 append(name
, SimpleTextAttributes
.fromTextAttributes(attributes
));
512 private Icon
getIcon(ToolWindow toolWindow
) {
513 Icon icon
= iconCache
.get(ids
.get(toolWindow
));
514 if (icon
!= null) return icon
;
516 icon
= toolWindow
.getIcon();
518 return Icons
.UI_FORM_ICON
;
521 icon
= to16x16(icon
);
522 iconCache
.put(ids
.get(toolWindow
), icon
);
526 private static Icon
to16x16(Icon icon
) {
527 if (icon
.getIconHeight() == 16 && icon
.getIconWidth() == 16) return icon
;
528 final int w
= icon
.getIconWidth();
529 final int h
= icon
.getIconHeight();
531 final BufferedImage image
= GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
532 .createCompatibleImage(16, 16, Color
.TRANSLUCENT
);
533 final Graphics2D g
= image
.createGraphics();
534 icon
.paintIcon(null, g
, 0, 0);
537 final BufferedImage img
= new BufferedImage(16, 16, BufferedImage
.TRANSLUCENT
);
538 final int offX
= Math
.max((16 - w
) / 2, 0);
539 final int offY
= Math
.max((16 - h
) / 2, 0);
540 for (int col
= 0; col
< w
; col
++) {
541 for (int row
= 0; row
< h
; row
++) {
542 img
.setRGB(col
+ offX
, row
+ offY
, image
.getRGB(col
, row
));
546 return new ImageIcon(img
);