better Ctrl release handling (IDEA-24436)
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / actions / ToolWindowSwitcher.java
blobd8ab5e00b3ec2b55907c6102d933c0776e983fc6
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;
32 import javax.swing.*;
33 import javax.swing.border.EmptyBorder;
34 import javax.swing.event.ListSelectionEvent;
35 import javax.swing.event.ListSelectionListener;
36 import java.awt.*;
37 import java.awt.event.*;
38 import java.awt.image.BufferedImage;
39 import java.io.File;
40 import java.util.*;
42 /**
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
52 @Override
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()) {
78 SWITCHER.goBack();
79 } else {
80 SWITCHER.goForward();
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;
88 final JList files;
89 final JPanel separator;
90 final ToolWindowManager twManager;
91 final JLabel pathLabel = new JLabel(" ");
92 final JPanel descriptions;
93 final Project project;
94 final int CTRL_KEY;
95 final int ALT_KEY;
97 ToolWindowSwitcherPanel(Project project) {
98 super(new BorderLayout(0, 0));
99 this.project = project;
100 setFocusable(true);
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()) {
110 @Override
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()) {
125 ids.put(tw, id);
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() {
151 @Override
152 protected void paintComponent(Graphics g) {
153 super.paintComponent(g);
154 g.setColor(SEPARATOR_COLOR);
155 g.drawLine(0, 0, 0, getHeight());
158 @Override
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();
169 try {
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);
211 return fullText;
214 public void valueChanged(final ListSelectionEvent e) {
215 SwingUtilities.invokeLater(new Runnable() {
216 public void run() {
217 updatePathLabel();
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()));
228 } else {
229 pathLabel.setText(" ");
231 } else {
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;
241 } else {
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)
248 .setResizable(false)
249 .setModalContext(false)
250 .setFocusable(true)
251 .setRequestFocus(true)
252 .setTitle(SWITCHER_TITLE)
253 .setMovable(false)
254 .setCancelCallback(new Computable<Boolean>() {
255 public Boolean compute() {
256 SWITCHER = null;
257 if (focusComponent != null) {
258 focusComponent.removeKeyListener(performanceProblemsSolver);
259 focusComponent = null;
261 return true;
263 }).createPopup();
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) {
279 navigate();
280 } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
281 goLeft();
282 } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
283 goRight();
287 public void keyPressed(KeyEvent e) {
288 switch (e.getKeyCode()) {
289 case KeyEvent.VK_UP:
290 goBack();
291 break;
292 case KeyEvent.VK_DOWN:
293 goForward();
294 break;
295 case KeyEvent.VK_ESCAPE:
296 cancel();
297 break;
298 case KeyEvent.VK_DELETE:
299 case KeyEvent.VK_BACK_SPACE: // Mac users
300 closeTabOrToolWindow();
301 break;
303 if (e.getKeyCode() == ALT_KEY) {
304 if (isFilesSelected()) {
305 goLeft();
306 } else {
307 goRight();
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) {
322 goLeft();
323 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
324 this.remove(jList);
325 this.remove(separator);
326 } else {
327 goForward();
328 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
329 jList.setSize(jList.getPreferredSize());
331 pack();
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);
338 } else {
339 toolWindow.hide(null);
344 private void pack() {
345 this.setSize(this.getPreferredSize());
346 final JRootPane rootPane = SwingUtilities.getRootPane(this);
347 Container container = this;
348 do {
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()) {
369 cancel();
371 else {
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() {
380 myPopup.cancel();
383 private void goLeft() {
384 if (isToolWindowsSelected()) {
385 cancel();
387 else {
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()) {
399 index = 0;
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;
410 if (index < 0) {
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);
423 return files;
425 else {
426 toolwindows.setSelectedIndex(0);
427 return toolwindows;
430 else {
431 return toolwindows.isSelectionEmpty() ? files : toolwindows;
435 private void navigate() {
436 myPopup.cancel();
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) {
472 navigate();
477 private boolean mouseMovedFirstTime = true;
478 public void mouseMoved(MouseEvent e) {
479 if (mouseMovedFirstTime) {
480 mouseMovedFirstTime = false;
481 return;
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) {
505 myProject = 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) {
526 this.ids = 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();
545 if (icon == null) {
546 return Icons.UI_FORM_ICON;
549 icon = to16x16(icon);
550 iconCache.put(ids.get(toolWindow), icon);
551 return 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);
563 g.dispose();
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);