closing switcher with left/right arrow should not expand/collapse tree-views (IDEADEV...
[fedora-idea.git] / platform-impl / src / com / intellij / ide / actions / ToolWindowSwitcher.java
blob92fd1fd9b0be511f5d6bb44fb5976f537eff076c
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;
31 import javax.swing.*;
32 import javax.swing.border.EmptyBorder;
33 import javax.swing.event.ListSelectionEvent;
34 import javax.swing.event.ListSelectionListener;
35 import java.awt.*;
36 import java.awt.event.*;
37 import java.awt.image.BufferedImage;
38 import java.io.File;
39 import java.util.*;
41 /**
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()) {
58 SWITCHER.goBack();
60 else {
61 SWITCHER.goForward();
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;
69 final JList files;
70 final JPanel separator;
71 final ToolWindowManager twManager;
72 final JLabel pathLabel = new JLabel(" ");
73 final JPanel descriptions;
74 final Project project;
75 final int CTRL_KEY;
76 final int ALT_KEY;
78 ToolWindowSwitcherPanel(Project project) {
79 super(new BorderLayout(0, 0));
80 this.project = project;
81 setFocusable(true);
82 addKeyListener(this);
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()) {
91 @Override
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()) {
106 ids.put(tw, id);
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() {
132 @Override
133 protected void paintComponent(Graphics g) {
134 super.paintComponent(g);
135 g.setColor(SEPARATOR_COLOR);
136 g.drawLine(0, 0, 0, getHeight());
139 @Override
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();
150 try {
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);
192 return fullText;
195 public void valueChanged(final ListSelectionEvent e) {
196 SwingUtilities.invokeLater(new Runnable() {
197 public void run() {
198 updatePathLabel();
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()));
209 } else {
210 pathLabel.setText(" ");
212 } else {
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;
222 } else {
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)
229 .setResizable(false)
230 .setModalContext(false)
231 .setFocusable(true)
232 .setRequestFocus(true)
233 .setTitle("Switcher")
234 .setMovable(false)
235 .setCancelCallback(new Computable<Boolean>() {
236 public Boolean compute() {
237 SWITCHER = null;
238 return true;
240 }).createPopup();
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) {
256 navigate();
257 } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
258 goLeft();
259 } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
260 goRight();
264 public void keyPressed(KeyEvent e) {
265 switch (e.getKeyCode()) {
266 case KeyEvent.VK_UP:
267 goBack();
268 break;
269 case KeyEvent.VK_DOWN:
270 goForward();
271 break;
272 case KeyEvent.VK_ESCAPE:
273 cancel();
274 break;
275 case KeyEvent.VK_DELETE:
276 case KeyEvent.VK_BACK_SPACE: // Mac users
277 closeTabOrToolWindow();
278 break;
280 if (e.getKeyCode() == ALT_KEY) {
281 if (isFilesSelected()) {
282 goLeft();
283 } else {
284 goRight();
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) {
299 goLeft();
300 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
301 this.remove(jList);
302 this.remove(separator);
303 } else {
304 goForward();
305 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
306 jList.setSize(jList.getPreferredSize());
308 pack();
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;
320 do {
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()) {
341 cancel();
343 else {
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() {
352 myPopup.cancel();
355 private void goLeft() {
356 if (isToolWindowsSelected()) {
357 cancel();
359 else {
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()) {
371 index = 0;
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;
382 if (index < 0) {
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);
395 return files;
397 else {
398 toolwindows.setSelectedIndex(0);
399 return toolwindows;
402 else {
403 return toolwindows.isSelectionEmpty() ? files : toolwindows;
407 private void navigate() {
408 myPopup.cancel();
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) {
444 navigate();
449 private boolean mouseMovedFirstTime = true;
450 public void mouseMoved(MouseEvent e) {
451 if (mouseMovedFirstTime) {
452 mouseMovedFirstTime = false;
453 return;
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) {
477 myProject = 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) {
498 this.ids = 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();
517 if (icon == null) {
518 return Icons.UI_FORM_ICON;
521 icon = to16x16(icon);
522 iconCache.put(ids.get(toolWindow), icon);
523 return 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);
535 g.dispose();
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);