2 * Copyright 2000-2014 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.openapi.fileEditor.impl;
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.actions.CloseAction;
20 import com.intellij.ide.ui.UISettings;
21 import com.intellij.openapi.actionSystem.CommonDataKeys;
22 import com.intellij.openapi.actionSystem.DataKey;
23 import com.intellij.openapi.actionSystem.DataProvider;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.editor.ScrollType;
28 import com.intellij.openapi.editor.ScrollingModel;
29 import com.intellij.openapi.extensions.Extensions;
30 import com.intellij.openapi.fileEditor.FileEditor;
31 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
32 import com.intellij.openapi.fileEditor.TextEditor;
33 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
34 import com.intellij.openapi.fileTypes.FileTypes;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.ui.GraphicsConfig;
37 import com.intellij.openapi.ui.Splitter;
38 import com.intellij.openapi.ui.ThreeComponentsSplitter;
39 import com.intellij.openapi.util.*;
40 import com.intellij.openapi.util.registry.Registry;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.openapi.vfs.VirtualFileManager;
43 import com.intellij.openapi.wm.IdeFocusManager;
44 import com.intellij.openapi.wm.ToolWindowManager;
45 import com.intellij.ui.JBColor;
46 import com.intellij.ui.LayeredIcon;
47 import com.intellij.util.IconUtil;
48 import com.intellij.util.containers.ContainerUtil;
49 import com.intellij.util.containers.Stack;
50 import com.intellij.util.ui.EmptyIcon;
51 import com.intellij.util.ui.GraphicsUtil;
52 import com.intellij.util.ui.UIUtil;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
57 import javax.swing.border.Border;
59 import java.awt.event.FocusAdapter;
60 import java.awt.event.FocusEvent;
62 import java.util.List;
67 public class EditorWindow {
68 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorWindow");
70 public static final DataKey<EditorWindow> DATA_KEY = DataKey.create("editorWindow");
72 protected JPanel myPanel;
73 private EditorTabbedContainer myTabbedPane;
74 private final EditorsSplitters myOwner;
75 private static final Icon MODIFIED_ICON = Registry.is("editor.use.compressible.tabs") ? new Icon() {
77 public void paintIcon(Component c, Graphics g, int x, int y) {
78 GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
79 Font oldFont = g.getFont();
81 g.setFont(UIUtil.getLabelFont());
82 g.setColor(JBColor.foreground());
83 g.drawString("*", 0, 10);
91 public int getIconWidth() {
96 public int getIconHeight() {
99 } : AllIcons.General.Modified;
100 private static final Icon GAP_ICON = new EmptyIcon(MODIFIED_ICON.getIconWidth(), MODIFIED_ICON.getIconHeight());
102 private boolean myIsDisposed = false;
103 public static final Key<Integer> INITIAL_INDEX_KEY = Key.create("initial editor index");
104 private final Stack<Pair<String, Integer>> myRemovedTabs = new Stack<Pair<String, Integer>>() {
106 public void push(Pair<String, Integer> pair) {
107 if (size() >= UISettings.getInstance().EDITOR_TAB_LIMIT) {
114 protected EditorWindow(final EditorsSplitters owner) {
116 myPanel = new JPanel(new BorderLayout());
117 myPanel.setBorder(new AdaptiveBorder());
118 myPanel.setOpaque(false);
122 final int tabPlacement = UISettings.getInstance().EDITOR_TAB_PLACEMENT;
123 if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) {
124 createTabs(tabPlacement);
128 if (UISettings.getInstance().SCROLL_TAB_LAYOUT_IN_EDITOR) {
129 setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
131 setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
134 myOwner.addWindow(this);
135 if (myOwner.getCurrentWindow() == null) {
136 myOwner.setCurrentWindow(this, false);
140 private void createTabs(int tabPlacement) {
141 LOG.assertTrue (myTabbedPane == null);
142 myTabbedPane = new EditorTabbedContainer(this, getManager().getProject(), tabPlacement);
143 myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
146 public boolean isShowing() {
147 return myPanel.isShowing();
150 public void closeAllExcept(final VirtualFile selectedFile) {
151 final VirtualFile[] files = getFiles();
152 for (final VirtualFile file : files) {
153 if (!Comparing.equal(file, selectedFile) && !isFilePinned(file)) {
159 private static class AdaptiveBorder implements Border {
161 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
162 Insets insets = ((JComponent)c).getInsets();
163 g.setColor(UIUtil.getPanelBackground());
164 paintBorder(g, x, y, width, height, insets);
165 g.setColor(new Color(0, 0, 0, 90));
166 paintBorder(g, x, y, width, height, insets);
169 private static void paintBorder(Graphics g, int x, int y, int width, int height, Insets insets) {
170 if (insets.left == 1) {
171 g.drawLine(x, y, x, y + height);
174 if (insets.right == 1) {
175 g.drawLine(x + width - 1, y, x + width - 1, y + height);
178 if (insets.bottom == 1) {
179 g.drawLine(x, y + height - 1, x + width, y + height - 1);
184 public Insets getBorderInsets(Component c) {
185 Container parent = c.getParent();
186 if (parent instanceof Splitter) {
187 boolean editorToTheLeft = false;
188 boolean editorToTheRight = false;
189 boolean editorToTheDown = false;
191 Splitter splitter = (Splitter)parent;
193 boolean vertical = splitter.getOrientation();
194 if (vertical && splitter.getFirstComponent() == c) {
195 editorToTheDown = true;
196 } else if (!vertical) {
197 if (splitter.getFirstComponent() == c) {
198 editorToTheRight = true;
200 if (splitter.getSecondComponent() == c) editorToTheLeft = true;
204 //Frame frame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, c);
205 //if (frame instanceof IdeFrame) {
206 // Project project = ((IdeFrame)frame).getProject();
207 // ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(project);
208 // if (!editorToTheLeft) {
209 // List<String> left = toolWindowManager.getIdsOn(ToolWindowAnchor.LEFT);
210 // if (left.size() > 0) {
211 // for (String lid : left) {
212 // ToolWindow window = toolWindowManager.getToolWindow(lid);
213 // editorToTheLeft = window != null && window.isVisible() && window.getType() == ToolWindowType.DOCKED;
214 // if (editorToTheLeft) break;
219 // if (!editorToTheRight) {
220 // List<String> right = toolWindowManager.getIdsOn(ToolWindowAnchor.RIGHT);
221 // if (right.size() > 0) {
222 // for (String lid : right) {
223 // ToolWindow window = toolWindowManager.getToolWindow(lid);
224 // editorToTheRight = window != null && window.isVisible() && window.getType() == ToolWindowType.DOCKED;
225 // if (editorToTheRight) break;
231 Splitter outer = nextOuterSplitter(splitter);
233 boolean outerVertical = outer.getOrientation();
234 if (!outerVertical) {
235 if (splitter.getParent() == outer.getFirstComponent()) editorToTheRight = true;
236 if (splitter.getParent() == outer.getSecondComponent()) editorToTheLeft = true;
238 if (splitter.getParent() == outer.getFirstComponent()) {
239 editorToTheDown = true;
244 int left = editorToTheLeft ? 1 : 0;
245 int right = editorToTheRight ? 1 : 0;
246 int bottom = editorToTheDown ? 1 : 0;
247 return new Insets(0, left, bottom, right);
250 return new Insets(0, 0, 0, 0);
254 private static Splitter nextOuterSplitter(Component c) {
255 Container parent = c.getParent();
256 if (parent == null) return null;
257 Container grandParent = parent.getParent();
258 if (grandParent instanceof Splitter) return (Splitter)grandParent;
263 public boolean isBorderOpaque() {
271 myOwner.removeWindow(this);
278 public boolean isDisposed() {
282 private void disposeTabs() {
283 if (myTabbedPane != null) {
284 Disposer.dispose(myTabbedPane);
288 myPanel.revalidate();
291 public void closeFile(final VirtualFile file) {
292 closeFile(file, true);
295 public void closeFile(final VirtualFile file, final boolean disposeIfNeeded) {
296 closeFile(file, disposeIfNeeded, true);
299 public boolean hasClosedTabs() {
300 return !myRemovedTabs.empty();
303 public void restoreClosedTab() {
304 assert hasClosedTabs() : "Nothing to restore";
306 final Pair<String, Integer> info = myRemovedTabs.pop();
307 final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(info.getFirst());
308 final Integer second = info.getSecond();
310 getManager().openFileImpl4(this, file, null, true, true, null, second == null ? -1 : second.intValue());
314 public void closeFile(@NotNull final VirtualFile file, final boolean disposeIfNeeded, final boolean transferFocus) {
315 final FileEditorManagerImpl editorManager = getManager();
316 editorManager.runChange(new FileEditorManagerChange() {
318 public void run(EditorsSplitters splitters) {
319 final List<EditorWithProviderComposite> editors = splitters.findEditorComposites(file);
320 if (editors.isEmpty()) return;
322 final EditorWithProviderComposite editor = findFileComposite(file);
324 final FileEditorManagerListener.Before beforePublisher =
325 editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER);
327 beforePublisher.beforeFileClosed(editorManager, file);
329 if (myTabbedPane != null && editor != null) {
330 final int componentIndex = findComponentIndex(editor.getComponent());
331 if (componentIndex >= 0) { // editor could close itself on decomposition
332 final int indexToSelect = calcIndexToSelect(file, componentIndex);
333 myRemovedTabs.push(Pair.create(file.getUrl(), componentIndex));
334 myTabbedPane.removeTabAt(componentIndex, indexToSelect, transferFocus);
335 editorManager.disposeComposite(editor);
339 myPanel.removeAll ();
340 if (editor != null) {
341 editorManager.disposeComposite(editor);
345 if (disposeIfNeeded && getTabCount() == 0) {
346 removeFromSplitter();
347 if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE) {
348 final EditorsSplitters owner = getOwner();
350 final ThreeComponentsSplitter splitter = UIUtil.getParentOfType(ThreeComponentsSplitter.class, owner);
351 if (splitter != null) {
352 splitter.revalidate();
359 myPanel.revalidate();
360 if (myTabbedPane == null) {
367 editorManager.removeSelectionRecord(file, EditorWindow.this);
369 editorManager.notifyPublisher(new Runnable() {
372 final Project project = editorManager.getProject();
373 if (!project.isDisposed()) {
374 final FileEditorManagerListener afterPublisher =
375 project.getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
376 afterPublisher.fileClosed(editorManager, file);
381 splitters.afterFileClosed(file);
387 private void removeFromSplitter() {
388 if (!inSplitter()) return;
390 if (myOwner.getCurrentWindow() == this) {
391 EditorWindow[] siblings = findSiblings();
392 myOwner.setCurrentWindow(siblings[0], false);
395 Splitter splitter = (Splitter)myPanel.getParent();
396 JComponent otherComponent = splitter.getOtherComponent(myPanel);
398 Container parent = splitter.getParent().getParent();
399 if (parent instanceof Splitter) {
400 Splitter parentSplitter = (Splitter)parent;
401 if (parentSplitter.getFirstComponent() == splitter.getParent()) {
402 parentSplitter.setFirstComponent(otherComponent);
405 parentSplitter.setSecondComponent(otherComponent);
408 else if (parent instanceof EditorsSplitters) {
410 parent.add(otherComponent, BorderLayout.CENTER);
411 ((JComponent)parent).revalidate();
414 throw new IllegalStateException("Unknown container: " + parent);
420 private int calcIndexToSelect(VirtualFile fileBeingClosed, final int fileIndex) {
421 final int currentlySelectedIndex = myTabbedPane.getSelectedIndex();
422 if (currentlySelectedIndex != fileIndex) {
423 // if the file being closed is not currently selected, keep the currently selected file open
424 return currentlySelectedIndex;
426 UISettings uiSettings = UISettings.getInstance();
427 if (uiSettings.ACTIVATE_MRU_EDITOR_ON_CLOSE) {
428 // try to open last visited file
429 final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles();
430 for (int idx = histFiles.length - 1; idx >= 0; idx--) {
431 final VirtualFile histFile = histFiles[idx];
432 if (histFile.equals(fileBeingClosed)) {
435 final EditorWithProviderComposite editor = findFileComposite(histFile);
436 if (editor == null) {
439 final int histFileIndex = findComponentIndex(editor.getComponent());
440 if (histFileIndex >= 0) {
441 // if the file being closed is located before the hist file, then after closing the index of the histFile will be shifted by -1
442 return histFileIndex;
446 if (uiSettings.ACTIVATE_RIGHT_EDITOR_ON_CLOSE && (fileIndex + 1 < myTabbedPane.getTabCount())) {
447 return fileIndex + 1;
450 // by default select previous neighbour
452 return fileIndex - 1;
458 public FileEditorManagerImpl getManager() { return myOwner.getManager(); }
460 public int getTabCount() {
461 if (myTabbedPane != null) {
462 return myTabbedPane.getTabCount();
464 return myPanel.getComponentCount();
467 public void setForegroundAt(final int index, final Color color) {
468 if (myTabbedPane != null) {
469 myTabbedPane.setForegroundAt(index, color);
473 public void setWaveColor(final int index, @Nullable final Color color) {
474 if (myTabbedPane != null) {
475 myTabbedPane.setWaveColor(index, color);
479 private void setIconAt(final int index, final Icon icon) {
480 if (myTabbedPane != null) {
481 myTabbedPane.setIconAt(index, icon);
485 private void setTitleAt(final int index, final String text) {
486 if (myTabbedPane != null) {
487 myTabbedPane.setTitleAt(index, text);
491 private boolean isTitleShortenedAt(int index) {
492 return myTabbedPane != null && myTabbedPane.isTitleShortened(index);
495 private void setBackgroundColorAt(final int index, final Color color) {
496 if (myTabbedPane != null) {
497 myTabbedPane.setBackgroundColorAt(index, color);
501 private void setToolTipTextAt(final int index, final String text) {
502 if (myTabbedPane != null) {
503 myTabbedPane.setToolTipTextAt(index, text);
508 public void setTabLayoutPolicy(final int policy) {
509 if (myTabbedPane != null) {
510 myTabbedPane.setTabLayoutPolicy(policy);
514 public void setTabsPlacement(final int tabPlacement) {
515 if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) {
516 if (myTabbedPane == null) {
517 final EditorWithProviderComposite editor = getSelectedEditor();
519 createTabs(tabPlacement);
520 setEditor (editor, true);
523 myTabbedPane.setTabPlacement(tabPlacement);
526 else if (myTabbedPane != null) {
527 final boolean focusEditor = ToolWindowManager.getInstance(getManager().getProject()).isEditorComponentActive();
528 final VirtualFile currentFile = getSelectedFile();
529 if (currentFile != null) {
530 // do not close associated language console on tab placement change
531 currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, Boolean.TRUE);
533 final VirtualFile[] files = getFiles();
534 for (VirtualFile file : files) {
535 closeFile(file, false);
538 if (currentFile != null) {
539 currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null);
540 getManager().openFileImpl2(this, currentFile, focusEditor && myOwner.getCurrentWindow() == this);
548 public void setAsCurrentWindow(final boolean requestFocus) {
549 myOwner.setCurrentWindow(this, requestFocus);
552 public void updateFileBackgroundColor(@NotNull VirtualFile file) {
553 final int index = findEditorIndex(findFileComposite(file));
555 final Color color = EditorTabbedContainer.calcTabColor(getManager().getProject(), file);
556 setBackgroundColorAt(index, color);
560 public EditorsSplitters getOwner() {
564 public boolean isEmptyVisible() {
565 return myTabbedPane != null ? myTabbedPane.isEmptyVisible() : getFiles().length == 0;
568 public Dimension getSize() {
569 return myPanel.getSize();
573 public EditorTabbedContainer getTabbedPane() {
577 public void requestFocus(boolean forced) {
578 if (myTabbedPane != null) {
579 myTabbedPane.requestFocus(forced);
582 EditorWithProviderComposite editor = getSelectedEditor();
583 JComponent preferred = editor == null ? null : editor.getPreferredFocusedComponent();
584 IdeFocusManager.findInstanceByComponent(preferred == null ? myPanel : preferred).requestFocus(myPanel, forced);
588 public boolean isValid() {
589 return myPanel.isShowing();
592 public void setPaintBlocked(boolean blocked) {
593 if (myTabbedPane != null) {
594 myTabbedPane.setPaintBlocked(blocked);
598 protected static class TComp extends JPanel implements DataProvider, EditorWindowHolder {
599 @NotNull final EditorWithProviderComposite myEditor;
600 protected final EditorWindow myWindow;
602 TComp(@NotNull EditorWindow window, @NotNull EditorWithProviderComposite editor) {
603 super(new BorderLayout());
606 add(editor.getComponent(), BorderLayout.CENTER);
607 addFocusListener(new FocusAdapter() {
609 public void focusGained(FocusEvent e) {
610 ApplicationManager.getApplication().invokeLater(new Runnable() {
613 if (!TComp.this.hasFocus()) return;
614 final JComponent focus = myEditor.getSelectedEditorWithProvider().getFirst().getPreferredFocusedComponent();
615 if (focus != null && !focus.hasFocus()) {
616 IdeFocusManager.getGlobalInstance().requestFocus(focus, true);
626 public EditorWindow getEditorWindow() {
631 public Object getData(String dataId) {
632 if (CommonDataKeys.VIRTUAL_FILE.is(dataId)){
633 final VirtualFile virtualFile = myEditor.getFile();
634 return virtualFile.isValid() ? virtualFile : null;
636 else if (CommonDataKeys.PROJECT.is(dataId)) {
637 return myEditor.getFileEditorManager().getProject();
643 protected static class TCompForTablessMode extends TComp implements CloseAction.CloseTarget {
644 TCompForTablessMode(@NotNull EditorWindow window, @NotNull EditorWithProviderComposite editor) {
645 super(window, editor);
649 public Object getData(String dataId) {
650 // this is essential for ability to close opened file
651 if (DATA_KEY.is(dataId)){
654 if (CloseAction.CloseTarget.KEY.is(dataId)) {
657 return super.getData(dataId);
661 public void close() {
662 myWindow.closeFile(myEditor.getFile());
666 private void checkConsistency() {
667 LOG.assertTrue(myOwner.containsWindow(this), "EditorWindow not in collection");
670 public EditorWithProviderComposite getSelectedEditor() {
672 if (myTabbedPane != null) {
673 comp = (TComp)myTabbedPane.getSelectedComponent();
675 else if (myPanel.getComponentCount() != 0) {
676 final Component component = myPanel.getComponent(0);
677 comp = component instanceof TComp ? (TComp)component : null;
684 return comp.myEditor;
689 public EditorWithProviderComposite[] getEditors() {
690 final int tabCount = getTabCount();
691 final EditorWithProviderComposite[] res = new EditorWithProviderComposite[tabCount];
692 for (int i = 0; i != tabCount; ++i) {
693 res[i] = getEditorAt(i);
698 public VirtualFile[] getFiles() {
699 final int tabCount = getTabCount();
700 final VirtualFile[] res = new VirtualFile[tabCount];
701 for (int i = 0; i != tabCount; ++i) {
702 res[i] = getEditorAt(i).getFile();
707 public void setSelectedEditor(final EditorComposite editor, final boolean focusEditor) {
708 if (myTabbedPane == null) {
711 if (editor != null) {
712 final int index = findFileIndex(editor.getFile());
714 UIUtil.invokeLaterIfNeeded(new Runnable() {
717 if (myTabbedPane != null) {
718 myTabbedPane.setSelectedIndex(index, focusEditor);
726 public void setEditor(@Nullable final EditorWithProviderComposite editor, final boolean focusEditor) {
727 setEditor(editor, true, focusEditor);
730 public void setEditor(@Nullable final EditorWithProviderComposite editor, final boolean selectEditor, final boolean focusEditor) {
731 if (editor != null) {
732 onBeforeSetEditor(editor.getFile());
733 if (myTabbedPane == null) {
734 myPanel.removeAll ();
735 myPanel.add (new TCompForTablessMode(this, editor), BorderLayout.CENTER);
740 final int index = findEditorIndex(editor);
743 setSelectedEditor(editor, focusEditor);
747 Integer initialIndex = editor.getFile().getUserData(INITIAL_INDEX_KEY);
748 int indexToInsert = 0;
749 if (Registry.is("ide.editor.tabs.open.at.the.end")) {
750 indexToInsert = myTabbedPane.getTabCount();
752 if (initialIndex == null) {
753 int selectedIndex = myTabbedPane.getSelectedIndex();
754 if (selectedIndex >= 0) {
755 indexToInsert = selectedIndex + 1;
758 indexToInsert = initialIndex;
762 final VirtualFile file = editor.getFile();
763 final Icon template = AllIcons.FileTypes.Text;
764 myTabbedPane.insertTab(file, new EmptyIcon(template.getIconWidth(), template.getIconHeight()), new TComp(this, editor), null, indexToInsert);
765 trimToSize(UISettings.getInstance().EDITOR_TAB_LIMIT, file, false);
767 setSelectedEditor(editor, focusEditor);
769 myOwner.updateFileIcon(file);
770 myOwner.updateFileColor(file);
772 myOwner.setCurrentWindow(this, false);
777 protected void onBeforeSetEditor(VirtualFile file) {
780 private boolean splitAvailable() {
781 return getTabCount() >= 1;
785 public EditorWindow split(final int orientation, boolean forceSplit, @Nullable VirtualFile virtualFile, boolean focusNew) {
787 final FileEditorManagerImpl fileEditorManager = myOwner.getManager();
788 if (splitAvailable()) {
789 if (!forceSplit && inSplitter()) {
790 final EditorWindow[] siblings = findSiblings();
791 final EditorWindow target = siblings[0];
792 if (virtualFile != null) {
793 final FileEditor[] editors = fileEditorManager.openFileImpl3(target, virtualFile, focusNew, null, true).first;
794 syncCaretIfPossible(editors);
798 final JPanel panel = myPanel;
799 panel.setBorder(null);
800 final int tabCount = getTabCount();
802 final EditorWithProviderComposite firstEC = getEditorAt(0);
803 myPanel = new JPanel(new BorderLayout());
804 myPanel.setOpaque(false);
805 myPanel.setBorder(new AdaptiveBorder());
806 myPanel.setOpaque(false);
808 final Splitter splitter = new Splitter(orientation == JSplitPane.VERTICAL_SPLIT, 0.5f, 0.1f, 0.9f);
809 final EditorWindow res = new EditorWindow(myOwner);
810 if (myTabbedPane != null) {
811 final EditorWithProviderComposite selectedEditor = getSelectedEditor();
812 panel.remove(myTabbedPane.getComponent());
813 panel.add(splitter, BorderLayout.CENTER);
814 splitter.setFirstComponent(myPanel);
815 myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
816 splitter.setSecondComponent(res.myPanel);
818 for (int i = 0; i != tabCount; ++i) {
819 final EditorWithProviderComposite eC = getEditorAt(i);
820 final VirtualFile file = eC.getFile();
821 fileEditorManager.openFileImpl3(res, file, false, null);
822 res.setFilePinned (file, isFilePinned (file));
825 // open only selected file in the new splitter instead of opening all tabs
826 final VirtualFile file = selectedEditor.getFile();
828 if (virtualFile == null) {
829 for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
830 VirtualFile associatedFile = finder.getAssociatedFileToOpen(fileEditorManager.getProject(), file);
832 if (associatedFile != null) {
833 virtualFile = associatedFile;
839 final VirtualFile nextFile = virtualFile == null ? file : virtualFile;
840 final FileEditor[] editors = fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true).first;
841 syncCaretIfPossible(editors);
842 res.setFilePinned (nextFile, isFilePinned (file));
844 res.setSelectedEditor(selectedEditor, true);
845 selectedEditor.getComponent().requestFocus();
851 panel.add(splitter, BorderLayout.CENTER);
852 splitter.setFirstComponent(myPanel);
853 splitter.setSecondComponent(res.myPanel);
855 final VirtualFile firstFile = firstEC.getFile();
856 final VirtualFile nextFile = virtualFile == null ? firstFile : virtualFile;
857 final FileEditor[] firstEditors = fileEditorManager.openFileImpl3(this, firstFile, !focusNew, null, true).first;
858 syncCaretIfPossible(firstEditors);
859 final FileEditor[] secondEditors = fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true).first;
860 syncCaretIfPossible(secondEditors);
869 * Tries to setup caret and viewport for the given editor from the selected one.
871 * @param toSync editor to setup caret and viewport for
873 private void syncCaretIfPossible(@Nullable FileEditor[] toSync) {
874 if (toSync == null) {
878 final EditorWithProviderComposite from = getSelectedEditor();
883 final FileEditor caretSource = from.getSelectedEditor();
884 if (!(caretSource instanceof TextEditor)) {
888 final Editor editorFrom = ((TextEditor)caretSource).getEditor();
889 final int offset = editorFrom.getCaretModel().getOffset();
894 final int scrollOffset = editorFrom.getScrollingModel().getVerticalScrollOffset();
896 for (FileEditor fileEditor : toSync) {
897 if (!(fileEditor instanceof TextEditor)) {
900 final Editor editor = ((TextEditor)fileEditor).getEditor();
901 if (editorFrom.getDocument() == editor.getDocument()) {
902 editor.getCaretModel().moveToOffset(offset);
903 final ScrollingModel scrollingModel = editor.getScrollingModel();
904 scrollingModel.scrollVertically(scrollOffset);
906 SwingUtilities.invokeLater(new Runnable() {
909 if (!editor.isDisposed()) {
910 scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE);
918 public EditorWindow[] findSiblings() {
920 final ArrayList<EditorWindow> res = new ArrayList<EditorWindow>();
921 if (myPanel.getParent() instanceof Splitter) {
922 final Splitter splitter = (Splitter)myPanel.getParent();
923 for (final EditorWindow win : myOwner.getWindows()) {
924 if (win != this && SwingUtilities.isDescendingFrom(win.myPanel, splitter)) {
929 return res.toArray(new EditorWindow[res.size()]);
932 public void changeOrientation() {
934 final Container parent = myPanel.getParent();
935 if (parent instanceof Splitter) {
936 final Splitter splitter = (Splitter)parent;
937 splitter.setOrientation(!splitter.getOrientation());
941 protected void updateFileIcon(VirtualFile file) {
942 final int index = findEditorIndex(findFileComposite(file));
943 LOG.assertTrue(index != -1);
944 setIconAt(index, getFileIcon(file));
947 protected void updateFileName(VirtualFile file) {
948 final int index = findEditorIndex(findFileComposite(file));
950 setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file));
951 setToolTipTextAt(index, UISettings.getInstance().SHOW_TABS_TOOLTIPS || isTitleShortenedAt(index)
952 ? getManager().getFileTooltipText(file)
958 * @return icon which represents file's type and modification status
960 private Icon getFileIcon(@NotNull final VirtualFile file) {
961 if (!file.isValid()) {
962 Icon fakeIcon = FileTypes.UNKNOWN.getIcon();
963 assert fakeIcon != null : "Can't find the icon for unknown file type";
967 final Icon baseIcon = IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, getManager().getProject());
972 final EditorComposite composite = findFileComposite(file);
973 if (composite != null && composite.isPinned()) {
975 pinIcon = AllIcons.Nodes.TabPin;
981 final Icon modifiedIcon;
982 if (UISettings.getInstance().MARK_MODIFIED_TABS_WITH_ASTERISK) {
983 modifiedIcon = composite != null && composite.isModified() ? MODIFIED_ICON : GAP_ICON;
990 if (count == 1) return baseIcon;
993 final LayeredIcon result = new LayeredIcon(count);
994 int xShift = Registry.is("editor.use.compressible.tabs") ? 4 : 0;
995 result.setIcon(baseIcon, i++, xShift, 0);
996 if (pinIcon != null) result.setIcon(pinIcon, i++, xShift, 0);
997 if (modifiedIcon != null) result.setIcon(modifiedIcon, i++);
1002 public void unsplit(boolean setCurrent) {
1004 final Container splitter = myPanel.getParent();
1006 if (!(splitter instanceof Splitter)) return;
1008 EditorWithProviderComposite editorToSelect = getSelectedEditor();
1009 final EditorWindow[] siblings = findSiblings();
1010 final JPanel parent = (JPanel)splitter.getParent();
1012 for (EditorWindow eachSibling : siblings) {
1013 // selected editors will be added first
1014 final EditorWithProviderComposite selected = eachSibling.getSelectedEditor();
1015 if (editorToSelect == null) {
1016 editorToSelect = selected;
1020 for (final EditorWindow sibling : siblings) {
1021 final EditorWithProviderComposite[] siblingEditors = sibling.getEditors();
1022 for (final EditorWithProviderComposite siblingEditor : siblingEditors) {
1023 if (editorToSelect == null) {
1024 editorToSelect = siblingEditor;
1026 processSiblingEditor(siblingEditor);
1028 LOG.assertTrue(sibling != this);
1031 parent.remove(splitter);
1032 if (myTabbedPane != null) {
1033 parent.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
1036 if (myPanel.getComponentCount() > 0) {
1037 parent.add(myPanel.getComponent(0), BorderLayout.CENTER);
1040 parent.revalidate();
1042 if (editorToSelect != null) {
1043 setSelectedEditor(editorToSelect, true);
1046 myOwner.setCurrentWindow(this, false);
1050 private void processSiblingEditor(final EditorWithProviderComposite siblingEditor) {
1051 if (myTabbedPane != null && getTabCount() < UISettings.getInstance().EDITOR_TAB_LIMIT && findFileComposite(siblingEditor.getFile()) == null) {
1052 setEditor(siblingEditor, true);
1054 else if (myTabbedPane == null && getTabCount() == 0) { // tabless mode and no file opened
1055 setEditor(siblingEditor, true);
1058 getManager().disposeComposite(siblingEditor);
1062 public void unsplitAll() {
1064 while (inSplitter()) {
1069 public boolean inSplitter() {
1071 return myPanel.getParent() instanceof Splitter;
1074 public VirtualFile getSelectedFile() {
1076 final EditorWithProviderComposite editor = getSelectedEditor();
1077 return editor == null ? null : editor.getFile();
1081 public EditorWithProviderComposite findFileComposite(final VirtualFile file) {
1082 for (int i = 0; i != getTabCount(); ++i) {
1083 final EditorWithProviderComposite editor = getEditorAt(i);
1084 if (editor.getFile().equals(file)) {
1092 public int findComponentIndex(final Component component) {
1093 for (int i = 0; i != getTabCount(); ++i) {
1094 final EditorWithProviderComposite editor = getEditorAt(i);
1095 if (editor.getComponent ().equals (component)) {
1102 public int findEditorIndex(final EditorComposite editorToFind) {
1103 for (int i = 0; i != getTabCount(); ++i) {
1104 final EditorWithProviderComposite editor = getEditorAt(i);
1105 if (editor.equals (editorToFind)) {
1112 public int findFileIndex(final VirtualFile fileToFind) {
1113 for (int i = 0; i != getTabCount(); ++i) {
1114 final VirtualFile file = getFileAt(i);
1115 if (file.equals (fileToFind)) {
1122 private EditorWithProviderComposite getEditorAt(final int i) {
1124 if (myTabbedPane != null) {
1125 comp = (TComp)myTabbedPane.getComponentAt(i);
1128 LOG.assertTrue(i <= 1);
1129 comp = (TComp)myPanel.getComponent(i);
1131 return comp.myEditor;
1134 public boolean isFileOpen(final VirtualFile file) {
1135 return findFileComposite(file) != null;
1138 public boolean isFilePinned(final VirtualFile file) {
1139 final EditorComposite editorComposite = findFileComposite(file);
1140 if (editorComposite == null) {
1141 throw new IllegalArgumentException("file is not open: " + file.getPath());
1143 return editorComposite.isPinned();
1146 public void setFilePinned(final VirtualFile file, final boolean pinned) {
1147 final EditorComposite editorComposite = findFileComposite(file);
1148 if (editorComposite == null) {
1149 throw new IllegalArgumentException("file is not open: " + file.getPath());
1151 boolean wasPinned = editorComposite.isPinned();
1152 editorComposite.setPinned(pinned);
1153 if (wasPinned != pinned && ApplicationManager.getApplication().isDispatchThread()) {
1154 updateFileIcon(file);
1158 void trimToSize(final int limit, @Nullable final VirtualFile fileToIgnore, final boolean transferFocus) {
1159 if (myTabbedPane == null) return;
1161 FileEditorManagerEx.getInstanceEx(getManager().getProject()).getReady(this).doWhenDone(new Runnable() {
1164 if (myTabbedPane == null) return;
1165 final boolean closeNonModifiedFilesFirst = UISettings.getInstance().CLOSE_NON_MODIFIED_FILES_FIRST;
1166 final EditorComposite selectedComposite = getSelectedEditor();
1168 doTrimSize(limit, fileToIgnore, closeNonModifiedFilesFirst, transferFocus);
1171 setSelectedEditor(selectedComposite, false);
1177 private void doTrimSize(int limit, @Nullable VirtualFile fileToIgnore, boolean closeNonModifiedFilesFirst, boolean transferFocus) {
1178 LinkedHashSet<VirtualFile> closingOrder = getTabClosingOrder(closeNonModifiedFilesFirst);
1180 for (VirtualFile file : closingOrder) {
1181 if (myTabbedPane.getTabCount() <= limit || myTabbedPane.getTabCount() == 0 || areAllTabsPinned(fileToIgnore)) {
1184 if (fileCanBeClosed(file, fileToIgnore)) {
1185 defaultCloseFile(file, transferFocus);
1191 private LinkedHashSet<VirtualFile> getTabClosingOrder(boolean closeNonModifiedFilesFirst) {
1192 final VirtualFile[] allFiles = getFiles();
1193 final Set<VirtualFile> histFiles = EditorHistoryManager.getInstance(getManager().getProject()).getFileSet();
1195 LinkedHashSet<VirtualFile> closingOrder = ContainerUtil.newLinkedHashSet();
1197 // first, we search for files not in history
1198 for (final VirtualFile file : allFiles) {
1199 if (!histFiles.contains(file)) {
1200 closingOrder.add(file);
1204 if (closeNonModifiedFilesFirst) {
1205 // Search in history
1206 for (final VirtualFile file : histFiles) {
1207 if (isFileModified(findFileComposite(file), file)) {
1208 // we found non modified file
1209 closingOrder.add(file);
1213 // Search in tabbed pane
1214 for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
1215 final VirtualFile file = getFileAt(i);
1216 if (isFileModified(getEditorAt(i), file)) {
1217 // we found non modified file
1218 closingOrder.add(file);
1223 // If it's not enough to close non-modified files only, try all other files.
1224 // Search in history from less frequently used.
1225 closingOrder.addAll(histFiles);
1227 // finally, close tabs by their order
1228 for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
1229 closingOrder.add(getFileAt(i));
1232 final VirtualFile selectedFile = getSelectedFile();
1233 closingOrder.remove(selectedFile);
1234 closingOrder.add(selectedFile); // selected should be closed last
1235 return closingOrder;
1238 private static boolean isFileModified(EditorComposite composite, VirtualFile file) {
1239 return composite != null && (composite.getInitialFileTimeStamp() == file.getTimeStamp() || composite.isModified());
1242 private boolean areAllTabsPinned(VirtualFile fileToIgnore) {
1243 for (int i = myTabbedPane.getTabCount() - 1; i >= 0; i--) {
1244 if (fileCanBeClosed(getFileAt(i), fileToIgnore)) {
1251 private void defaultCloseFile(VirtualFile file, boolean transferFocus) {
1252 closeFile(file, true, transferFocus);
1255 private boolean fileCanBeClosed(final VirtualFile file, @Nullable final VirtualFile fileToIgnore) {
1256 return isFileOpen (file) && !file.equals(fileToIgnore) && !isFilePinned(file);
1259 protected VirtualFile getFileAt(int i) {
1260 return getEditorAt(i).getFile();
1264 public String toString() {
1265 return "EditorWindow: files=" + Arrays.asList(getFiles());