toolwindow resize for docked windows
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / actions / ToolWindowSwitcher.java
blob69992ab347969509f84f0faedb304345e00a2216
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide.actions;
18 import com.intellij.featureStatistics.FeatureUsageTracker;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.editor.markup.EffectType;
21 import com.intellij.openapi.editor.markup.TextAttributes;
22 import com.intellij.openapi.fileEditor.FileEditorManager;
23 import com.intellij.openapi.fileEditor.impl.EditorHistoryManager;
24 import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
25 import com.intellij.openapi.project.DumbAware;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.popup.JBPopup;
28 import com.intellij.openapi.ui.popup.JBPopupFactory;
29 import com.intellij.openapi.util.Computable;
30 import com.intellij.openapi.util.Iconable;
31 import com.intellij.openapi.vcs.FileStatus;
32 import com.intellij.openapi.vcs.FileStatusManager;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.openapi.wm.ToolWindow;
35 import com.intellij.openapi.wm.ToolWindowManager;
36 import com.intellij.openapi.wm.ex.WindowManagerEx;
37 import com.intellij.openapi.wm.impl.IdeFrameImpl;
38 import com.intellij.openapi.wm.impl.ToolWindowManagerImpl;
39 import com.intellij.ui.ColoredListCellRenderer;
40 import com.intellij.ui.IdeBorderFactory;
41 import com.intellij.ui.SimpleTextAttributes;
42 import com.intellij.util.ArrayUtil;
43 import com.intellij.util.IconUtil;
44 import com.intellij.util.Icons;
45 import org.jetbrains.annotations.NonNls;
47 import javax.swing.*;
48 import javax.swing.FocusManager;
49 import javax.swing.border.EmptyBorder;
50 import javax.swing.event.ListSelectionEvent;
51 import javax.swing.event.ListSelectionListener;
52 import java.awt.*;
53 import java.awt.event.*;
54 import java.awt.image.BufferedImage;
55 import java.io.File;
56 import java.util.*;
58 /**
59 * @author Konstantin Bulenkov
61 public class ToolWindowSwitcher extends AnAction implements DumbAware {
62 private static volatile ToolWindowSwitcherPanel SWITCHER = null;
63 private static final Color BORDER_COLOR = new Color(0x87, 0x87, 0x87);
64 private static final Color SEPARATOR_COLOR = BORDER_COLOR.brighter();
65 @NonNls private static final String SWITCHER_FEATURE_ID = "switcher";
67 private static final KeyListener performanceProblemsSolver = new KeyAdapter() { //IDEA-24436
68 @Override
69 public void keyReleased(KeyEvent e) {
70 if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
71 synchronized (ToolWindowSwitcher.class) {
72 if (ToolWindowSwitcher.SWITCHER != null) {
73 ToolWindowSwitcher.SWITCHER.navigate();
80 private static Component focusComponent = null;
81 @NonNls private static final String SWITCHER_TITLE = "Switcher";
83 public void actionPerformed(AnActionEvent e) {
84 final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext());
85 if (project == null) return;
86 if (SWITCHER == null) {
87 focusComponent = FocusManager.getCurrentManager().getFocusOwner();
88 if (focusComponent != null) {
89 focusComponent.addKeyListener(performanceProblemsSolver);
91 SWITCHER = new ToolWindowSwitcherPanel(project);
92 FeatureUsageTracker.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID);
95 if (e.getInputEvent().isShiftDown()) {
96 SWITCHER.goBack();
97 } else {
98 SWITCHER.goForward();
102 private class ToolWindowSwitcherPanel extends JPanel implements KeyListener, MouseListener, MouseMotionListener {
103 final JBPopup myPopup;
104 final Map<ToolWindow, String> ids = new HashMap<ToolWindow, String>();
105 final JList toolwindows;
106 final JList files;
107 final JPanel separator;
108 final ToolWindowManager twManager;
109 final JLabel pathLabel = new JLabel(" ");
110 final JPanel descriptions;
111 final Project project;
112 final int CTRL_KEY;
113 final int ALT_KEY;
115 ToolWindowSwitcherPanel(Project project) {
116 super(new BorderLayout(0, 0));
117 this.project = project;
118 setFocusable(true);
119 addKeyListener(this);
120 setBorder(new EmptyBorder(0, 0, 0, 0));
121 setBackground(Color.WHITE);
122 pathLabel.setHorizontalAlignment(SwingConstants.RIGHT);
124 final Font font = pathLabel.getFont();
125 pathLabel.setFont(font.deriveFont((float)10));
127 descriptions = new JPanel(new BorderLayout()) {
128 @Override
129 protected void paintComponent(Graphics g) {
130 super.paintComponent(g);
131 g.setColor(BORDER_COLOR);
132 g.drawLine(0, 0, getWidth(), 0);
136 descriptions.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
137 descriptions.add(pathLabel);
138 twManager = ToolWindowManager.getInstance(project);
139 final DefaultListModel twModel = new DefaultListModel();
140 for (String id : twManager.getToolWindowIds()) {
141 final ToolWindow tw = twManager.getToolWindow(id);
142 if (tw.isAvailable()) {
143 ids.put(tw, id);
147 final ArrayList<ToolWindow> windows = new ArrayList<ToolWindow>(ids.keySet());
148 Collections.sort(windows, new ToolWindowComparator());
149 for (ToolWindow window : windows) {
150 twModel.addElement(window);
153 toolwindows = new JList(twModel);
154 toolwindows.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 20));
155 toolwindows.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
156 toolwindows.setCellRenderer(new ToolWindowsRenderer(ids));
157 toolwindows.addKeyListener(this);
158 toolwindows.addMouseListener(this);
159 toolwindows.addMouseMotionListener(this);
160 toolwindows.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
161 public void valueChanged(ListSelectionEvent e) {
162 if (!toolwindows.getSelectionModel().isSelectionEmpty()) {
163 files.getSelectionModel().clearSelection();
168 separator = new JPanel() {
169 @Override
170 protected void paintComponent(Graphics g) {
171 super.paintComponent(g);
172 g.setColor(SEPARATOR_COLOR);
173 g.drawLine(0, 0, 0, getHeight());
176 @Override
177 public Dimension getMaximumSize() {
178 final Dimension max = super.getMaximumSize();
179 return new Dimension(5, max.height);
182 separator.setBackground(Color.WHITE);
184 final FileEditorManager editorManager = FileEditorManager.getInstance(project);
185 final VirtualFile[] openFiles = editorManager.getOpenFiles();
187 try {
188 Arrays.sort(openFiles, new RecentFilesComparator(project));
189 } catch (Exception e) {// IndexNotReadyException
192 final DefaultListModel filesModel = new DefaultListModel();
193 for (VirtualFile openFile : openFiles) {
194 filesModel.addElement(openFile);
196 files = new JList(filesModel);
197 files.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
198 files.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 20));
199 files.setCellRenderer(new VirtualFilesRenderer(project));
200 files.addKeyListener(this);
201 files.addMouseListener(this);
202 files.addMouseMotionListener(this);
203 files.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
204 public void valueChanged(ListSelectionEvent e) {
205 if (!files.getSelectionModel().isSelectionEmpty()) {
206 toolwindows.getSelectionModel().clearSelection();
211 this.add(toolwindows, BorderLayout.WEST);
212 if (filesModel.size() > 0) {
213 files.setAlignmentY(1f);
214 this.add(files, BorderLayout.EAST);
215 this.add(separator, BorderLayout.CENTER);
217 this.add(descriptions, BorderLayout.SOUTH);
219 files.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
220 private String getTitle2Text(String fullText) {
221 int labelWidth = pathLabel.getWidth();
222 if (fullText == null || fullText.length() == 0) return " ";
223 while (pathLabel.getFontMetrics(pathLabel.getFont()).stringWidth(fullText) > labelWidth) {
224 int sep = fullText.indexOf(File.separatorChar, 4);
225 if (sep < 0) return fullText;
226 fullText = "..." + fullText.substring(sep);
229 return fullText;
232 public void valueChanged(final ListSelectionEvent e) {
233 SwingUtilities.invokeLater(new Runnable() {
234 public void run() {
235 updatePathLabel();
240 private void updatePathLabel() {
241 final Object[] values = files.getSelectedValues();
242 if (values != null && values.length == 1) {
243 final VirtualFile parent = ((VirtualFile)values[0]).getParent();
244 if (parent != null) {
245 pathLabel.setText(getTitle2Text(parent.getPresentableUrl()));
246 } else {
247 pathLabel.setText(" ");
249 } else {
250 pathLabel.setText(" ");
255 final int modifiers = getModifiers(ToolWindowSwitcher.this.getShortcutSet());
256 if ((modifiers & Event.ALT_MASK) != 0) {
257 ALT_KEY = KeyEvent.VK_CONTROL;
258 CTRL_KEY = KeyEvent.VK_ALT;
259 } else {
260 ALT_KEY = KeyEvent.VK_ALT;
261 CTRL_KEY = KeyEvent.VK_CONTROL;
264 final IdeFrameImpl ideFrame = WindowManagerEx.getInstanceEx().getFrame(project);
265 myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(this, this)
266 .setResizable(false)
267 .setModalContext(false)
268 .setFocusable(true)
269 .setRequestFocus(true)
270 .setTitle(SWITCHER_TITLE)
271 .setMovable(false)
272 .setCancelCallback(new Computable<Boolean>() {
273 public Boolean compute() {
274 SWITCHER = null;
275 if (focusComponent != null) {
276 focusComponent.removeKeyListener(performanceProblemsSolver);
277 focusComponent = null;
279 return true;
281 }).createPopup();
282 myPopup.showInCenterOf(ideFrame.getContentPane());
285 private int getModifiers(ShortcutSet shortcutSet) {
286 if (shortcutSet == null
287 || shortcutSet.getShortcuts().length == 0
288 || !(shortcutSet.getShortcuts()[0] instanceof KeyboardShortcut)) return Event.CTRL_MASK;
289 return ((KeyboardShortcut)shortcutSet.getShortcuts()[0]).getFirstKeyStroke().getModifiers();
292 public void keyTyped(KeyEvent e) {
295 public void keyReleased(KeyEvent e) {
296 if (e.getKeyCode() == CTRL_KEY || e.getKeyCode() == KeyEvent.VK_ENTER) {
297 navigate();
298 } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
299 goLeft();
300 } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
301 goRight();
305 public void keyPressed(KeyEvent e) {
306 switch (e.getKeyCode()) {
307 case KeyEvent.VK_UP:
308 goBack();
309 break;
310 case KeyEvent.VK_DOWN:
311 goForward();
312 break;
313 case KeyEvent.VK_ESCAPE:
314 cancel();
315 break;
316 case KeyEvent.VK_DELETE:
317 case KeyEvent.VK_BACK_SPACE: // Mac users
318 closeTabOrToolWindow();
319 break;
321 if (e.getKeyCode() == ALT_KEY) {
322 if (isFilesSelected()) {
323 goLeft();
324 } else {
325 goRight();
330 private void closeTabOrToolWindow() {
331 final Object value = getSelectedList().getSelectedValue();
332 if (value instanceof VirtualFile) {
333 final VirtualFile virtualFile = (VirtualFile)value;
334 final FileEditorManager editorManager = FileEditorManager.getInstance(project);
335 if (editorManager instanceof FileEditorManagerImpl) {
336 final JList jList = getSelectedList();
337 ((FileEditorManagerImpl)editorManager).closeFile(virtualFile, false);
338 final int selectedIndex = jList.getSelectedIndex();
339 if (jList.getModel().getSize() == 1) {
340 goLeft();
341 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
342 this.remove(jList);
343 this.remove(separator);
344 } else {
345 goForward();
346 ((DefaultListModel)jList.getModel()).removeElementAt(selectedIndex);
347 jList.setSize(jList.getPreferredSize());
349 pack();
351 } else if (value instanceof ToolWindow) {
352 final ToolWindow toolWindow = (ToolWindow)value;
353 if (twManager instanceof ToolWindowManagerImpl) {
354 ToolWindowManagerImpl manager = (ToolWindowManagerImpl)twManager;
355 manager.hideToolWindow(ids.get(toolWindow), false, false);
356 } else {
357 toolWindow.hide(null);
362 private void pack() {
363 this.setSize(this.getPreferredSize());
364 final JRootPane rootPane = SwingUtilities.getRootPane(this);
365 Container container = this;
366 do {
367 container = container.getParent();
368 container.setSize(container.getPreferredSize());
369 } while (container != rootPane);
370 container.getParent().setSize(container.getPreferredSize());
373 private boolean isFilesSelected() {
374 return getSelectedList() == files;
377 private boolean isFilesVisible() {
378 return files.getModel().getSize() > 0;
381 private boolean isToolWindowsSelected() {
382 return getSelectedList() == toolwindows;
385 private void goRight() {
386 if (isFilesSelected() || !isFilesVisible()) {
387 cancel();
389 else {
390 if (files.getModel().getSize() > 0) {
391 files.setSelectedIndex(Math.min(toolwindows.getSelectedIndex(), files.getModel().getSize() - 1));
392 toolwindows.getSelectionModel().clearSelection();
397 private void cancel() {
398 myPopup.cancel();
401 private void goLeft() {
402 if (isToolWindowsSelected()) {
403 cancel();
405 else {
406 if (toolwindows.getModel().getSize() > 0) {
407 toolwindows.setSelectedIndex(Math.min(files.getSelectedIndex(), toolwindows.getModel().getSize() - 1));
408 files.getSelectionModel().clearSelection();
413 public void goForward() {
414 JList list = getSelectedList();
415 int index = list.getSelectedIndex() + 1;
416 if (index >= list.getModel().getSize()) {
417 index = 0;
418 if (isFilesVisible()) {
419 list = isFilesSelected() ? toolwindows : files;
422 list.setSelectedIndex(index);
425 public void goBack() {
426 JList list = getSelectedList();
427 int index = list.getSelectedIndex() - 1;
428 if (index < 0) {
429 if (isFilesVisible()) {
430 list = isFilesSelected() ? toolwindows : files;
432 index = list.getModel().getSize() - 1;
434 list.setSelectedIndex(index);
437 public JList getSelectedList() {
438 if (toolwindows.isSelectionEmpty() && files.isSelectionEmpty()) {
439 if (files.getModel().getSize() > 1) {
440 files.setSelectedIndex(0);
441 return files;
443 else {
444 toolwindows.setSelectedIndex(0);
445 return toolwindows;
448 else {
449 return toolwindows.isSelectionEmpty() ? files : toolwindows;
453 private void navigate() {
454 myPopup.cancel();
455 final Object value = getSelectedList().getSelectedValue();
456 if (value instanceof ToolWindow) {
457 ((ToolWindow)value).activate(null, true, true);
459 else if (value instanceof VirtualFile) {
460 FileEditorManager.getInstance(project).openFile((VirtualFile)value, true);
464 private class RecentFilesComparator implements Comparator<VirtualFile> {
465 private final VirtualFile[] recentFiles;
467 public RecentFilesComparator(Project project) {
468 recentFiles = EditorHistoryManager.getInstance(project).getFiles();
471 public int compare(VirtualFile vf1, VirtualFile vf2) {
472 return ArrayUtil.find(recentFiles, vf2) - ArrayUtil.find(recentFiles, vf1);
476 private class ToolWindowComparator implements Comparator<ToolWindow> {
477 public int compare(ToolWindow o1, ToolWindow o2) {
478 return ids.get(o1).compareTo(ids.get(o2));
482 public void mouseClicked(MouseEvent e) {
483 final Object source = e.getSource();
484 if (source instanceof JList) {
485 JList jList = (JList)source;
486 if (jList.getSelectedIndex() == -1 && jList.getAnchorSelectionIndex() != -1) {
487 jList.setSelectedIndex(jList.getAnchorSelectionIndex());
489 if (jList.getSelectedIndex() != -1) {
490 navigate();
495 private boolean mouseMovedFirstTime = true;
496 public void mouseMoved(MouseEvent e) {
497 if (mouseMovedFirstTime) {
498 mouseMovedFirstTime = false;
499 return;
502 final Object source = e.getSource();
503 if (source instanceof JList) {
504 JList list = (JList)source;
505 int index = list.locationToIndex(e.getPoint());
506 if (0 <= index && index < list.getModel().getSize()) {
507 list.setSelectedIndex(index);
512 public void mousePressed(MouseEvent e) {}
513 public void mouseReleased(MouseEvent e) {}
514 public void mouseEntered(MouseEvent e) {}
515 public void mouseExited(MouseEvent e) {}
516 public void mouseDragged(MouseEvent e) {}
519 private static class VirtualFilesRenderer extends ColoredListCellRenderer {
520 private final Project myProject;
522 public VirtualFilesRenderer(Project project) {
523 myProject = project;
526 protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
527 if (value instanceof VirtualFile) {
528 final VirtualFile virtualFile = (VirtualFile)value;
529 final String name = virtualFile.getPresentableName();
530 setIcon(IconUtil.getIcon(virtualFile, Iconable.ICON_FLAG_READ_STATUS, myProject));
532 final FileStatus fileStatus = FileStatusManager.getInstance(myProject).getStatus(virtualFile);
533 final TextAttributes attributes = new TextAttributes(fileStatus.getColor(), null, null, EffectType.LINE_UNDERSCORE, Font.PLAIN);
534 append(name, SimpleTextAttributes.fromTextAttributes(attributes));
539 private static class ToolWindowsRenderer extends ColoredListCellRenderer {
540 private static final Map<String, Icon> iconCache = new HashMap<String, Icon>();
541 private final Map<ToolWindow, String> ids;
543 public ToolWindowsRenderer(Map<ToolWindow, String> ids) {
544 this.ids = ids;
547 protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
548 if (value instanceof ToolWindow) {
549 final ToolWindow tw = (ToolWindow)value;
550 final String name = ids.get(tw);
551 setIcon(getIcon(tw));
553 final TextAttributes attributes = new TextAttributes(Color.BLACK, null, null, EffectType.LINE_UNDERSCORE, Font.PLAIN);
554 append(name, SimpleTextAttributes.fromTextAttributes(attributes));
558 private Icon getIcon(ToolWindow toolWindow) {
559 Icon icon = iconCache.get(ids.get(toolWindow));
560 if (icon != null) return icon;
562 icon = toolWindow.getIcon();
563 if (icon == null) {
564 return Icons.UI_FORM_ICON;
567 icon = to16x16(icon);
568 iconCache.put(ids.get(toolWindow), icon);
569 return icon;
572 private static Icon to16x16(Icon icon) {
573 if (icon.getIconHeight() == 16 && icon.getIconWidth() == 16) return icon;
574 final int w = Math.min (icon.getIconWidth(), 16);
575 final int h = Math.min(icon.getIconHeight(), 16);
577 final BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
578 .createCompatibleImage(16, 16, Color.TRANSLUCENT);
579 final Graphics2D g = image.createGraphics();
580 icon.paintIcon(null, g, 0, 0);
581 g.dispose();
583 final BufferedImage img = new BufferedImage(16, 16, BufferedImage.TRANSLUCENT);
584 final int offX = Math.max((16 - w) / 2, 0);
585 final int offY = Math.max((16 - h) / 2, 0);
586 for (int col = 0; col < w; col++) {
587 for (int row = 0; row < h; row++) {
588 img.setRGB(col + offX, row + offY, image.getRGB(col, row));
592 return new ImageIcon(img);