| /* |
| * Copyright 2000-2012 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.openapi.fileEditor.impl; |
| |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.actions.CloseAction; |
| import com.intellij.ide.ui.UISettings; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.DataKey; |
| import com.intellij.openapi.actionSystem.DataProvider; |
| import com.intellij.openapi.actionSystem.PlatformDataKeys; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.ScrollType; |
| import com.intellij.openapi.editor.ScrollingModel; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileEditor; |
| import com.intellij.openapi.fileEditor.FileEditorManagerListener; |
| import com.intellij.openapi.fileEditor.TextEditor; |
| import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; |
| import com.intellij.openapi.fileTypes.FileTypes; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.Splitter; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFileManager; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.openapi.wm.ToolWindowManager; |
| import com.intellij.ui.LayeredIcon; |
| import com.intellij.util.IconUtil; |
| import com.intellij.util.containers.Stack; |
| import com.intellij.util.ui.EmptyIcon; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.Border; |
| import java.awt.*; |
| import java.awt.event.FocusAdapter; |
| import java.awt.event.FocusEvent; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Author: msk |
| */ |
| public class EditorWindow { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorWindow"); |
| |
| public static final DataKey<EditorWindow> DATA_KEY = DataKey.create("editorWindow"); |
| |
| protected JPanel myPanel; |
| private EditorTabbedContainer myTabbedPane; |
| private final EditorsSplitters myOwner; |
| private static final Icon MODIFIED_ICON = AllIcons.General.Modified; |
| private static final Icon GAP_ICON = new EmptyIcon(MODIFIED_ICON.getIconWidth(), MODIFIED_ICON.getIconHeight()); |
| |
| private boolean myIsDisposed = false; |
| public static final Key<Integer> INITIAL_INDEX_KEY = Key.create("initial editor index"); |
| private Stack<Pair<String, Integer>> myRemovedTabs = new Stack<Pair<String, Integer>>() { |
| @Override |
| public void push(Pair<String, Integer> pair) { |
| if (size() >= UISettings.getInstance().EDITOR_TAB_LIMIT) { |
| remove(0); |
| } |
| super.push(pair); |
| } |
| }; |
| |
| protected EditorWindow(final EditorsSplitters owner) { |
| myOwner = owner; |
| myPanel = new JPanel(new BorderLayout()); |
| myPanel.setBorder(new AdaptiveBorder()); |
| myPanel.setOpaque(false); |
| |
| myTabbedPane = null; |
| |
| final int tabPlacement = UISettings.getInstance().EDITOR_TAB_PLACEMENT; |
| if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) { |
| createTabs(tabPlacement); |
| } |
| |
| // Tab layout policy |
| if (UISettings.getInstance().SCROLL_TAB_LAYOUT_IN_EDITOR) { |
| setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); |
| } else { |
| setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); |
| } |
| |
| getWindows().add(this); |
| if (myOwner.getCurrentWindow() == null) { |
| myOwner.setCurrentWindow(this, false); |
| } |
| } |
| |
| private void createTabs(int tabPlacement) { |
| LOG.assertTrue (myTabbedPane == null); |
| myTabbedPane = new EditorTabbedContainer(this, getManager().getProject(), tabPlacement); |
| myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); |
| } |
| |
| public boolean isShowing() { |
| return myPanel.isShowing(); |
| } |
| |
| public void closeAllExcept(final VirtualFile selectedFile) { |
| final VirtualFile[] files = getFiles(); |
| for (final VirtualFile file : files) { |
| if (!Comparing.equal(file, selectedFile) && !isFilePinned(file)) { |
| closeFile(file); |
| } |
| } |
| } |
| |
| private static class AdaptiveBorder implements Border { |
| @Override |
| public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { |
| Insets insets = ((JComponent)c).getInsets(); |
| g.setColor(UIUtil.getPanelBackground()); |
| paintBorder(g, x, y, width, height, insets); |
| g.setColor(new Color(0, 0, 0, 90)); |
| paintBorder(g, x, y, width, height, insets); |
| } |
| |
| private static void paintBorder(Graphics g, int x, int y, int width, int height, Insets insets) { |
| if (insets.left == 1) { |
| g.drawLine(x, y, x, y + height); |
| } |
| |
| if (insets.right == 1) { |
| g.drawLine(x + width - 1, y, x + width - 1, y + height); |
| } |
| |
| if (insets.bottom == 1) { |
| g.drawLine(x, y + height - 1, x + width, y + height - 1); |
| } |
| } |
| |
| @Override |
| public Insets getBorderInsets(Component c) { |
| Container parent = c.getParent(); |
| if (parent instanceof Splitter) { |
| boolean editorToTheLeft = false; |
| boolean editorToTheRight = false; |
| boolean editorToTheDown = false; |
| |
| Splitter splitter = (Splitter)parent; |
| |
| boolean vertical = splitter.getOrientation(); |
| if (vertical && splitter.getFirstComponent() == c) { |
| editorToTheDown = true; |
| } else if (!vertical) { |
| if (splitter.getFirstComponent() == c) { |
| editorToTheRight = true; |
| } |
| if (splitter.getSecondComponent() == c) editorToTheLeft = true; |
| } |
| |
| |
| //Frame frame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, c); |
| //if (frame instanceof IdeFrame) { |
| // Project project = ((IdeFrame)frame).getProject(); |
| // ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(project); |
| // if (!editorToTheLeft) { |
| // List<String> left = toolWindowManager.getIdsOn(ToolWindowAnchor.LEFT); |
| // if (left.size() > 0) { |
| // for (String lid : left) { |
| // ToolWindow window = toolWindowManager.getToolWindow(lid); |
| // editorToTheLeft = window != null && window.isVisible() && window.getType() == ToolWindowType.DOCKED; |
| // if (editorToTheLeft) break; |
| // } |
| // } |
| // } |
| // |
| // if (!editorToTheRight) { |
| // List<String> right = toolWindowManager.getIdsOn(ToolWindowAnchor.RIGHT); |
| // if (right.size() > 0) { |
| // for (String lid : right) { |
| // ToolWindow window = toolWindowManager.getToolWindow(lid); |
| // editorToTheRight = window != null && window.isVisible() && window.getType() == ToolWindowType.DOCKED; |
| // if (editorToTheRight) break; |
| // } |
| // } |
| // } |
| //} |
| |
| Splitter outer = nextOuterSplitter(splitter); |
| if (outer != null) { |
| boolean outerVertical = outer.getOrientation(); |
| if (!outerVertical) { |
| if (splitter.getParent() == outer.getFirstComponent()) editorToTheRight = true; |
| if (splitter.getParent() == outer.getSecondComponent()) editorToTheLeft = true; |
| } else { |
| if (splitter.getParent() == outer.getFirstComponent()) { |
| editorToTheDown = true; |
| } |
| } |
| } |
| |
| int left = editorToTheLeft ? 1 : 0; |
| int right = editorToTheRight ? 1 : 0; |
| int bottom = editorToTheDown ? 1 : 0; |
| return new Insets(0, left, bottom, right); |
| } |
| |
| return new Insets(0, 0, 0, 0); |
| } |
| |
| @Nullable |
| private static Splitter nextOuterSplitter(Component c) { |
| Container parent = c.getParent(); |
| if (parent == null) return null; |
| Container grandParent = parent.getParent(); |
| if (grandParent instanceof Splitter) return (Splitter)grandParent; |
| return null; |
| } |
| |
| @Override |
| public boolean isBorderOpaque() { |
| return true; |
| } |
| } |
| |
| private Set<EditorWindow> getWindows() { |
| return myOwner.myWindows; |
| } |
| |
| private void dispose() { |
| try { |
| disposeTabs(); |
| getWindows ().remove(this); |
| } |
| finally { |
| myIsDisposed = true; |
| } |
| } |
| |
| public boolean isDisposed() { |
| return myIsDisposed; |
| } |
| |
| private void disposeTabs() { |
| if (myTabbedPane != null) { |
| Disposer.dispose(myTabbedPane); |
| myTabbedPane = null; |
| } |
| myPanel.removeAll(); |
| myPanel.revalidate(); |
| } |
| |
| public void closeFile(final VirtualFile file) { |
| closeFile (file, true); |
| } |
| |
| public void closeFile(final VirtualFile file, final boolean unsplit) { |
| closeFile(file, unsplit, true); |
| } |
| |
| public boolean hasClosedTabs() { |
| return !myRemovedTabs.empty(); |
| } |
| |
| public void restoreClosedTab() { |
| assert hasClosedTabs() : "Nothing to restore"; |
| |
| final Pair<String, Integer> info = myRemovedTabs.pop(); |
| final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(info.getFirst()); |
| final Integer second = info.getSecond(); |
| if (file != null) { |
| getManager().openFileImpl4(this, file, true, null, true, second == null ? -1 : second.intValue()); |
| } |
| } |
| |
| public void closeFile(final VirtualFile file, final boolean unsplit, final boolean transferFocus) { |
| final FileEditorManagerImpl editorManager = getManager(); |
| editorManager.runChange(new FileEditorManagerChange() { |
| @Override |
| public void run(EditorsSplitters splitters) { |
| final List<EditorWithProviderComposite> editors = splitters.findEditorComposites(file); |
| if (editors.isEmpty()) return; |
| try { |
| final EditorWithProviderComposite editor = findFileComposite(file); |
| |
| final FileEditorManagerListener.Before beforePublisher = |
| editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER); |
| |
| beforePublisher.beforeFileClosed(editorManager, file); |
| |
| if (myTabbedPane != null && editor != null) { |
| final int componentIndex = findComponentIndex(editor.getComponent()); |
| if (componentIndex >= 0) { // editor could close itself on decomposition |
| final int indexToSelect = calcIndexToSelect(file, componentIndex); |
| myRemovedTabs.push(Pair.create(file.getUrl(), componentIndex)); |
| final ActionCallback removeTab = myTabbedPane.removeTabAt(componentIndex, indexToSelect, transferFocus); |
| final Runnable disposer = new Runnable() { |
| @Override |
| public void run() { |
| editorManager.disposeComposite(editor); |
| } |
| }; |
| disposer.run(); |
| //removeTab.doWhenDone(disposer); |
| } |
| } |
| else { |
| myPanel.removeAll (); |
| if (editor != null) { |
| editorManager.disposeComposite(editor); |
| } |
| } |
| |
| if (unsplit && getTabCount() == 0) { |
| unsplit (true); |
| } |
| myPanel.revalidate (); |
| if (myTabbedPane == null) { |
| // in tabless mode |
| myPanel.repaint(); |
| } |
| } |
| finally { |
| editorManager.removeSelectionRecord(file, EditorWindow.this); |
| |
| editorManager.notifyPublisher(new Runnable() { |
| @Override |
| public void run() { |
| final Project project = editorManager.getProject(); |
| if (!project.isDisposed()) { |
| final FileEditorManagerListener afterPublisher = |
| project.getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); |
| afterPublisher.fileClosed(editorManager, file); |
| } |
| } |
| }); |
| |
| splitters.afterFileClosed(file); |
| } |
| } |
| }, myOwner); |
| } |
| |
| private int calcIndexToSelect(VirtualFile fileBeingClosed, final int fileIndex) { |
| final int currentlySelectedIndex = myTabbedPane.getSelectedIndex(); |
| if (currentlySelectedIndex != fileIndex) { |
| // if the file being closed is not currently selected, keep the currently selected file open |
| return currentlySelectedIndex; |
| } |
| UISettings uiSettings = UISettings.getInstance(); |
| if (uiSettings.ACTIVATE_MRU_EDITOR_ON_CLOSE) { |
| // try to open last visited file |
| final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); |
| for (int idx = histFiles.length - 1; idx >= 0; idx--) { |
| final VirtualFile histFile = histFiles[idx]; |
| if (histFile.equals(fileBeingClosed)) { |
| continue; |
| } |
| final EditorWithProviderComposite editor = findFileComposite(histFile); |
| if (editor == null) { |
| continue; // ???? |
| } |
| final int histFileIndex = findComponentIndex(editor.getComponent()); |
| if (histFileIndex >= 0) { |
| // if the file being closed is located before the hist file, then after closing the index of the histFile will be shifted by -1 |
| return histFileIndex; |
| } |
| } |
| } else |
| if (uiSettings.ACTIVATE_RIGHT_EDITOR_ON_CLOSE && (fileIndex + 1 < myTabbedPane.getTabCount())) { |
| return fileIndex + 1; |
| } |
| |
| // by default select previous neighbour |
| if (fileIndex > 0) { |
| return fileIndex - 1; |
| } |
| // do nothing |
| return -1; |
| } |
| |
| public FileEditorManagerImpl getManager() { return myOwner.getManager(); } |
| |
| public int getTabCount() { |
| if (myTabbedPane != null) { |
| return myTabbedPane.getTabCount(); |
| } |
| return myPanel.getComponentCount(); |
| } |
| |
| public void setForegroundAt(final int index, final Color color) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setForegroundAt(index, color); |
| } |
| } |
| |
| public void setWaveColor(final int index, @Nullable final Color color) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setWaveColor(index, color); |
| } |
| } |
| |
| private void setIconAt(final int index, final Icon icon) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setIconAt(index, icon); |
| } |
| } |
| |
| private void setTitleAt(final int index, final String text) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setTitleAt(index, text); |
| } |
| } |
| |
| private void setBackgroundColorAt(final int index, final Color color) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setBackgroundColorAt(index, color); |
| } |
| } |
| |
| private void setToolTipTextAt(final int index, final String text) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setToolTipTextAt(index, text); |
| } |
| } |
| |
| |
| public void setTabLayoutPolicy(final int policy) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setTabLayoutPolicy(policy); |
| } |
| } |
| |
| public void setTabsPlacement(final int tabPlacement) { |
| if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) { |
| if (myTabbedPane == null) { |
| final EditorWithProviderComposite editor = getSelectedEditor(); |
| myPanel.removeAll(); |
| createTabs(tabPlacement); |
| setEditor (editor, true); |
| } |
| else { |
| myTabbedPane.setTabPlacement(tabPlacement); |
| } |
| } |
| else if (myTabbedPane != null) { |
| final boolean focusEditor = ToolWindowManager.getInstance(getManager().getProject()).isEditorComponentActive(); |
| final VirtualFile currentFile = getSelectedFile(); |
| if (currentFile != null) { |
| // do not close associated language console on tab placement change |
| currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, Boolean.TRUE); |
| } |
| final VirtualFile[] files = getFiles(); |
| for (VirtualFile file : files) { |
| closeFile(file, false); |
| } |
| disposeTabs(); |
| if (currentFile != null) { |
| currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null); |
| getManager().openFileImpl2(this, currentFile, focusEditor && myOwner.getCurrentWindow() == this); |
| } |
| else { |
| myPanel.repaint(); |
| } |
| } |
| } |
| |
| public void setAsCurrentWindow(final boolean requestFocus) { |
| myOwner.setCurrentWindow(this, requestFocus); |
| } |
| |
| public void updateFileBackgroundColor(final VirtualFile file) { |
| final int index = findEditorIndex(findFileComposite(file)); |
| if (index != -1) { |
| final Color color = EditorTabbedContainer.calcTabColor(getManager().getProject(), file); |
| setBackgroundColorAt(index, color); |
| } |
| } |
| |
| public EditorsSplitters getOwner() { |
| return myOwner; |
| } |
| |
| public boolean isEmptyVisible() { |
| return myTabbedPane != null ? myTabbedPane.isEmptyVisible() : getFiles().length == 0; |
| } |
| |
| public Dimension getSize() { |
| return myPanel.getSize(); |
| } |
| |
| @Nullable |
| public EditorTabbedContainer getTabbedPane() { |
| return myTabbedPane; |
| } |
| |
| public void requestFocus(boolean forced) { |
| if (myTabbedPane != null) { |
| myTabbedPane.requestFocus(forced); |
| } |
| } |
| |
| public boolean isValid() { |
| return myPanel.isShowing(); |
| } |
| |
| public void setPaintBlocked(boolean blocked) { |
| if (myTabbedPane != null) { |
| myTabbedPane.setPaintBlocked(blocked); |
| } |
| } |
| |
| protected static class TComp extends JPanel implements DataProvider, EditorWindowHolder { |
| final EditorWithProviderComposite myEditor; |
| protected final EditorWindow myWindow; |
| |
| TComp(final EditorWindow window, final EditorWithProviderComposite editor) { |
| super(new BorderLayout()); |
| myEditor = editor; |
| myWindow = window; |
| add(editor.getComponent(), BorderLayout.CENTER); |
| addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusGained(FocusEvent e) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (!TComp.this.hasFocus()) return; |
| final JComponent focus = myEditor.getSelectedEditorWithProvider().getFirst().getPreferredFocusedComponent(); |
| if (focus != null && !focus.hasFocus()) { |
| IdeFocusManager.getGlobalInstance().requestFocus(focus, true); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| @Override |
| public EditorWindow getEditorWindow() { |
| return myWindow; |
| } |
| |
| @Override |
| public Object getData(String dataId) { |
| if (CommonDataKeys.VIRTUAL_FILE.is(dataId)){ |
| final VirtualFile virtualFile = myEditor.getFile(); |
| return virtualFile.isValid() ? virtualFile : null; |
| } |
| else if (CommonDataKeys.PROJECT.is(dataId)) { |
| return myEditor.getFileEditorManager().getProject(); |
| } |
| return null; |
| } |
| } |
| |
| protected static class TCompForTablessMode extends TComp implements CloseAction.CloseTarget { |
| TCompForTablessMode(final EditorWindow window, final EditorWithProviderComposite editor) { |
| super(window, editor); |
| } |
| |
| @Override |
| public Object getData(String dataId) { |
| // this is essential for ability to close opened file |
| if (DATA_KEY.is(dataId)){ |
| return myWindow; |
| } |
| if (CloseAction.CloseTarget.KEY.is(dataId)) { |
| return this; |
| } |
| return super.getData(dataId); |
| } |
| |
| @Override |
| public void close() { |
| myWindow.closeFile(myEditor.getFile()); |
| } |
| } |
| |
| private void checkConsistency() { |
| LOG.assertTrue(getWindows().contains(this), "EditorWindow not in collection"); |
| } |
| |
| public EditorWithProviderComposite getSelectedEditor() { |
| final TComp comp; |
| if (myTabbedPane != null) { |
| comp = (TComp)myTabbedPane.getSelectedComponent(); |
| } |
| else if (myPanel.getComponentCount() != 0) { |
| final Component component = myPanel.getComponent(0); |
| comp = component instanceof TComp ? (TComp)component : null; |
| } |
| else { |
| return null; |
| } |
| |
| if (comp != null) { |
| return comp.myEditor; |
| } |
| return null; |
| } |
| |
| public EditorWithProviderComposite[] getEditors() { |
| final int tabCount = getTabCount(); |
| final EditorWithProviderComposite[] res = new EditorWithProviderComposite[tabCount]; |
| for (int i = 0; i != tabCount; ++i) { |
| res[i] = getEditorAt(i); |
| } |
| return res; |
| } |
| |
| public VirtualFile[] getFiles() { |
| final int tabCount = getTabCount(); |
| final VirtualFile[] res = new VirtualFile[tabCount]; |
| for (int i = 0; i != tabCount; ++i) { |
| res[i] = getEditorAt(i).getFile(); |
| } |
| return res; |
| } |
| |
| public void setSelectedEditor(final EditorComposite editor, final boolean focusEditor) { |
| if (myTabbedPane == null) { |
| return; |
| } |
| if (editor != null) { |
| final int index = findFileIndex(editor.getFile()); |
| if (index != -1) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| myTabbedPane.setSelectedIndex(index, focusEditor); |
| } |
| }); |
| } |
| } |
| } |
| |
| public void setEditor(@Nullable final EditorWithProviderComposite editor, final boolean focusEditor) { |
| if (editor != null) { |
| if (myTabbedPane == null) { |
| myPanel.removeAll (); |
| myPanel.add (new TCompForTablessMode(this, editor), BorderLayout.CENTER); |
| myPanel.revalidate (); |
| return; |
| } |
| |
| final int index = findEditorIndex(editor); |
| if (index != -1) { |
| setSelectedEditor(editor, focusEditor); |
| } |
| else { |
| Integer initialIndex = editor.getFile().getUserData(INITIAL_INDEX_KEY); |
| int indexToInsert = 0; |
| if (initialIndex == null) { |
| int selectedIndex = myTabbedPane.getSelectedIndex(); |
| if (selectedIndex >= 0) { |
| indexToInsert = UISettings.getInstance().ACTIVATE_RIGHT_EDITOR_ON_CLOSE ? selectedIndex : selectedIndex + 1; |
| } |
| } else { |
| indexToInsert = initialIndex; |
| } |
| |
| final VirtualFile file = editor.getFile(); |
| final Icon template = AllIcons.FileTypes.Text; |
| myTabbedPane.insertTab(file, new EmptyIcon(template.getIconWidth(), template.getIconHeight()), new TComp(this, editor), null, indexToInsert); |
| trimToSize(UISettings.getInstance().EDITOR_TAB_LIMIT, file, false); |
| setSelectedEditor(editor, focusEditor); |
| myOwner.updateFileIcon(file); |
| myOwner.updateFileColor(file); |
| } |
| myOwner.setCurrentWindow(this, false); |
| } |
| myPanel.revalidate(); |
| } |
| |
| private boolean splitAvailable() { |
| return getTabCount() >= 1; |
| } |
| |
| @Nullable |
| public EditorWindow split(final int orientation, boolean forceSplit, @Nullable VirtualFile virtualFile, boolean focusNew) { |
| checkConsistency(); |
| final FileEditorManagerImpl fileEditorManager = myOwner.getManager(); |
| if (splitAvailable()) { |
| if (!forceSplit && inSplitter()) { |
| final EditorWindow[] siblings = findSiblings(); |
| final EditorWindow target = siblings[0]; |
| if (virtualFile != null) { |
| final FileEditor[] editors = fileEditorManager.openFileImpl3(target, virtualFile, focusNew, null, true).first; |
| syncCaretIfPossible(editors); |
| } |
| return target; |
| } |
| final JPanel panel = myPanel; |
| panel.setBorder(null); |
| final int tabCount = getTabCount(); |
| if (tabCount != 0) { |
| final EditorWithProviderComposite firstEC = getEditorAt(0); |
| myPanel = new JPanel(new BorderLayout()); |
| myPanel.setOpaque(false); |
| myPanel.setBorder(new AdaptiveBorder()); |
| myPanel.setOpaque(false); |
| |
| final Splitter splitter = new Splitter(orientation == JSplitPane.VERTICAL_SPLIT, 0.5f, 0.1f, 0.9f); |
| final EditorWindow res = new EditorWindow(myOwner); |
| if (myTabbedPane != null) { |
| final EditorWithProviderComposite selectedEditor = getSelectedEditor(); |
| panel.remove(myTabbedPane.getComponent()); |
| panel.add(splitter, BorderLayout.CENTER); |
| splitter.setFirstComponent(myPanel); |
| myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); |
| splitter.setSecondComponent(res.myPanel); |
| /* |
| for (int i = 0; i != tabCount; ++i) { |
| final EditorWithProviderComposite eC = getEditorAt(i); |
| final VirtualFile file = eC.getFile(); |
| fileEditorManager.openFileImpl3(res, file, false, null); |
| res.setFilePinned (file, isFilePinned (file)); |
| } |
| */ |
| // open only selected file in the new splitter instead of opening all tabs |
| final VirtualFile file = selectedEditor.getFile(); |
| |
| if (virtualFile == null) { |
| for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) { |
| VirtualFile associatedFile = finder.getAssociatedFileToOpen(fileEditorManager.getProject(), file); |
| |
| if (associatedFile != null) { |
| virtualFile = associatedFile; |
| break; |
| } |
| } |
| } |
| |
| final VirtualFile nextFile = virtualFile == null ? file : virtualFile; |
| final FileEditor[] editors = fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true).first; |
| syncCaretIfPossible(editors); |
| res.setFilePinned (nextFile, isFilePinned (file)); |
| if (!focusNew) { |
| res.setSelectedEditor(selectedEditor, true); |
| selectedEditor.getComponent().requestFocus(); |
| } |
| panel.revalidate(); |
| } |
| else { |
| panel.removeAll(); |
| panel.add(splitter, BorderLayout.CENTER); |
| splitter.setFirstComponent(myPanel); |
| splitter.setSecondComponent(res.myPanel); |
| panel.revalidate(); |
| final VirtualFile firstFile = firstEC.getFile(); |
| final VirtualFile nextFile = virtualFile == null ? firstFile : virtualFile; |
| final FileEditor[] firstEditors = fileEditorManager.openFileImpl3(this, firstFile, !focusNew, null, true).first; |
| syncCaretIfPossible(firstEditors); |
| final FileEditor[] secondEditors = fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true).first; |
| syncCaretIfPossible(secondEditors); |
| } |
| return res; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Tries to setup caret and viewport for the given editor from the selected one. |
| * |
| * @param toSync editor to setup caret and viewport for |
| */ |
| private void syncCaretIfPossible(@Nullable FileEditor[] toSync) { |
| if (toSync == null) { |
| return; |
| } |
| |
| final EditorWithProviderComposite from = getSelectedEditor(); |
| if (from == null) { |
| return; |
| } |
| |
| final FileEditor caretSource = from.getSelectedEditor(); |
| if (!(caretSource instanceof TextEditor)) { |
| return; |
| } |
| |
| final Editor editorFrom = ((TextEditor)caretSource).getEditor(); |
| final int offset = editorFrom.getCaretModel().getOffset(); |
| if (offset <= 0) { |
| return; |
| } |
| |
| final int scrollOffset = editorFrom.getScrollingModel().getVerticalScrollOffset(); |
| |
| for (FileEditor fileEditor : toSync) { |
| if (!(fileEditor instanceof TextEditor)) { |
| continue; |
| } |
| final Editor editor = ((TextEditor)fileEditor).getEditor(); |
| if (editorFrom.getDocument() == editor.getDocument()) { |
| editor.getCaretModel().moveToOffset(offset); |
| final ScrollingModel scrollingModel = editor.getScrollingModel(); |
| scrollingModel.scrollVertically(scrollOffset); |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (!editor.isDisposed()) { |
| scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE); |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| public EditorWindow[] findSiblings() { |
| checkConsistency(); |
| final ArrayList<EditorWindow> res = new ArrayList<EditorWindow>(); |
| if (myPanel.getParent() instanceof Splitter) { |
| final Splitter splitter = (Splitter)myPanel.getParent(); |
| for (final EditorWindow win : getWindows()) { |
| if (win != this && SwingUtilities.isDescendingFrom(win.myPanel, splitter)) { |
| res.add(win); |
| } |
| } |
| } |
| return res.toArray(new EditorWindow[res.size()]); |
| } |
| |
| public void changeOrientation() { |
| checkConsistency(); |
| final Container parent = myPanel.getParent(); |
| if (parent instanceof Splitter) { |
| final Splitter splitter = (Splitter)parent; |
| splitter.setOrientation(!splitter.getOrientation()); |
| } |
| } |
| |
| protected void updateFileIcon(VirtualFile file) { |
| final int index = findEditorIndex(findFileComposite(file)); |
| LOG.assertTrue(index != -1); |
| setIconAt(index, getFileIcon(file)); |
| } |
| |
| protected void updateFileName(VirtualFile file) { |
| final int index = findEditorIndex(findFileComposite(file)); |
| if (index != -1) { |
| setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file)); |
| setToolTipTextAt(index, getManager().getFileTooltipText(file)); |
| } |
| } |
| |
| /** |
| * @return icon which represents file's type and modification status |
| */ |
| private Icon getFileIcon(@NotNull final VirtualFile file) { |
| if (!file.isValid()) { |
| Icon fakeIcon = FileTypes.UNKNOWN.getIcon(); |
| assert fakeIcon != null : "Can't find the icon for unknown file type"; |
| return fakeIcon; |
| } |
| |
| final Icon baseIcon = IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, getManager().getProject()); |
| |
| int count = 1; |
| |
| final Icon pinIcon; |
| final EditorComposite composite = findFileComposite(file); |
| if (composite != null && composite.isPinned()) { |
| count++; |
| pinIcon = AllIcons.Nodes.TabPin; |
| } |
| else { |
| pinIcon = null; |
| } |
| |
| final Icon modifiedIcon; |
| if (UISettings.getInstance().MARK_MODIFIED_TABS_WITH_ASTERISK) { |
| modifiedIcon = composite != null && composite.isModified() ? MODIFIED_ICON : GAP_ICON; |
| count++; |
| } |
| else { |
| modifiedIcon = null; |
| } |
| |
| if (count == 1) return baseIcon; |
| |
| int i = 0; |
| final LayeredIcon result = new LayeredIcon(count); |
| result.setIcon(baseIcon, i++); |
| if (pinIcon != null) result.setIcon(pinIcon, i++); |
| if (modifiedIcon != null) result.setIcon(modifiedIcon, i++); |
| |
| return result; |
| } |
| |
| public void unsplit(boolean setCurrent) { |
| checkConsistency(); |
| final Container splitter = myPanel.getParent(); |
| |
| if (!(splitter instanceof Splitter)) return; |
| |
| EditorWithProviderComposite editorToSelect = getSelectedEditor(); |
| final EditorWindow[] siblings = findSiblings(); |
| final JPanel parent = (JPanel)splitter.getParent(); |
| |
| for (EditorWindow eachSibling : siblings) { |
| // selected editors will be added first |
| final EditorWithProviderComposite selected = eachSibling.getSelectedEditor(); |
| if (editorToSelect == null) { |
| editorToSelect = selected; |
| } |
| } |
| |
| for (final EditorWindow sibling : siblings) { |
| final EditorWithProviderComposite[] siblingEditors = sibling.getEditors(); |
| for (final EditorWithProviderComposite siblingEditor : siblingEditors) { |
| if (editorToSelect == null) { |
| editorToSelect = siblingEditor; |
| } |
| processSiblingEditor(siblingEditor); |
| } |
| LOG.assertTrue(sibling != this); |
| sibling.dispose(); |
| } |
| parent.remove(splitter); |
| if (myTabbedPane != null) { |
| parent.add(myTabbedPane.getComponent(), BorderLayout.CENTER); |
| } |
| else { |
| if (myPanel.getComponentCount() > 0) { |
| parent.add(myPanel.getComponent(0), BorderLayout.CENTER); |
| } |
| } |
| parent.revalidate(); |
| myPanel = parent; |
| if (editorToSelect != null) { |
| setSelectedEditor(editorToSelect, true); |
| } |
| if (setCurrent) { |
| myOwner.setCurrentWindow(this, false); |
| } |
| } |
| |
| private void processSiblingEditor(final EditorWithProviderComposite siblingEditor) { |
| if (myTabbedPane != null && getTabCount() < UISettings.getInstance().EDITOR_TAB_LIMIT && findFileComposite(siblingEditor.getFile()) == null) { |
| setEditor(siblingEditor, true); |
| } |
| else if (myTabbedPane == null && getTabCount() == 0) { // tabless mode and no file opened |
| setEditor(siblingEditor, true); |
| } |
| else { |
| getManager().disposeComposite(siblingEditor); |
| } |
| } |
| |
| public void unsplitAll() { |
| checkConsistency(); |
| while (inSplitter()) { |
| unsplit(true); |
| } |
| } |
| |
| public boolean inSplitter() { |
| checkConsistency(); |
| return myPanel.getParent() instanceof Splitter; |
| } |
| |
| public VirtualFile getSelectedFile() { |
| checkConsistency(); |
| final EditorWithProviderComposite editor = getSelectedEditor(); |
| return editor == null ? null : editor.getFile(); |
| } |
| |
| @Nullable |
| public EditorWithProviderComposite findFileComposite(final VirtualFile file) { |
| for (int i = 0; i != getTabCount(); ++i) { |
| final EditorWithProviderComposite editor = getEditorAt(i); |
| if (editor.getFile().equals(file)) { |
| return editor; |
| } |
| } |
| return null; |
| } |
| |
| |
| public int findComponentIndex(final Component component) { |
| for (int i = 0; i != getTabCount(); ++i) { |
| final EditorWithProviderComposite editor = getEditorAt(i); |
| if (editor.getComponent ().equals (component)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| public int findEditorIndex(final EditorComposite editorToFind) { |
| for (int i = 0; i != getTabCount(); ++i) { |
| final EditorWithProviderComposite editor = getEditorAt(i); |
| if (editor.equals (editorToFind)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| public int findFileIndex(final VirtualFile fileToFind) { |
| for (int i = 0; i != getTabCount(); ++i) { |
| final VirtualFile file = getFileAt(i); |
| if (file.equals (fileToFind)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| private EditorWithProviderComposite getEditorAt(final int i) { |
| final TComp comp; |
| if (myTabbedPane != null) { |
| comp = (TComp)myTabbedPane.getComponentAt(i); |
| } |
| else { |
| LOG.assertTrue(i <= 1); |
| comp = (TComp)myPanel.getComponent(i); |
| } |
| return comp.myEditor; |
| } |
| |
| public boolean isFileOpen(final VirtualFile file) { |
| return findFileComposite(file) != null; |
| } |
| |
| public boolean isFilePinned(final VirtualFile file) { |
| final EditorComposite editorComposite = findFileComposite(file); |
| if (editorComposite == null) { |
| throw new IllegalArgumentException("file is not open: " + file.getPath()); |
| } |
| return editorComposite.isPinned(); |
| } |
| |
| public void setFilePinned(final VirtualFile file, final boolean pinned) { |
| final EditorComposite editorComposite = findFileComposite(file); |
| if (editorComposite == null) { |
| throw new IllegalArgumentException("file is not open: " + file.getPath()); |
| } |
| boolean wasPinned = editorComposite.isPinned(); |
| editorComposite.setPinned(pinned); |
| if (wasPinned != pinned && ApplicationManager.getApplication().isDispatchThread()) { |
| updateFileIcon(file); |
| } |
| } |
| |
| void trimToSize(final int limit, @Nullable final VirtualFile fileToIgnore, final boolean transferFocus) { |
| if (myTabbedPane == null) return; |
| |
| FileEditorManagerEx.getInstanceEx(getManager().getProject()).getReady(this).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (myTabbedPane == null) return; |
| final boolean closeNonModifiedFilesFirst = UISettings.getInstance().CLOSE_NON_MODIFIED_FILES_FIRST; |
| final EditorComposite selectedComposite = getSelectedEditor(); |
| try { |
| doTrimSize(limit, fileToIgnore, closeNonModifiedFilesFirst, transferFocus); |
| } |
| finally { |
| setSelectedEditor(selectedComposite, false); |
| } |
| } |
| }); |
| } |
| |
| private void doTrimSize(int limit, @Nullable VirtualFile fileToIgnore, boolean closeNonModifiedFilesFirst, boolean transferFocus) { |
| while_label: |
| while (myTabbedPane.getTabCount() > limit && myTabbedPane.getTabCount() > 0) { |
| // If all tabs are pinned then do nothings. Othrwise we will get infinitive loop |
| boolean allTabsArePinned = true; |
| for (int i = myTabbedPane.getTabCount() - 1; i >= 0; i--) { |
| final VirtualFile file = getFileAt(i); |
| if (fileCanBeClosed(file, fileToIgnore)) { |
| allTabsArePinned = false; |
| break; |
| } |
| } |
| if (allTabsArePinned) { |
| return; |
| } |
| |
| // Try to close non-modified files first (is specified in option) |
| if (closeNonModifiedFilesFirst) { |
| // Search in history |
| final VirtualFile[] allFiles = getFiles(); |
| final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); |
| |
| // first, we search for files not in history |
| for (int i = 0; i != allFiles.length; ++ i) { |
| final VirtualFile file = allFiles[i]; |
| if (fileCanBeClosed(file, fileToIgnore)) { |
| boolean found = false; |
| for (int j = 0; j != histFiles.length; j++) { |
| if (Comparing.equal(histFiles[j], file)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| } |
| } |
| |
| for (final VirtualFile file : histFiles) { |
| if (!fileCanBeClosed(file, fileToIgnore)) { |
| continue; |
| } |
| |
| final EditorComposite composite = findFileComposite(file); |
| //LOG.assertTrue(composite != null); |
| if (composite != null && composite.getInitialFileTimeStamp() == file.getTimeStamp()) { |
| // we found non modified file |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| } |
| |
| // Search in tabbed pane |
| final VirtualFile selectedFile = getSelectedFile(); |
| for (int i = 0; i < myTabbedPane.getTabCount(); i++) { |
| final VirtualFile file = getFileAt(i); |
| final EditorComposite composite = getEditorAt(i); |
| if (!fileCanBeClosed(file, fileToIgnore)) { |
| continue; |
| } |
| if (!selectedFile.equals(file)) { |
| if (composite.getInitialFileTimeStamp() == file.getTimeStamp()) { |
| // we found non modified file |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| } |
| } |
| } |
| |
| // It's non enough to close non-modified files only. Try all other files. |
| // Search in history from less frequently used. |
| { |
| final VirtualFile[] allFiles = getFiles(); |
| final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); |
| |
| // first, we search for files not in history |
| for (int i = 0; i != allFiles.length; ++ i) { |
| final VirtualFile file = allFiles[i]; |
| if (fileCanBeClosed(file, fileToIgnore)) { |
| boolean found = false; |
| for (int j = 0; j != histFiles.length; j++) { |
| if (Comparing.equal(histFiles[j], file)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| } |
| } |
| |
| |
| for (final VirtualFile file : histFiles) { |
| if (fileCanBeClosed(file, fileToIgnore)) { |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| } |
| } |
| |
| // Close first opened file in tabbed pane that isn't a selected one |
| { |
| final VirtualFile selectedFile = getSelectedFile(); |
| for (int i = 0; i < myTabbedPane.getTabCount(); i++) { |
| final VirtualFile file = getFileAt(i); |
| if (!fileCanBeClosed(file, fileToIgnore)) { |
| continue; |
| } |
| if (!selectedFile.equals(file)) { |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| else if (i == myTabbedPane.getTabCount() - 1) { |
| // if file is selected one and it's last file that we have no choice as close it |
| defaultCloseFile(file, transferFocus); |
| continue while_label; |
| } |
| } |
| } |
| } |
| } |
| |
| private void defaultCloseFile(VirtualFile file, boolean transferFocus) { |
| closeFile(file, true, transferFocus); |
| } |
| |
| private boolean fileCanBeClosed(final VirtualFile file, @Nullable final VirtualFile fileToIgnore) { |
| return isFileOpen (file) && !file.equals(fileToIgnore) && !isFilePinned(file); |
| } |
| |
| protected VirtualFile getFileAt(int i) { |
| return getEditorAt(i).getFile(); |
| } |
| |
| @Override |
| public String toString() { |
| return "EditorWindow: files=" + Arrays.asList(getFiles()); |
| } |
| } |