make focus hook better
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / actions / ToolWindowSwitcher.java
blob1f6bd0eeb68aa3a0ab6cc3f92fd685239580858a
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.FocusManager;
34 import javax.swing.border.EmptyBorder;
35 import javax.swing.event.ListSelectionEvent;
36 import javax.swing.event.ListSelectionListener;
37 import java.awt.*;
38 import java.awt.event.*;
39 import java.awt.image.BufferedImage;
40 import java.io.File;
41 import java.util.*;
43 /**
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
53 @Override
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()) {
81 SWITCHER.goBack();
82 } else {
83 SWITCHER.goForward();
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;
91 final JList files;
92 final JPanel separator;
93 final ToolWindowManager twManager;
94 final JLabel pathLabel = new JLabel(" ");
95 final JPanel descriptions;
96 final Project project;
97 final int CTRL_KEY;
98 final int ALT_KEY;
100 ToolWindowSwitcherPanel(Project project) {
101 super(new BorderLayout(0, 0));
102 this.project = project;
103 setFocusable(true);
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()) {
113 @Override
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()) {
128 ids.put(tw, id);
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() {
154 @Override
155 protected void paintComponent(Graphics g) {
156 super.paintComponent(g);
157 g.setColor(SEPARATOR_COLOR);
158 g.drawLine(0, 0, 0, getHeight());
161 @Override
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();
172 try {
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);
214 return fullText;
217 public void valueChanged(final ListSelectionEvent e) {
218 SwingUtilities.invokeLater(new Runnable() {
219 public void run() {
220 updatePathLabel();
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()));
231 } else {
232 pathLabel.setText(" ");
234 } else {
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;
244 } else {
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)
251 .setResizable(false)
252 .setModalContext(false)
253 .setFocusable(true)
254 .setRequestFocus(true)
255 .setTitle(SWITCHER_TITLE)
256 .setMovable(false)
257 .setCancelCallback(new Computable<Boolean>() {
258 public Boolean compute() {
259 SWITCHER = null;
260 if (focusComponent != null) {
261 focusComponent.removeKeyListener(performanceProblemsSolver);
262 focusComponent = null;
264 return true;
266 }).createPopup();
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) {
282 navigate();
283 } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
284 goLeft();
285 } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
286 goRight();
290 public void keyPressed(KeyEvent e) {
291 switch (e.getKeyCode()) {
292 case KeyEvent.VK_UP:
293 goBack();
294 break;
295 case KeyEvent.VK_DOWN:
296 goForward();
297 break;
298 case KeyEvent.VK_ESCAPE:
299 cancel();
300 break;
301 case KeyEvent.VK_DELETE:
302 case KeyEvent.VK_BACK_SPACE: // Mac users
303 closeTabOrToolWindow();
304 break;
306 if (e.getKeyCode() == ALT_KEY) {
307 if (isFilesSelected()) {
308 goLeft();
309 } else {
310 goRight();
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) {
325 goLeft();
326 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
327 this.remove(jList);
328 this.remove(separator);
329 } else {
330 goForward();
331 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
332 jList.setSize(jList.getPreferredSize());
334 pack();
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);
341 } else {
342 toolWindow.hide(null);
347 private void pack() {
348 this.setSize(this.getPreferredSize());
349 final JRootPane rootPane = SwingUtilities.getRootPane(this);
350 Container container = this;
351 do {
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()) {
372 cancel();
374 else {
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() {
383 myPopup.cancel();
386 private void goLeft() {
387 if (isToolWindowsSelected()) {
388 cancel();
390 else {
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()) {
402 index = 0;
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;
413 if (index < 0) {
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);
426 return files;
428 else {
429 toolwindows.setSelectedIndex(0);
430 return toolwindows;
433 else {
434 return toolwindows.isSelectionEmpty() ? files : toolwindows;
438 private void navigate() {
439 myPopup.cancel();
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) {
475 navigate();
480 private boolean mouseMovedFirstTime = true;
481 public void mouseMoved(MouseEvent e) {
482 if (mouseMovedFirstTime) {
483 mouseMovedFirstTime = false;
484 return;
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) {
508 myProject = 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) {
529 this.ids = 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();
548 if (icon == null) {
549 return Icons.UI_FORM_ICON;
552 icon = to16x16(icon);
553 iconCache.put(ids.get(toolWindow), icon);
554 return 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);
566 g.dispose();
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);