| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ide.actions; |
| |
| import com.intellij.featureStatistics.FeatureUsageTracker; |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.IdeEventQueue; |
| import com.intellij.ide.ui.UISettings; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.actionSystem.impl.PresentationFactory; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.markup.EffectType; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.UniqueVFilePathBuilder; |
| import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; |
| import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; |
| import com.intellij.openapi.fileEditor.impl.EditorTabbedContainer; |
| import com.intellij.openapi.fileEditor.impl.EditorWindow; |
| import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.GraphicsConfig; |
| import com.intellij.openapi.ui.popup.JBPopup; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Iconable; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vcs.FileStatus; |
| import com.intellij.openapi.vcs.FileStatusManager; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFilePathWrapper; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.openapi.wm.ToolWindow; |
| import com.intellij.openapi.wm.ToolWindowManager; |
| import com.intellij.openapi.wm.impl.ToolWindowImpl; |
| import com.intellij.openapi.wm.impl.ToolWindowManagerImpl; |
| import com.intellij.ui.*; |
| import com.intellij.ui.components.JBList; |
| import com.intellij.ui.speedSearch.NameFilteringListModel; |
| import com.intellij.ui.speedSearch.SpeedSearchUtil; |
| import com.intellij.util.*; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.ui.StatusText; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.EmptyBorder; |
| import javax.swing.event.ListSelectionEvent; |
| import javax.swing.event.ListSelectionListener; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.io.File; |
| import java.util.*; |
| import java.util.List; |
| |
| import static java.awt.event.KeyEvent.*; |
| |
| /** |
| * @author Konstantin Bulenkov |
| */ |
| @SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod", "SSBasedInspection"}) |
| public class Switcher extends AnAction implements DumbAware { |
| private static volatile SwitcherPanel SWITCHER = null; |
| private static final Color BORDER_COLOR = Gray._135; |
| private static final Color SEPARATOR_COLOR = new JBColor(BORDER_COLOR.brighter(), Gray._75); |
| @NonNls private static final String SWITCHER_FEATURE_ID = "switcher"; |
| private static final Color ON_MOUSE_OVER_BG_COLOR = new JBColor(new Color(231, 242, 249), new Color(77, 80, 84)); |
| private static int CTRL_KEY; |
| private static int ALT_KEY; |
| public static final Runnable CHECKER = new Runnable() { |
| @Override |
| public void run() { |
| synchronized (Switcher.class) { |
| if (SWITCHER != null) { |
| SWITCHER.navigate(false); |
| } |
| } |
| } |
| }; |
| private static final CustomShortcutSet TW_SHORTCUT; |
| |
| static { |
| List<Shortcut> shortcuts = ContainerUtil.newArrayList(); |
| for (char ch = '0'; ch <= '9'; ch++) { |
| shortcuts.add(CustomShortcutSet.fromString("control " + ch).getShortcuts()[0]); |
| } |
| for (char ch = 'A'; ch <= 'Z'; ch++) { |
| shortcuts.add(CustomShortcutSet.fromString("control " + ch).getShortcuts()[0]); |
| } |
| TW_SHORTCUT = new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])); |
| |
| |
| IdeEventQueue.getInstance().addPostprocessor(new IdeEventQueue.EventDispatcher() { |
| @Override |
| public boolean dispatch(AWTEvent event) { |
| ToolWindow tw; |
| if (SWITCHER != null && event instanceof KeyEvent && !SWITCHER.isPinnedMode()) { |
| final KeyEvent keyEvent = (KeyEvent)event; |
| if (event.getID() == KEY_RELEASED && keyEvent.getKeyCode() == CTRL_KEY) { |
| SwingUtilities.invokeLater(CHECKER); |
| } |
| else if (event.getID() == KEY_PRESSED |
| && (tw = SWITCHER.twShortcuts.get(String.valueOf((char)keyEvent.getKeyCode()))) != null) { |
| SWITCHER.myPopup.closeOk(null); |
| tw.activate(null, true, true); |
| } |
| } |
| return false; |
| } |
| }, null); |
| |
| } |
| |
| @NonNls private static final String SWITCHER_TITLE = "Switcher"; |
| |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); |
| if (project == null) return; |
| |
| synchronized (Switcher.class) { |
| if (SWITCHER != null && SWITCHER.isPinnedMode()) { |
| SWITCHER.cancel(); |
| SWITCHER = null; |
| } |
| if (SWITCHER == null) { |
| SWITCHER = createAndShowSwitcher(project, SWITCHER_TITLE, false); |
| FeatureUsageTracker.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID); |
| } |
| } |
| |
| |
| assert SWITCHER != null; |
| if (!SWITCHER.isPinnedMode()) { |
| if (e.getInputEvent().isShiftDown()) { |
| SWITCHER.goBack(); |
| } else { |
| if (!FileEditorManagerEx.getInstanceEx(project).hasOpenedFile()) { |
| SWITCHER.files.setSelectedIndex(0); |
| } else { |
| SWITCHER.goForward(); |
| } |
| } |
| } |
| } |
| |
| public static SwitcherPanel createAndShowSwitcher(Project project, String title, boolean pinned) { |
| synchronized (Switcher.class) { |
| if (SWITCHER != null) { |
| SWITCHER.cancel(); |
| } |
| SWITCHER = new SwitcherPanel(project, title, pinned); |
| return SWITCHER; |
| } |
| } |
| |
| public static class SwitcherPanel extends JPanel implements KeyListener, MouseListener, MouseMotionListener { |
| private final int MAX_FILES_IN_SWITCHER; |
| final JBPopup myPopup; |
| final MyList toolWindows; |
| final MyList files; |
| final JPanel separator; |
| final ToolWindowManager twManager; |
| final JLabel pathLabel = new JLabel(" "); |
| final JPanel descriptions; |
| final Project project; |
| private final boolean myPinned; |
| final Map<String, ToolWindow> twShortcuts; |
| final Alarm myAlarm; |
| final SwitcherSpeedSearch mySpeedSearch; |
| final ClickListener myClickListener = new ClickListener() { |
| @Override |
| public boolean onClick(@NotNull MouseEvent e, int clickCount) { |
| if (myPinned && (e.isControlDown() || e.isMetaDown() || e.isShiftDown())) return false; |
| final Object source = e.getSource(); |
| if (source instanceof JList) { |
| JList jList = (JList)source; |
| if (jList.getSelectedIndex() == -1 && jList.getAnchorSelectionIndex() != -1) { |
| jList.setSelectedIndex(jList.getAnchorSelectionIndex()); |
| } |
| if (jList.getSelectedIndex() != -1) { |
| navigate(false); |
| } |
| } |
| return true; |
| } |
| }; |
| |
| @SuppressWarnings({"ManualArrayToCollectionCopy", "ConstantConditions"}) |
| SwitcherPanel(final Project project, String title, boolean pinned) { |
| setLayout(new SwitcherLayouter()); |
| this.project = project; |
| MAX_FILES_IN_SWITCHER = pinned ? UISettings.getInstance().RECENT_FILES_LIMIT : 30; |
| myPinned = pinned; |
| mySpeedSearch = pinned ? new SwitcherSpeedSearch() : null; |
| |
| setFocusable(true); |
| addKeyListener(this); |
| setBorder(new EmptyBorder(0, 0, 0, 0)); |
| setBackground(JBColor.background()); |
| pathLabel.setHorizontalAlignment(SwingConstants.LEFT); |
| |
| final Font font = pathLabel.getFont(); |
| pathLabel.setFont(font.deriveFont(Math.max(10f, font.getSize() - 4f))); |
| |
| descriptions = new JPanel(new BorderLayout()) { |
| @Override |
| protected void paintComponent(@NotNull Graphics g) { |
| super.paintComponent(g); |
| g.setColor(UIUtil.isUnderDarcula() ? SEPARATOR_COLOR : BORDER_COLOR); |
| g.drawLine(0, 0, getWidth(), 0); |
| } |
| }; |
| |
| descriptions.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4)); |
| descriptions.add(pathLabel, BorderLayout.CENTER); |
| twManager = ToolWindowManager.getInstance(project); |
| DefaultListModel twModel = new DefaultListModel(); |
| List<ActivateToolWindowAction> actions = ToolWindowsGroup.getToolWindowActions(project); |
| List<ToolWindow> windows = ContainerUtil.newArrayList(); |
| for (ActivateToolWindowAction action : actions) { |
| ToolWindow tw = twManager.getToolWindow(action.getToolWindowId()); |
| if (tw.isAvailable()) { |
| windows.add(tw); |
| } |
| } |
| twShortcuts = createShortcuts(windows); |
| final Map<ToolWindow, String> map = ContainerUtil.reverseMap(twShortcuts); |
| Collections.sort(windows, new Comparator<ToolWindow>() { |
| @Override |
| public int compare(@NotNull ToolWindow o1, @NotNull ToolWindow o2) { |
| return StringUtil.compare(map.get(o1), map.get(o2), false); |
| } |
| }); |
| for (ToolWindow window : windows) { |
| twModel.addElement(window); |
| } |
| |
| toolWindows = new MyList(twModel); |
| if (pinned) { |
| new NameFilteringListModel<ToolWindow>(toolWindows, new Function<ToolWindow, String>() { |
| @Override |
| public String fun(ToolWindow window) { |
| return window.getStripeTitle(); |
| } |
| }, new Condition<String>() { |
| @Override |
| public boolean value(String s) { |
| return !mySpeedSearch.isPopupActive() |
| || StringUtil.isEmpty(mySpeedSearch.getEnteredPrefix()) |
| || mySpeedSearch.getComparator().matchingFragments(mySpeedSearch.getEnteredPrefix(), s) != null; |
| } |
| }, mySpeedSearch); |
| } |
| |
| toolWindows.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 20)); |
| toolWindows.setSelectionMode(pinned ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); |
| toolWindows.setCellRenderer(new SwitcherToolWindowsListRenderer(mySpeedSearch, map, myPinned) { |
| @Override |
| public Component getListCellRendererComponent(JList list, |
| Object value, |
| int index, |
| boolean selected, |
| boolean hasFocus) { |
| final JComponent renderer = (JComponent)super.getListCellRendererComponent(list, value, index, selected, selected); |
| if (selected) { |
| return renderer; |
| } |
| final Color bgColor = list == mouseMoveSrc && index == mouseMoveListIndex ? ON_MOUSE_OVER_BG_COLOR : list.getBackground(); |
| UIUtil.changeBackGround(renderer, bgColor); |
| return renderer; |
| } |
| }); |
| toolWindows.addKeyListener(this); |
| toolWindows.addMouseListener(this); |
| toolWindows.addMouseMotionListener(this); |
| myClickListener.installOn(toolWindows); |
| toolWindows.getSelectionModel().addListSelectionListener(new ListSelectionListener() { |
| public void valueChanged(@NotNull ListSelectionEvent e) { |
| if (!toolWindows.isSelectionEmpty() && !files.isSelectionEmpty()) { |
| files.clearSelection(); |
| } |
| } |
| }); |
| |
| separator = new JPanel() { |
| @Override |
| protected void paintComponent(@NotNull Graphics g) { |
| super.paintComponent(g); |
| g.setColor(SEPARATOR_COLOR); |
| g.drawLine(0, 0, 0, getHeight()); |
| } |
| }; |
| separator.setBackground(toolWindows.getBackground()); |
| |
| int selectionIndex = -1; |
| final FileEditorManagerImpl editorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project); |
| final ArrayList<FileInfo> filesData = new ArrayList<FileInfo>(); |
| final ArrayList<FileInfo> editors = new ArrayList<FileInfo>(); |
| if (!pinned) { |
| for (Pair<VirtualFile, EditorWindow> pair : editorManager.getSelectionHistory()) { |
| editors.add(new FileInfo(pair.first, pair.second)); |
| } |
| } |
| if (editors.size() < 2 || isPinnedMode()) { |
| if (isPinnedMode() && editors.size() > 1) { |
| filesData.addAll(editors); |
| } |
| final VirtualFile[] recentFiles = ArrayUtil.reverseArray(EditorHistoryManager.getInstance(project).getFiles()); |
| final int maxFiles = Math.max(editors.size(), recentFiles.length); |
| final int len = isPinnedMode() ? recentFiles.length : Math.min(toolWindows.getModel().getSize(), maxFiles); |
| boolean firstRecentMarked = false; |
| final List<VirtualFile> selectedFiles = Arrays.asList(editorManager.getSelectedFiles()); |
| for (int i = 0; i < len; i++) { |
| if (isPinnedMode() && selectedFiles.contains(recentFiles[i])) { |
| continue; |
| } |
| |
| final FileInfo info = new FileInfo(recentFiles[i], null); |
| boolean add = true; |
| if (isPinnedMode()) { |
| for (FileInfo fileInfo : filesData) { |
| if (fileInfo.first.equals(info.first)) { |
| add = false; |
| break; |
| } |
| } |
| } |
| if (add) { |
| filesData.add(info); |
| if (!firstRecentMarked) { |
| firstRecentMarked = true; |
| selectionIndex = filesData.size() - 1; |
| } |
| } |
| } |
| if (editors.size() == 1) selectionIndex++; |
| if (editors.size() == 1 && (filesData.isEmpty() || !editors.get(0).getFirst().equals(filesData.get(0).getFirst()))) { |
| filesData.add(0, editors.get(0)); |
| } |
| } else { |
| for (int i = 0; i < Math.min(MAX_FILES_IN_SWITCHER, editors.size()); i++) { |
| filesData.add(editors.get(i)); |
| } |
| } |
| |
| final DefaultListModel filesModel = new DefaultListModel(); |
| for (FileInfo editor : filesData) { |
| filesModel.addElement(editor); |
| } |
| |
| final VirtualFilesRenderer filesRenderer = new VirtualFilesRenderer(this) { |
| JPanel myPanel = new JPanel(new BorderLayout()); |
| JLabel myLabel = new JLabel() { |
| @Override |
| protected void paintComponent(@NotNull Graphics g) { |
| GraphicsConfig config = new GraphicsConfig(g); |
| ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); |
| super.paintComponent(g); |
| config.restore(); |
| } |
| }; |
| { |
| myPanel.setOpaque(false); |
| myPanel.setBackground(UIUtil.getListBackground()); |
| myLabel.setText("* "); |
| } |
| |
| @Override |
| public Component getListCellRendererComponent(JList list, |
| Object value, |
| int index, |
| boolean selected, |
| boolean hasFocus) { |
| final Component c = super.getListCellRendererComponent(list, value, index, selected, selected); |
| final Color bg = UIUtil.getListBackground(); |
| final Color fg = UIUtil.getListForeground(); |
| myLabel.setFont(list.getFont()); |
| myLabel.setForeground(open ? fg : bg); |
| |
| myPanel.removeAll(); |
| myPanel.add(myLabel, BorderLayout.WEST); |
| myPanel.add(c, BorderLayout.CENTER); |
| return myPanel; |
| } |
| |
| @Override |
| protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { |
| setPaintFocusBorder(false); |
| super.customizeCellRenderer(list, value, index, selected, hasFocus); |
| } |
| }; |
| |
| final ListSelectionListener filesSelectionListener = new ListSelectionListener() { |
| private String getTitle2Text(String fullText) { |
| int labelWidth = pathLabel.getWidth(); |
| if (fullText == null || fullText.length() == 0) return " "; |
| while (pathLabel.getFontMetrics(pathLabel.getFont()).stringWidth(fullText) > labelWidth) { |
| int sep = fullText.indexOf(File.separatorChar, 4); |
| if (sep < 0) return fullText; |
| fullText = "..." + fullText.substring(sep); |
| } |
| |
| return fullText; |
| } |
| |
| public void valueChanged(@NotNull final ListSelectionEvent e) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| public void run() { |
| updatePathLabel(); |
| } |
| }); |
| } |
| |
| private void updatePathLabel() { |
| Object[] values = files.getSelectedValues(); |
| if (values != null && values.length == 1) { |
| VirtualFile file = ((FileInfo)values[0]).first; |
| String presentableUrl = ObjectUtils.notNull(file.getParent(), file).getPresentableUrl(); |
| pathLabel.setText(getTitle2Text(FileUtil.getLocationRelativeToUserHome(presentableUrl))); |
| } |
| else { |
| pathLabel.setText(" "); |
| } |
| } |
| }; |
| |
| files = new MyList(filesModel); |
| if (pinned) { |
| new NameFilteringListModel<FileInfo>(files, new Function<FileInfo, String>() { |
| @Override |
| public String fun(FileInfo info) { |
| final VirtualFile file = info.getFirst(); |
| return file instanceof VirtualFilePathWrapper ? ((VirtualFilePathWrapper)file).getPresentablePath() : file.getName(); |
| } |
| }, new Condition<String>() { |
| @Override |
| public boolean value(String s) { |
| return !mySpeedSearch.isPopupActive() |
| || StringUtil.isEmpty(mySpeedSearch.getEnteredPrefix()) |
| || mySpeedSearch.getComparator().matchingFragments(mySpeedSearch.getEnteredPrefix(), s) != null; |
| } |
| }, mySpeedSearch); |
| } |
| |
| files.setSelectionMode(pinned ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); |
| files.getSelectionModel().addListSelectionListener(new ListSelectionListener() { |
| public void valueChanged(@NotNull ListSelectionEvent e) { |
| if (!files.isSelectionEmpty() && !toolWindows.isSelectionEmpty()) { |
| toolWindows.getSelectionModel().clearSelection(); |
| } |
| } |
| }); |
| |
| files.getSelectionModel().addListSelectionListener(filesSelectionListener); |
| |
| files.setCellRenderer(filesRenderer); |
| files.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 20)); |
| files.addKeyListener(this); |
| files.addMouseListener(this); |
| files.addMouseMotionListener(this); |
| myClickListener.installOn(files); |
| |
| this.add(toolWindows, BorderLayout.WEST); |
| if (filesModel.size() > 0) { |
| files.setAlignmentY(1f); |
| if (files.getModel().getSize() > 20) { |
| final JScrollPane pane = ScrollPaneFactory.createScrollPane(files, true); |
| pane.setPreferredSize(new Dimension(files.getPreferredSize().width + 10, 20 * 20)); |
| pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); |
| this.add(pane, BorderLayout.EAST); |
| } else { |
| this.add(files, BorderLayout.EAST); |
| } |
| if (selectionIndex > -1) { |
| files.setSelectedIndex(selectionIndex); |
| } |
| this.add(separator, BorderLayout.CENTER); |
| } |
| this.add(descriptions, BorderLayout.SOUTH); |
| |
| final ShortcutSet shortcutSet = ActionManager.getInstance().getAction("Switcher").getShortcutSet(); |
| final int modifiers = getModifiers(shortcutSet); |
| final boolean isAlt = (modifiers & Event.ALT_MASK) != 0; |
| ALT_KEY = isAlt ? VK_CONTROL : VK_ALT; |
| CTRL_KEY = isAlt ? VK_ALT : VK_CONTROL; |
| |
| myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(this, this) |
| .setResizable(pinned) |
| .setModalContext(false) |
| .setFocusable(true) |
| .setRequestFocus(true) |
| .setTitle(title) |
| .setCancelOnWindowDeactivation(true) |
| .setCancelOnOtherWindowOpen(true) |
| .setMovable(pinned) |
| .setCancelKeyEnabled(false) |
| .setCancelCallback(new Computable<Boolean>() { |
| public Boolean compute() { |
| SWITCHER = null; |
| return true; |
| } |
| }).createPopup(); |
| |
| if (isPinnedMode()) { |
| new AnAction(null ,null ,null) { |
| @Override |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| changeSelection(); |
| } |
| }.registerCustomShortcutSet(CustomShortcutSet.fromString("TAB"), this, myPopup); |
| |
| new AnAction(null, null, null) { |
| @Override |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| //suppress all actions to activate a toolwindow : IDEA-71277 |
| } |
| }.registerCustomShortcutSet(TW_SHORTCUT, this, myPopup); |
| new AnAction(null, null, null) { |
| |
| @Override |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| if (mySpeedSearch != null && mySpeedSearch.isPopupActive()) { |
| mySpeedSearch.hidePopup(); |
| } else { |
| myPopup.cancel(); |
| } |
| } |
| }.registerCustomShortcutSet(CustomShortcutSet.fromString("ESCAPE"), this, myPopup); |
| } |
| |
| final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); |
| myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, myPopup); |
| myPopup.showInCenterOf(window); |
| } |
| |
| |
| private static Map<String, ToolWindow> createShortcuts(List<ToolWindow> windows) { |
| final Map<String, ToolWindow> keymap = new HashMap<String, ToolWindow>(windows.size()); |
| final List<ToolWindow> otherTW = new ArrayList<ToolWindow>(); |
| for (ToolWindow window : windows) { |
| int index = ActivateToolWindowAction.getMnemonicForToolWindow(((ToolWindowImpl)window).getId()); |
| if (index >= '0' && index <= '9') { |
| keymap.put(getIndexShortcut(index - '0'), window); |
| } |
| else { |
| otherTW.add(window); |
| } |
| } |
| int i = 0; |
| for (ToolWindow window : otherTW) { |
| while (keymap.get(getIndexShortcut(i)) != null) { |
| i++; |
| } |
| keymap.put(getIndexShortcut(i), window); |
| i++; |
| } |
| return keymap; |
| } |
| |
| private static String getIndexShortcut(int index) { |
| return StringUtil.toUpperCase(Integer.toString(index, index + 1)); |
| } |
| |
| private static int getModifiers(ShortcutSet shortcutSet) { |
| if (shortcutSet == null |
| || shortcutSet.getShortcuts().length == 0 |
| || !(shortcutSet.getShortcuts()[0] instanceof KeyboardShortcut)) return Event.CTRL_MASK; |
| return ((KeyboardShortcut)shortcutSet.getShortcuts()[0]).getFirstKeyStroke().getModifiers(); |
| } |
| |
| public void keyTyped(@NotNull KeyEvent e) { |
| } |
| |
| public void keyReleased(KeyEvent e) { |
| if (e.getKeyCode() == CTRL_KEY && isAutoHide() || e.getKeyCode() == VK_ENTER) { |
| navigate(e.isShiftDown()); |
| } |
| else if (e.getKeyCode() == VK_LEFT) { |
| goLeft(); |
| } |
| else if (e.getKeyCode() == VK_RIGHT) { |
| goRight(); |
| } |
| } |
| |
| KeyEvent lastEvent; |
| public void keyPressed(@NotNull KeyEvent e) { |
| if (mySpeedSearch != null && mySpeedSearch.isPopupActive() || lastEvent == e) return; |
| lastEvent = e; |
| switch (e.getKeyCode()) { |
| case VK_UP: |
| if (!isPinnedMode()) { |
| goBack(); |
| } else { |
| getSelectedList().processKeyEvent(e); |
| } |
| break; |
| case VK_DOWN: |
| if (!isPinnedMode()) { |
| goForward(); |
| } else { |
| getSelectedList().processKeyEvent(e); |
| } |
| break; |
| case VK_ESCAPE: |
| cancel(); |
| break; |
| case VK_END: |
| ListScrollingUtil.moveEnd(getSelectedList()); |
| break; |
| case VK_PAGE_DOWN: |
| ListScrollingUtil.movePageDown(getSelectedList()); |
| break; |
| case VK_HOME: |
| ListScrollingUtil.moveHome(getSelectedList()); |
| break; |
| case VK_PAGE_UP: |
| ListScrollingUtil.movePageUp(getSelectedList()); |
| break; |
| case VK_DELETE: |
| case VK_BACK_SPACE: // Mac users |
| case VK_Q: |
| closeTabOrToolWindow(); |
| break; |
| } |
| if (e.getKeyCode() == ALT_KEY) { |
| changeSelection(); |
| } |
| } |
| |
| private void changeSelection() { |
| if (isFilesSelected()) { |
| goLeft(); |
| } else { |
| goRight(); |
| } |
| } |
| |
| private void closeTabOrToolWindow() { |
| final int[] selected = getSelectedList().getSelectedIndices(); |
| Arrays.sort(selected); |
| int selectedIndex = 0; |
| for (int i = selected.length - 1; i>=0; i--) { |
| selectedIndex = selected[i]; |
| Object value = getSelectedList().getModel().getElementAt(selectedIndex); |
| if (value instanceof FileInfo) { |
| final FileInfo info = (FileInfo)value; |
| final VirtualFile virtualFile = info.first; |
| final FileEditorManagerImpl editorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project); |
| final JList jList = getSelectedList(); |
| final EditorWindow wnd = findAppropriateWindow(info); |
| if (wnd == null) { |
| editorManager.closeFile(virtualFile, false, false); |
| } |
| else { |
| editorManager.closeFile(virtualFile, wnd, false); |
| } |
| |
| final IdeFocusManager focusManager = IdeFocusManager.getInstance(project); |
| myAlarm.cancelAllRequests(); |
| myAlarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| focusManager.requestFocus(SwitcherPanel.this, true); |
| } |
| }, 300); |
| if (jList.getModel().getSize() == 1) { |
| goLeft(); |
| removeElementAt(jList, selectedIndex); |
| this.remove(jList); |
| this.remove(separator); |
| final Dimension size = toolWindows.getSize(); |
| myPopup.setSize(new Dimension(size.width, myPopup.getSize().height)); |
| } |
| else { |
| removeElementAt(jList, selectedIndex); |
| jList.setSize(jList.getPreferredSize()); |
| } |
| if (isPinnedMode()) { |
| EditorHistoryManager.getInstance(project).removeFile(virtualFile); |
| } |
| } |
| else if (value instanceof ToolWindow) { |
| final ToolWindow toolWindow = (ToolWindow)value; |
| if (twManager instanceof ToolWindowManagerImpl) { |
| ToolWindowManagerImpl manager = (ToolWindowManagerImpl)twManager; |
| manager.hideToolWindow(((ToolWindowImpl)toolWindow).getId(), false, false); |
| } |
| else { |
| toolWindow.hide(null); |
| } |
| } |
| } |
| pack(); |
| myPopup.getContent().revalidate(); |
| myPopup.getContent().repaint(); |
| if (getSelectedList().getModel().getSize() > selectedIndex) { |
| getSelectedList().setSelectedIndex(selectedIndex); |
| getSelectedList().ensureIndexIsVisible(selectedIndex); |
| } |
| } |
| |
| private static void removeElementAt(JList jList, int index) { |
| final ListModel model = jList.getModel(); |
| if (model instanceof DefaultListModel) { |
| ((DefaultListModel)model).removeElementAt(index); |
| } else if (model instanceof NameFilteringListModel) { |
| ((NameFilteringListModel)model).remove(index); |
| } else { |
| throw new IllegalArgumentException("Wrong list model " + model.getClass()); |
| } |
| } |
| |
| private void pack() { |
| this.setSize(this.getPreferredSize()); |
| final JRootPane rootPane = SwingUtilities.getRootPane(this); |
| Container container = this; |
| do { |
| container = container.getParent(); |
| container.setSize(container.getPreferredSize()); |
| } while (container != rootPane); |
| container.getParent().setSize(container.getPreferredSize()); |
| } |
| |
| private boolean isFilesSelected() { |
| return getSelectedList() == files; |
| } |
| |
| private boolean isFilesVisible() { |
| return files.getModel().getSize() > 0; |
| } |
| |
| private boolean isToolWindowsSelected() { |
| return getSelectedList() == toolWindows; |
| } |
| |
| private void goRight() { |
| if ((isFilesSelected() || !isFilesVisible()) && isAutoHide()) { |
| cancel(); |
| } |
| else { |
| if (files.getModel().getSize() > 0) { |
| final int index = Math.min(toolWindows.getSelectedIndex(), files.getModel().getSize() - 1); |
| files.setSelectedIndex(index); |
| files.ensureIndexIsVisible(index); |
| toolWindows.getSelectionModel().clearSelection(); |
| } |
| } |
| } |
| |
| private void cancel() { |
| myPopup.cancel(); |
| } |
| |
| private void goLeft() { |
| if (isToolWindowsSelected() && isAutoHide()) { |
| cancel(); |
| } |
| else { |
| if (toolWindows.getModel().getSize() > 0) { |
| toolWindows.setSelectedIndex(Math.min(files.getSelectedIndex(), toolWindows.getModel().getSize() - 1)); |
| files.getSelectionModel().clearSelection(); |
| } |
| } |
| } |
| |
| public void goForward() { |
| JList list = getSelectedList(); |
| int index = list.getSelectedIndex() + 1; |
| if (index >= list.getModel().getSize()) { |
| index = 0; |
| if (isFilesVisible()) { |
| list = isFilesSelected() ? toolWindows : files; |
| } |
| } |
| list.setSelectedIndex(index); |
| list.ensureIndexIsVisible(index); |
| } |
| |
| public void goBack() { |
| JList list = getSelectedList(); |
| int index = list.getSelectedIndex() - 1; |
| if (index < 0) { |
| if (isFilesVisible()) { |
| list = isFilesSelected() ? toolWindows : files; |
| } |
| index = list.getModel().getSize() - 1; |
| } |
| list.setSelectedIndex(index); |
| list.ensureIndexIsVisible(index); |
| } |
| |
| public MyList getSelectedList() { |
| return getSelectedList(files); |
| } |
| |
| MyList getSelectedList(MyList preferable) { |
| if (toolWindows.isSelectionEmpty() && files.isSelectionEmpty()) { |
| if (preferable != null && preferable.getModel().getSize() > 0) { |
| preferable.setSelectedIndex(0); |
| return preferable; |
| } else |
| |
| if (files.getModel().getSize() > 0) { |
| files.setSelectedIndex(0); |
| return files; |
| } else { |
| toolWindows.setSelectedIndex(0); |
| return toolWindows; |
| } |
| } |
| else { |
| return toolWindows.isSelectionEmpty() ? files : toolWindows; |
| } |
| } |
| |
| void navigate(final boolean openInNewWindow) { |
| final Object[] values = getSelectedList().getSelectedValues(); |
| myPopup.closeOk(null); |
| if (values.length > 0 && values[0] instanceof ToolWindow) { |
| ((ToolWindow)values[0]).activate(null, true, true); |
| } else{ |
| IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() { |
| @Override |
| public void run() { |
| final FileEditorManagerImpl manager = (FileEditorManagerImpl)FileEditorManager.getInstance(project); |
| for (Object value : values) { |
| if (value instanceof FileInfo) { |
| final FileInfo info = (FileInfo)value; |
| |
| VirtualFile file = info.first; |
| if (openInNewWindow) { |
| manager.openFileInNewWindow(file); |
| } |
| else if (info.second != null) { |
| EditorWindow wnd = findAppropriateWindow(info); |
| if (wnd != null) { |
| manager.openFileImpl2(wnd, file, true); |
| manager.addSelectionRecord(file, wnd); |
| } |
| } |
| else { |
| manager.openFile(file, true, true); |
| } |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| @Nullable |
| private static EditorWindow findAppropriateWindow(FileInfo info) { |
| if (info.second == null) return null; |
| final EditorWindow[] windows = info.second.getOwner().getWindows(); |
| return ArrayUtil.contains(info.second, windows) ? info.second : windows.length > 0 ? windows[0] : null; |
| } |
| |
| public void mouseClicked(@NotNull MouseEvent e) { |
| } |
| |
| private boolean mouseMovedFirstTime = true; |
| private JList mouseMoveSrc = null; |
| private int mouseMoveListIndex = -1; |
| public void mouseMoved(@NotNull MouseEvent e) { |
| if (mouseMovedFirstTime) { |
| mouseMovedFirstTime = false; |
| return; |
| } |
| final Object source = e.getSource(); |
| boolean changed = false; |
| if (source instanceof JList) { |
| JList list = (JList)source; |
| int index = list.locationToIndex(e.getPoint()); |
| if (0 <= index && index < list.getModel().getSize()) { |
| mouseMoveSrc = list; |
| mouseMoveListIndex = index; |
| changed = true; |
| } |
| } |
| if (!changed) { |
| mouseMoveSrc = null; |
| mouseMoveListIndex = -1; |
| } |
| |
| repaintLists(); |
| } |
| private void repaintLists() { |
| toolWindows.repaint(); |
| files.repaint(); |
| } |
| |
| public void mousePressed(@NotNull MouseEvent e) {} |
| public void mouseReleased(@NotNull MouseEvent e) {} |
| public void mouseEntered(@NotNull MouseEvent e) {} |
| public void mouseExited(@NotNull MouseEvent e) { |
| mouseMoveSrc = null; |
| mouseMoveListIndex = -1; |
| repaintLists(); |
| } |
| public void mouseDragged(@NotNull MouseEvent e) {} |
| |
| private class SwitcherSpeedSearch extends SpeedSearchBase<SwitcherPanel> implements PropertyChangeListener { |
| private Object[] myElements; |
| |
| public SwitcherSpeedSearch() { |
| super(SwitcherPanel.this); |
| addChangeListener(this); |
| setComparator(new SpeedSearchComparator(false, true)); |
| } |
| |
| @Override |
| protected void processKeyEvent(final KeyEvent e) { |
| final int keyCode = e.getKeyCode(); |
| if (keyCode == VK_LEFT || keyCode == VK_RIGHT) { |
| return; |
| } |
| if (keyCode == VK_ENTER && files.getModel().getSize() + toolWindows.getModel().getSize() == 0) { |
| AnAction gotoFile = ActionManager.getInstance().getAction("GotoFile"); |
| if (gotoFile != null) { |
| final String search = mySpeedSearch.getEnteredPrefix(); |
| myPopup.cancel(); |
| final AnAction action = gotoFile; |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| |
| DataManager.getInstance().getDataContextFromFocus().doWhenDone(new Consumer<DataContext>() { |
| @Override |
| public void consume(final DataContext context) { |
| final DataContext dataContext = new DataContext() { |
| @Nullable |
| @Override |
| public Object getData(@NonNls String dataId) { |
| if (PlatformDataKeys.PREDEFINED_TEXT.is(dataId)) { |
| return search; |
| } |
| return context.getData(dataId); |
| } |
| }; |
| final AnActionEvent event = |
| new AnActionEvent(e, dataContext, ActionPlaces.EDITOR_POPUP, new PresentationFactory().getPresentation(action), |
| ActionManager.getInstance(), 0); |
| action.actionPerformed(event); |
| } |
| }); |
| } |
| }); |
| return; |
| } |
| } |
| super.processKeyEvent(e); |
| } |
| |
| @Override |
| protected int getSelectedIndex() { |
| return isFilesSelected() |
| ? files.getSelectedIndex() |
| : files.getModel().getSize() + toolWindows.getSelectedIndex(); |
| } |
| |
| @Override |
| protected Object[] getAllElements() { |
| |
| final SwitcherPanel switcher = SwitcherPanel.this; |
| |
| ListModel filesModel = switcher.files.getModel(); |
| final Object[] files = new Object[filesModel.getSize()]; |
| for (int i = 0; i < files.length; i++) { |
| files[i] = filesModel.getElementAt(i); |
| } |
| |
| ListModel twModel = switcher.toolWindows.getModel(); |
| final Object[] toolWindows = new Object[twModel.getSize()]; |
| for (int i = 0; i < toolWindows.length; i++) { |
| toolWindows[i] = twModel.getElementAt(i); |
| } |
| |
| myElements = new Object[files.length + toolWindows.length]; |
| System.arraycopy(files, 0, myElements, 0, files.length); |
| System.arraycopy(toolWindows, 0, myElements, files.length, toolWindows.length); |
| |
| return myElements; |
| } |
| |
| |
| @Override |
| protected String getElementText(Object element) { |
| if (element instanceof ToolWindow) { |
| return ((ToolWindow)element).getStripeTitle(); |
| } else if (element instanceof FileInfo) { |
| final VirtualFile file = ((FileInfo)element).getFirst(); |
| return file instanceof VirtualFilePathWrapper ? ((VirtualFilePathWrapper)file).getPresentablePath() : file.getName(); |
| } |
| return ""; |
| } |
| |
| @Override |
| protected void selectElement(Object element, String selectedText) { |
| if (element instanceof FileInfo) { |
| if (!toolWindows.isSelectionEmpty()) toolWindows.clearSelection(); |
| files.setSelectedValue(element, false); |
| } else { |
| if (!files.isSelectionEmpty()) files.clearSelection(); |
| toolWindows.setSelectedValue(element, false); |
| } |
| } |
| |
| @Nullable |
| @Override |
| protected Object findElement(String s) { |
| final List<SpeedSearchObjectWithWeight> elements = SpeedSearchObjectWithWeight.findElement(s, this); |
| return elements.isEmpty() ? null : elements.get(0).node; |
| } |
| |
| @Override |
| public void propertyChange(@NotNull PropertyChangeEvent evt) { |
| final MyList list = getSelectedList(); |
| final Object value = list.getSelectedValue(); |
| if (project.isDisposed()) { |
| myPopup.cancel(); |
| return; |
| } |
| ((NameFilteringListModel)files.getModel()).refilter(); |
| ((NameFilteringListModel)toolWindows.getModel()).refilter(); |
| if (files.getModel().getSize() + toolWindows.getModel().getSize() == 0) { |
| toolWindows.getEmptyText().setText(""); |
| files.getEmptyText().setText("Press 'Enter' to search in Project"); |
| } else { |
| files.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT); |
| toolWindows.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT); |
| } |
| files.repaint(); |
| toolWindows.repaint(); |
| getSelectedList(list).setSelectedValue(value, true); |
| } |
| } |
| |
| public boolean isAutoHide() { |
| return !myPinned; |
| } |
| |
| public boolean isPinnedMode() { |
| return myPinned; |
| } |
| |
| private class SwitcherLayouter extends BorderLayout { |
| private Rectangle sBounds; |
| private Rectangle tBounds; |
| private Rectangle fBounds; |
| private Rectangle dBounds; |
| |
| @Override |
| public void layoutContainer(@NotNull Container target) { |
| final JScrollPane scrollPane = UIUtil.getParentOfType(JScrollPane.class, files); |
| JComponent filesPane = scrollPane != null ? scrollPane : files; |
| if (sBounds == null || !target.isShowing()) { |
| super.layoutContainer(target); |
| sBounds = separator.getBounds(); |
| tBounds = toolWindows.getBounds(); |
| fBounds = filesPane.getBounds(); |
| dBounds = descriptions.getBounds(); |
| } else { |
| final int h = target.getHeight(); |
| final int w = target.getWidth(); |
| sBounds.height = h - dBounds.height; |
| tBounds.height = h - dBounds.height; |
| fBounds.height = h - dBounds.height; |
| fBounds.width = w - sBounds.width - tBounds.width; |
| dBounds.width = w; |
| dBounds.y = h - dBounds.height; |
| separator.setBounds(sBounds); |
| toolWindows.setBounds(tBounds); |
| filesPane.setBounds(fBounds); |
| descriptions.setBounds(dBounds); |
| } |
| } |
| } |
| } |
| |
| private static class VirtualFilesRenderer extends ColoredListCellRenderer { |
| private final SwitcherPanel mySwitcherPanel; |
| boolean open; |
| |
| public VirtualFilesRenderer(@NotNull SwitcherPanel switcherPanel) { |
| mySwitcherPanel = switcherPanel; |
| } |
| |
| protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { |
| if (value instanceof FileInfo) { |
| Project project = mySwitcherPanel.project; |
| VirtualFile virtualFile = ((FileInfo)value).getFirst(); |
| String name = virtualFile instanceof VirtualFilePathWrapper |
| ? ((VirtualFilePathWrapper)virtualFile).getPresentablePath() |
| : UISettings.getInstance().SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES |
| ? UniqueVFilePathBuilder.getInstance().getUniqueVirtualFilePath(project, virtualFile) |
| : virtualFile.getName(); |
| setIcon(IconUtil.getIcon(virtualFile, Iconable.ICON_FLAG_READ_STATUS, project)); |
| |
| FileStatus fileStatus = FileStatusManager.getInstance(project).getStatus(virtualFile); |
| open = FileEditorManager.getInstance(project).isFileOpen(virtualFile); |
| TextAttributes attributes = new TextAttributes(fileStatus.getColor(), null , null, EffectType.LINE_UNDERSCORE, Font.PLAIN); |
| append(name, SimpleTextAttributes.fromTextAttributes(attributes)); |
| |
| // calc color the same way editor tabs do this, i.e. including extensions |
| Color color = EditorTabbedContainer.calcTabColor(project, virtualFile); |
| |
| if (!selected && color != null) { |
| setBackground(color); |
| } |
| SpeedSearchUtil.applySpeedSearchHighlighting(mySwitcherPanel, this, false, selected); |
| } |
| } |
| } |
| |
| private static class FileInfo extends Pair<VirtualFile, EditorWindow> { |
| public FileInfo(VirtualFile first, EditorWindow second) { |
| super(first, second); |
| } |
| } |
| |
| private static class MyList extends JBList { |
| public MyList(DefaultListModel model) { |
| super(model); |
| } |
| |
| @Override |
| public void processKeyEvent(@NotNull KeyEvent e) { |
| super.processKeyEvent(e); |
| } |
| } |
| } |