06a3966ade1d04ce15b1dcbe3161fe507425427c
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / EditorWindow.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.fileEditor.impl;
17
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.Splitter;
37 import com.intellij.openapi.ui.ThreeComponentsSplitter;
38 import com.intellij.openapi.util.*;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.openapi.vfs.VirtualFileManager;
41 import com.intellij.openapi.wm.IdeFocusManager;
42 import com.intellij.openapi.wm.ToolWindowManager;
43 import com.intellij.ui.LayeredIcon;
44 import com.intellij.util.IconUtil;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.containers.Stack;
47 import com.intellij.util.ui.EmptyIcon;
48 import com.intellij.util.ui.UIUtil;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51
52 import javax.swing.*;
53 import javax.swing.border.Border;
54 import java.awt.*;
55 import java.awt.event.FocusAdapter;
56 import java.awt.event.FocusEvent;
57 import java.util.*;
58 import java.util.List;
59
60 /**
61  * Author: msk
62  */
63 public class EditorWindow {
64   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorWindow");
65
66   public static final DataKey<EditorWindow> DATA_KEY = DataKey.create("editorWindow");
67
68   protected JPanel myPanel;
69   private EditorTabbedContainer myTabbedPane;
70   private final EditorsSplitters myOwner;
71   private static final Icon MODIFIED_ICON = AllIcons.General.Modified;
72   private static final Icon GAP_ICON = new EmptyIcon(MODIFIED_ICON.getIconWidth(), MODIFIED_ICON.getIconHeight());
73
74   private boolean myIsDisposed = false;
75   public static final Key<Integer> INITIAL_INDEX_KEY = Key.create("initial editor index");
76   private final Stack<Pair<String, Integer>> myRemovedTabs = new Stack<Pair<String, Integer>>() {
77     @Override
78     public void push(Pair<String, Integer> pair) {
79       if (size() >= UISettings.getInstance().EDITOR_TAB_LIMIT) {
80         remove(0);
81       }
82       super.push(pair);
83     }
84   };
85
86   protected EditorWindow(final EditorsSplitters owner) {
87     myOwner = owner;
88     myPanel = new JPanel(new BorderLayout());
89     myPanel.setBorder(new AdaptiveBorder());
90     myPanel.setOpaque(false);
91
92     myTabbedPane = null;
93
94     final int tabPlacement = UISettings.getInstance().EDITOR_TAB_PLACEMENT;
95     if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) {
96       createTabs(tabPlacement);
97     }
98
99     // Tab layout policy
100     if (UISettings.getInstance().SCROLL_TAB_LAYOUT_IN_EDITOR) {
101       setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
102     } else {
103       setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
104     }
105
106     getWindows().add(this);
107     if (myOwner.getCurrentWindow() == null) {
108       myOwner.setCurrentWindow(this, false);
109     }
110   }
111
112   private void createTabs(int tabPlacement) {
113     LOG.assertTrue (myTabbedPane == null);
114     myTabbedPane = new EditorTabbedContainer(this, getManager().getProject(), tabPlacement);
115     myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
116   }
117
118   public boolean isShowing() {
119     return myPanel.isShowing();
120   }
121
122   public void closeAllExcept(final VirtualFile selectedFile) {
123     final VirtualFile[] files = getFiles();
124     for (final VirtualFile file : files) {
125       if (!Comparing.equal(file, selectedFile) && !isFilePinned(file)) {
126         closeFile(file);
127       }
128     }
129   }
130
131   private static class AdaptiveBorder implements Border {
132     @Override
133     public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
134       Insets insets = ((JComponent)c).getInsets();
135       g.setColor(UIUtil.getPanelBackground());
136       paintBorder(g, x, y, width, height, insets);
137       g.setColor(new Color(0, 0, 0, 90));
138       paintBorder(g, x, y, width, height, insets);
139     }
140
141     private static void paintBorder(Graphics g, int x, int y, int width, int height, Insets insets) {
142       if (insets.left == 1) {
143         g.drawLine(x, y, x, y + height);
144       }
145
146       if (insets.right == 1) {
147         g.drawLine(x + width - 1, y, x + width - 1, y + height);
148       }
149
150       if (insets.bottom == 1) {
151         g.drawLine(x, y + height - 1, x + width, y + height - 1);
152       }
153     }
154
155     @Override
156     public Insets getBorderInsets(Component c) {
157       Container parent = c.getParent();
158       if (parent instanceof Splitter) {
159         boolean editorToTheLeft = false;
160         boolean editorToTheRight = false;
161         boolean editorToTheDown = false;
162
163         Splitter splitter = (Splitter)parent;
164
165         boolean vertical = splitter.getOrientation();
166         if (vertical && splitter.getFirstComponent() == c) {
167           editorToTheDown = true;
168         } else if (!vertical) {
169           if (splitter.getFirstComponent() == c) {
170             editorToTheRight = true;
171           }
172           if (splitter.getSecondComponent() == c) editorToTheLeft = true;
173         }
174
175
176         //Frame frame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, c);
177         //if (frame instanceof IdeFrame) {
178         //  Project project = ((IdeFrame)frame).getProject();
179         //  ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(project);
180         //  if (!editorToTheLeft) {
181         //    List<String> left = toolWindowManager.getIdsOn(ToolWindowAnchor.LEFT);
182         //    if (left.size() > 0) {
183         //      for (String lid : left) {
184         //        ToolWindow window = toolWindowManager.getToolWindow(lid);
185         //        editorToTheLeft = window != null && window.isVisible() && window.getType() == ToolWindowType.DOCKED;
186         //        if (editorToTheLeft) break;
187         //      }
188         //    }
189         //  }
190         //
191         //  if (!editorToTheRight) {
192         //    List<String> right = toolWindowManager.getIdsOn(ToolWindowAnchor.RIGHT);
193         //    if (right.size() > 0) {
194         //      for (String lid : right) {
195         //        ToolWindow window = toolWindowManager.getToolWindow(lid);
196         //        editorToTheRight = window != null && window.isVisible() && window.getType() == ToolWindowType.DOCKED;
197         //        if (editorToTheRight) break;
198         //      }
199         //    }
200         //  }
201         //}
202
203         Splitter outer = nextOuterSplitter(splitter);
204         if (outer != null) {
205           boolean outerVertical = outer.getOrientation();
206           if (!outerVertical) {
207             if (splitter.getParent() == outer.getFirstComponent()) editorToTheRight = true;
208             if (splitter.getParent() == outer.getSecondComponent()) editorToTheLeft = true;
209           } else {
210             if (splitter.getParent() == outer.getFirstComponent()) {
211               editorToTheDown = true;
212             }
213           }
214         }
215
216         int left = editorToTheLeft ? 1 : 0;
217         int right = editorToTheRight ? 1 : 0;
218         int bottom = editorToTheDown ? 1 : 0;
219         return new Insets(0, left, bottom, right);
220       }
221
222       return new Insets(0, 0, 0, 0);
223     }
224
225     @Nullable
226     private static Splitter nextOuterSplitter(Component c) {
227       Container parent = c.getParent();
228       if (parent == null) return null;
229       Container grandParent = parent.getParent();
230       if (grandParent instanceof Splitter) return (Splitter)grandParent;
231       return null;
232     }
233
234     @Override
235     public boolean isBorderOpaque() {
236       return true;
237     }
238   }
239
240   private Set<EditorWindow> getWindows() {
241     return myOwner.myWindows;
242   }
243
244   private void dispose() {
245     try {
246       disposeTabs();
247       getWindows ().remove(this);
248     }
249     finally {
250       myIsDisposed = true;
251     }
252   }
253
254   public boolean isDisposed() {
255     return myIsDisposed;
256   }
257
258   private void disposeTabs() {
259     if (myTabbedPane != null) {
260       Disposer.dispose(myTabbedPane);
261       myTabbedPane = null;
262     }
263     myPanel.removeAll();
264     myPanel.revalidate();
265   }
266
267   public void closeFile(final VirtualFile file) {
268     closeFile(file, true);
269   }
270
271   public void closeFile(final VirtualFile file, final boolean disposeIfNeeded) {
272     closeFile(file, disposeIfNeeded, true);
273   }
274
275   public boolean hasClosedTabs() {
276     return !myRemovedTabs.empty();
277   }
278
279   public void restoreClosedTab() {
280     assert hasClosedTabs() : "Nothing to restore";
281
282     final Pair<String, Integer> info = myRemovedTabs.pop();
283     final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(info.getFirst());
284     final Integer second = info.getSecond();
285     if (file != null) {
286       getManager().openFileImpl4(this, file, null, true, true, null, second == null ? -1 : second.intValue());
287     }
288   }
289
290   public void closeFile(final VirtualFile file, final boolean disposeIfNeeded, final boolean transferFocus) {
291     final FileEditorManagerImpl editorManager = getManager();
292     editorManager.runChange(new FileEditorManagerChange() {
293       @Override
294       public void run(EditorsSplitters splitters) {
295         final List<EditorWithProviderComposite> editors = splitters.findEditorComposites(file);
296         if (editors.isEmpty()) return;
297         try {
298           final EditorWithProviderComposite editor = findFileComposite(file);
299
300           final FileEditorManagerListener.Before beforePublisher =
301             editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER);
302
303           beforePublisher.beforeFileClosed(editorManager, file);
304
305           if (myTabbedPane != null && editor != null) {
306             final int componentIndex = findComponentIndex(editor.getComponent());
307             if (componentIndex >= 0) { // editor could close itself on decomposition
308               final int indexToSelect = calcIndexToSelect(file, componentIndex);
309               myRemovedTabs.push(Pair.create(file.getUrl(), componentIndex));
310               myTabbedPane.removeTabAt(componentIndex, indexToSelect, transferFocus);
311               editorManager.disposeComposite(editor);
312             }
313           }
314           else {
315             myPanel.removeAll ();
316             if (editor != null) {
317               editorManager.disposeComposite(editor);
318             }
319           }
320
321           if (disposeIfNeeded && getTabCount() == 0) {
322             removeFromSplitter();
323             if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE) {
324               final EditorsSplitters owner = getOwner();
325               if (owner != null) {
326                 final ThreeComponentsSplitter splitter = UIUtil.getParentOfType(ThreeComponentsSplitter.class, owner);
327                 if (splitter != null) {
328                   splitter.revalidate();
329                   splitter.repaint();
330                 }
331               }
332             }
333           }
334           else {
335             myPanel.revalidate();
336             if (myTabbedPane == null) {
337               // in tabless mode
338               myPanel.repaint();
339             }
340           }
341         }
342         finally {
343           editorManager.removeSelectionRecord(file, EditorWindow.this);
344
345           editorManager.notifyPublisher(new Runnable() {
346             @Override
347             public void run() {
348               final Project project = editorManager.getProject();
349               if (!project.isDisposed()) {
350                 final FileEditorManagerListener afterPublisher =
351                   project.getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
352                 afterPublisher.fileClosed(editorManager, file);
353               }
354             }
355           });
356
357           splitters.afterFileClosed(file);
358         }
359       }
360     }, myOwner);
361   }
362
363   private void removeFromSplitter() {
364     if (!inSplitter()) return;
365
366     if (myOwner.getCurrentWindow() == this) {
367       EditorWindow[] siblings = findSiblings();
368       myOwner.setCurrentWindow(siblings[0], false);
369     }
370
371     Splitter splitter = (Splitter)myPanel.getParent();
372     JComponent otherComponent = splitter.getOtherComponent(myPanel);
373
374     Container parent = splitter.getParent().getParent();
375     if (parent instanceof Splitter) {
376       Splitter parentSplitter = (Splitter)parent;
377       if (parentSplitter.getFirstComponent() == splitter.getParent()) {
378         parentSplitter.setFirstComponent(otherComponent);
379       }
380       else {
381         parentSplitter.setSecondComponent(otherComponent);
382       }
383     }
384     else if (parent instanceof EditorsSplitters) {
385       parent.removeAll();
386       parent.add(otherComponent, BorderLayout.CENTER);
387       ((JComponent)parent).revalidate();
388     }
389     else {
390       throw new IllegalStateException("Unknown container: " + parent);
391     }
392
393     dispose();
394   }
395
396   private int calcIndexToSelect(VirtualFile fileBeingClosed, final int fileIndex) {
397     final int currentlySelectedIndex = myTabbedPane.getSelectedIndex();
398     if (currentlySelectedIndex != fileIndex) {
399       // if the file being closed is not currently selected, keep the currently selected file open
400       return currentlySelectedIndex;
401     }
402     UISettings uiSettings = UISettings.getInstance();
403     if (uiSettings.ACTIVATE_MRU_EDITOR_ON_CLOSE) {
404       // try to open last visited file
405       final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles();
406       for (int idx = histFiles.length - 1; idx >= 0; idx--) {
407         final VirtualFile histFile = histFiles[idx];
408         if (histFile.equals(fileBeingClosed)) {
409           continue;
410         }
411         final EditorWithProviderComposite editor = findFileComposite(histFile);
412         if (editor == null) {
413           continue; // ????
414         }
415         final int histFileIndex = findComponentIndex(editor.getComponent());
416         if (histFileIndex >= 0) {
417           // if the file being closed is located before the hist file, then after closing the index of the histFile will be shifted by -1
418           return histFileIndex;
419         }
420       }
421     } else
422     if (uiSettings.ACTIVATE_RIGHT_EDITOR_ON_CLOSE && (fileIndex + 1 < myTabbedPane.getTabCount())) {
423       return fileIndex + 1;
424     }
425
426     // by default select previous neighbour
427     if (fileIndex > 0) {
428       return fileIndex - 1;
429     }
430     // do nothing
431     return -1;
432   }
433
434   public FileEditorManagerImpl getManager() { return myOwner.getManager(); }
435
436   public int getTabCount() {
437     if (myTabbedPane != null) {
438       return myTabbedPane.getTabCount();
439     }
440     return myPanel.getComponentCount();
441   }
442
443   public void setForegroundAt(final int index, final Color color) {
444     if (myTabbedPane != null) {
445       myTabbedPane.setForegroundAt(index, color);
446     }
447   }
448
449   public void setWaveColor(final int index, @Nullable final Color color) {
450     if (myTabbedPane != null) {
451       myTabbedPane.setWaveColor(index, color);
452     }
453   }
454
455   private void setIconAt(final int index, final Icon icon) {
456     if (myTabbedPane != null) {
457       myTabbedPane.setIconAt(index, icon);
458     }
459   }
460
461   private void setTitleAt(final int index, final String text) {
462     if (myTabbedPane != null) {
463       myTabbedPane.setTitleAt(index, text);
464     }
465   }
466
467   private boolean isTitleShortenedAt(int index) {
468     return myTabbedPane != null && myTabbedPane.isTitleShortened(index);
469   }
470
471   private void setBackgroundColorAt(final int index, final Color color) {
472     if (myTabbedPane != null) {
473       myTabbedPane.setBackgroundColorAt(index, color);
474     }
475   }
476
477   private void setToolTipTextAt(final int index, final String text) {
478     if (myTabbedPane != null) {
479       myTabbedPane.setToolTipTextAt(index, text);
480     }
481   }
482
483
484   public void setTabLayoutPolicy(final int policy) {
485     if (myTabbedPane != null) {
486       myTabbedPane.setTabLayoutPolicy(policy);
487     }
488   }
489
490   public void setTabsPlacement(final int tabPlacement) {
491     if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) {
492       if (myTabbedPane == null) {
493         final EditorWithProviderComposite editor = getSelectedEditor();
494         myPanel.removeAll();
495         createTabs(tabPlacement);
496         setEditor (editor, true);
497       }
498       else {
499         myTabbedPane.setTabPlacement(tabPlacement);
500       }
501     }
502     else if (myTabbedPane != null) {
503       final boolean focusEditor = ToolWindowManager.getInstance(getManager().getProject()).isEditorComponentActive();
504       final VirtualFile currentFile = getSelectedFile();
505       if (currentFile != null) {
506         // do not close associated language console on tab placement change
507         currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, Boolean.TRUE);
508       }
509       final VirtualFile[] files = getFiles();
510       for (VirtualFile file : files) {
511         closeFile(file, false);
512       }
513       disposeTabs();
514       if (currentFile != null) {
515         currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null);
516         getManager().openFileImpl2(this, currentFile, focusEditor && myOwner.getCurrentWindow() == this);
517       }
518       else {
519         myPanel.repaint();
520       }
521     }
522   }
523
524   public void setAsCurrentWindow(final boolean requestFocus) {
525     myOwner.setCurrentWindow(this, requestFocus);
526   }
527
528   public void updateFileBackgroundColor(final VirtualFile file) {
529     final int index = findEditorIndex(findFileComposite(file));
530     if (index != -1) {
531       final Color color = EditorTabbedContainer.calcTabColor(getManager().getProject(), file);
532       setBackgroundColorAt(index, color);
533     }
534   }
535
536   public EditorsSplitters getOwner() {
537     return myOwner;
538   }
539
540   public boolean isEmptyVisible() {
541     return myTabbedPane != null ? myTabbedPane.isEmptyVisible() : getFiles().length == 0;
542   }
543
544   public Dimension getSize() {
545     return myPanel.getSize();
546   }
547
548   @Nullable
549   public EditorTabbedContainer getTabbedPane() {
550     return myTabbedPane;
551   }
552
553   public void requestFocus(boolean forced) {
554     if (myTabbedPane != null) {
555       myTabbedPane.requestFocus(forced);
556     } else {
557       IdeFocusManager.findInstanceByComponent(myPanel).requestFocus(myPanel, forced);
558     }
559   }
560
561   public boolean isValid() {
562     return myPanel.isShowing();
563   }
564
565   public void setPaintBlocked(boolean blocked) {
566     if (myTabbedPane != null) {
567       myTabbedPane.setPaintBlocked(blocked);
568     }
569   }
570
571   protected static class TComp extends JPanel implements DataProvider, EditorWindowHolder {
572     final EditorWithProviderComposite myEditor;
573     protected final EditorWindow myWindow;
574
575     TComp(final EditorWindow window, final EditorWithProviderComposite editor) {
576       super(new BorderLayout());
577       myEditor = editor;
578       myWindow = window;
579       add(editor.getComponent(), BorderLayout.CENTER);
580       addFocusListener(new FocusAdapter() {
581         @Override
582         public void focusGained(FocusEvent e) {
583           ApplicationManager.getApplication().invokeLater(new Runnable() {
584             @Override
585             public void run() {
586               if (!TComp.this.hasFocus()) return;
587               final JComponent focus = myEditor.getSelectedEditorWithProvider().getFirst().getPreferredFocusedComponent();
588               if (focus != null && !focus.hasFocus()) {
589                 IdeFocusManager.getGlobalInstance().requestFocus(focus, true);
590               }
591             }
592           });
593         }
594       });
595     }
596
597     @Override
598     public EditorWindow getEditorWindow() {
599       return myWindow;
600     }
601
602     @Override
603     public Object getData(String dataId) {
604       if (CommonDataKeys.VIRTUAL_FILE.is(dataId)){
605         final VirtualFile virtualFile = myEditor.getFile();
606         return virtualFile.isValid() ? virtualFile : null;
607       }
608       else if (CommonDataKeys.PROJECT.is(dataId)) {
609         return myEditor.getFileEditorManager().getProject();
610       }
611       return null;
612     }
613   }
614
615   protected static class TCompForTablessMode extends TComp implements CloseAction.CloseTarget {
616     TCompForTablessMode(final EditorWindow window, final EditorWithProviderComposite editor) {
617       super(window, editor);
618     }
619
620     @Override
621     public Object getData(String dataId) {
622       // this is essential for ability to close opened file
623       if (DATA_KEY.is(dataId)){
624         return myWindow;
625       }
626       if (CloseAction.CloseTarget.KEY.is(dataId)) {
627         return this;
628       }
629       return super.getData(dataId);
630     }
631
632     @Override
633     public void close() {
634       myWindow.closeFile(myEditor.getFile());
635     }
636   }
637
638   private void checkConsistency() {
639     LOG.assertTrue(getWindows().contains(this), "EditorWindow not in collection");
640   }
641
642   public EditorWithProviderComposite getSelectedEditor() {
643     final TComp comp;
644     if (myTabbedPane != null) {
645       comp = (TComp)myTabbedPane.getSelectedComponent();
646     }
647     else if (myPanel.getComponentCount() != 0) {
648       final Component component = myPanel.getComponent(0);
649       comp = component instanceof TComp ? (TComp)component : null;
650     }
651     else {
652       return null;
653     }
654
655     if (comp != null) {
656       return comp.myEditor;
657     }
658     return null;
659   }
660
661   public EditorWithProviderComposite[] getEditors() {
662     final int tabCount = getTabCount();
663     final EditorWithProviderComposite[] res = new EditorWithProviderComposite[tabCount];
664     for (int i = 0; i != tabCount; ++i) {
665       res[i] = getEditorAt(i);
666     }
667     return res;
668   }
669
670   public VirtualFile[] getFiles() {
671     final int tabCount = getTabCount();
672     final VirtualFile[] res = new VirtualFile[tabCount];
673     for (int i = 0; i != tabCount; ++i) {
674       res[i] = getEditorAt(i).getFile();
675     }
676     return res;
677   }
678
679   public void setSelectedEditor(final EditorComposite editor, final boolean focusEditor) {
680     if (myTabbedPane == null) {
681       return;
682     }
683     if (editor != null) {
684       final int index = findFileIndex(editor.getFile());
685       if (index != -1) {
686         UIUtil.invokeLaterIfNeeded(new Runnable() {
687           @Override
688           public void run() {
689             if (myTabbedPane != null) {
690               myTabbedPane.setSelectedIndex(index, focusEditor);
691             }
692           }
693         });
694       }
695     }
696   }
697
698   public void setEditor(@Nullable final EditorWithProviderComposite editor, final boolean focusEditor) {
699     setEditor(editor, true, focusEditor);
700   }
701
702   public void setEditor(@Nullable final EditorWithProviderComposite editor, final boolean selectEditor, final boolean focusEditor) {
703     if (editor != null) {
704       if (myTabbedPane == null) {
705         myPanel.removeAll ();
706         myPanel.add (new TCompForTablessMode(this, editor), BorderLayout.CENTER);
707         myOwner.validate();
708         return;
709       }
710
711       final int index = findEditorIndex(editor);
712       if (index != -1) {
713         if (selectEditor) {
714           setSelectedEditor(editor, focusEditor);
715         }
716       }
717       else {
718         Integer initialIndex = editor.getFile().getUserData(INITIAL_INDEX_KEY);
719         int indexToInsert = 0;
720         if (initialIndex == null) {
721           int selectedIndex = myTabbedPane.getSelectedIndex();
722           if (selectedIndex >= 0) {
723             indexToInsert = selectedIndex + 1;
724           }
725         } else {
726           indexToInsert = initialIndex;
727         }
728
729         final VirtualFile file = editor.getFile();
730         final Icon template = AllIcons.FileTypes.Text;
731         myTabbedPane.insertTab(file, new EmptyIcon(template.getIconWidth(), template.getIconHeight()), new TComp(this, editor), null, indexToInsert);
732         trimToSize(UISettings.getInstance().EDITOR_TAB_LIMIT, file, false);
733         if (selectEditor) {
734           setSelectedEditor(editor, focusEditor);
735         }
736         myOwner.updateFileIcon(file);
737         myOwner.updateFileColor(file);
738       }
739       myOwner.setCurrentWindow(this, false);
740     }
741     myOwner.validate();
742   }
743
744   private boolean splitAvailable() {
745     return getTabCount() >= 1;
746   }
747
748   @Nullable
749   public EditorWindow split(final int orientation, boolean forceSplit, @Nullable VirtualFile virtualFile, boolean focusNew) {
750     checkConsistency();
751     final FileEditorManagerImpl fileEditorManager = myOwner.getManager();
752     if (splitAvailable()) {
753       if (!forceSplit && inSplitter()) {
754         final EditorWindow[] siblings = findSiblings();
755         final EditorWindow target = siblings[0];
756         if (virtualFile != null) {
757           final FileEditor[] editors = fileEditorManager.openFileImpl3(target, virtualFile, focusNew, null, true).first;
758           syncCaretIfPossible(editors);
759         }
760         return target;
761       }
762       final JPanel panel = myPanel;
763       panel.setBorder(null);
764       final int tabCount = getTabCount();
765       if (tabCount != 0) {
766         final EditorWithProviderComposite firstEC = getEditorAt(0);
767         myPanel = new JPanel(new BorderLayout());
768         myPanel.setOpaque(false);
769         myPanel.setBorder(new AdaptiveBorder());
770         myPanel.setOpaque(false);
771
772         final Splitter splitter = new Splitter(orientation == JSplitPane.VERTICAL_SPLIT, 0.5f, 0.1f, 0.9f);
773         final EditorWindow res = new EditorWindow(myOwner);
774         if (myTabbedPane != null) {
775           final EditorWithProviderComposite selectedEditor = getSelectedEditor();
776           panel.remove(myTabbedPane.getComponent());
777           panel.add(splitter, BorderLayout.CENTER);
778           splitter.setFirstComponent(myPanel);
779           myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
780           splitter.setSecondComponent(res.myPanel);
781           /*
782           for (int i = 0; i != tabCount; ++i) {
783             final EditorWithProviderComposite eC = getEditorAt(i);
784             final VirtualFile file = eC.getFile();
785             fileEditorManager.openFileImpl3(res, file, false, null);
786             res.setFilePinned (file, isFilePinned (file));
787           }
788           */
789           // open only selected file in the new splitter instead of opening all tabs
790           final VirtualFile file = selectedEditor.getFile();
791
792           if (virtualFile == null) {
793             for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
794               VirtualFile associatedFile = finder.getAssociatedFileToOpen(fileEditorManager.getProject(), file);
795
796               if (associatedFile != null) {
797                 virtualFile = associatedFile;
798                 break;
799               }
800             }
801           }
802
803           final VirtualFile nextFile = virtualFile == null ? file : virtualFile;
804           final FileEditor[] editors = fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true).first;
805           syncCaretIfPossible(editors);
806           res.setFilePinned (nextFile, isFilePinned (file));
807           if (!focusNew) {
808             res.setSelectedEditor(selectedEditor, true);
809             selectedEditor.getComponent().requestFocus();
810           }
811           panel.revalidate();
812         }
813         else {
814           panel.removeAll();
815           panel.add(splitter, BorderLayout.CENTER);
816           splitter.setFirstComponent(myPanel);
817           splitter.setSecondComponent(res.myPanel);
818           panel.revalidate();
819           final VirtualFile firstFile = firstEC.getFile();
820           final VirtualFile nextFile = virtualFile == null ? firstFile : virtualFile;
821           final FileEditor[] firstEditors = fileEditorManager.openFileImpl3(this, firstFile, !focusNew, null, true).first;
822           syncCaretIfPossible(firstEditors);
823           final FileEditor[] secondEditors = fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true).first;
824           syncCaretIfPossible(secondEditors);
825         }
826         return res;
827       }
828     }
829     return null;
830   }
831
832   /**
833    * Tries to setup caret and viewport for the given editor from the selected one.
834    *
835    * @param toSync    editor to setup caret and viewport for
836    */
837   private void syncCaretIfPossible(@Nullable FileEditor[] toSync) {
838     if (toSync == null) {
839       return;
840     }
841
842     final EditorWithProviderComposite from = getSelectedEditor();
843     if (from == null) {
844       return;
845     }
846
847     final FileEditor caretSource = from.getSelectedEditor();
848     if (!(caretSource instanceof TextEditor)) {
849       return;
850     }
851
852     final Editor editorFrom = ((TextEditor)caretSource).getEditor();
853     final int offset = editorFrom.getCaretModel().getOffset();
854     if (offset <= 0) {
855       return;
856     }
857
858     final int scrollOffset = editorFrom.getScrollingModel().getVerticalScrollOffset();
859
860     for (FileEditor fileEditor : toSync) {
861       if (!(fileEditor instanceof TextEditor)) {
862         continue;
863       }
864       final Editor editor = ((TextEditor)fileEditor).getEditor();
865       if (editorFrom.getDocument() == editor.getDocument()) {
866         editor.getCaretModel().moveToOffset(offset);
867         final ScrollingModel scrollingModel = editor.getScrollingModel();
868         scrollingModel.scrollVertically(scrollOffset);
869
870         SwingUtilities.invokeLater(new Runnable() {
871           @Override
872           public void run() {
873             if (!editor.isDisposed()) {
874               scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE);
875             }
876           }
877         });
878       }
879     }
880   }
881
882   public EditorWindow[] findSiblings() {
883     checkConsistency();
884     final ArrayList<EditorWindow> res = new ArrayList<EditorWindow>();
885     if (myPanel.getParent() instanceof Splitter) {
886       final Splitter splitter = (Splitter)myPanel.getParent();
887       for (final EditorWindow win : getWindows()) {
888         if (win != this && SwingUtilities.isDescendingFrom(win.myPanel, splitter)) {
889           res.add(win);
890         }
891       }
892     }
893     return res.toArray(new EditorWindow[res.size()]);
894   }
895
896   public void changeOrientation() {
897     checkConsistency();
898     final Container parent = myPanel.getParent();
899     if (parent instanceof Splitter) {
900       final Splitter splitter = (Splitter)parent;
901       splitter.setOrientation(!splitter.getOrientation());
902     }
903   }
904
905   protected void updateFileIcon(VirtualFile file) {
906     final int index = findEditorIndex(findFileComposite(file));
907     LOG.assertTrue(index != -1);
908     setIconAt(index, getFileIcon(file));
909   }
910
911   protected void updateFileName(VirtualFile file) {
912     final int index = findEditorIndex(findFileComposite(file));
913     if (index != -1) {
914       setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file));
915       setToolTipTextAt(index, UISettings.getInstance().SHOW_TABS_TOOLTIPS || isTitleShortenedAt(index)
916                               ? getManager().getFileTooltipText(file)
917                               : null);
918     }
919   }
920
921   /**
922    * @return icon which represents file's type and modification status
923    */
924   private Icon getFileIcon(@NotNull final VirtualFile file) {
925     if (!file.isValid()) {
926       Icon fakeIcon = FileTypes.UNKNOWN.getIcon();
927       assert fakeIcon != null : "Can't find the icon for unknown file type";
928       return fakeIcon;
929     }
930
931     final Icon baseIcon = IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, getManager().getProject());
932
933     int count = 1;
934
935     final Icon pinIcon;
936     final EditorComposite composite = findFileComposite(file);
937     if (composite != null && composite.isPinned()) {
938       count++;
939       pinIcon = AllIcons.Nodes.TabPin;
940     }
941     else {
942       pinIcon = null;
943     }
944
945     final Icon modifiedIcon;
946     if (UISettings.getInstance().MARK_MODIFIED_TABS_WITH_ASTERISK) {
947       modifiedIcon = composite != null && composite.isModified() ? MODIFIED_ICON : GAP_ICON;
948       count++;
949     }
950     else {
951       modifiedIcon = null;
952     }
953
954     if (count == 1) return baseIcon;
955
956     int i = 0;
957     final LayeredIcon result = new LayeredIcon(count);
958     result.setIcon(baseIcon, i++);
959     if (pinIcon != null) result.setIcon(pinIcon, i++);
960     if (modifiedIcon != null) result.setIcon(modifiedIcon, i++);
961
962     return result;
963   }
964
965   public void unsplit(boolean setCurrent) {
966     checkConsistency();
967     final Container splitter = myPanel.getParent();
968
969     if (!(splitter instanceof Splitter)) return;
970
971     EditorWithProviderComposite editorToSelect = getSelectedEditor();
972     final EditorWindow[] siblings = findSiblings();
973     final JPanel parent = (JPanel)splitter.getParent();
974
975     for (EditorWindow eachSibling : siblings) {
976       // selected editors will be added first
977       final EditorWithProviderComposite selected = eachSibling.getSelectedEditor();
978       if (editorToSelect == null) {
979         editorToSelect = selected;
980       }
981     }
982
983     for (final EditorWindow sibling : siblings) {
984       final EditorWithProviderComposite[] siblingEditors = sibling.getEditors();
985       for (final EditorWithProviderComposite siblingEditor : siblingEditors) {
986         if (editorToSelect == null) {
987           editorToSelect = siblingEditor;
988         }
989         processSiblingEditor(siblingEditor);
990       }
991       LOG.assertTrue(sibling != this);
992       sibling.dispose();
993     }
994     parent.remove(splitter);
995     if (myTabbedPane != null) {
996       parent.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
997     }
998     else {
999       if (myPanel.getComponentCount() > 0) {
1000         parent.add(myPanel.getComponent(0), BorderLayout.CENTER);
1001       }
1002     }
1003     parent.revalidate();
1004     myPanel = parent;
1005     if (editorToSelect != null) {
1006       setSelectedEditor(editorToSelect, true);
1007     }
1008     if (setCurrent) {
1009       myOwner.setCurrentWindow(this, false);
1010     }
1011   }
1012
1013   private void processSiblingEditor(final EditorWithProviderComposite siblingEditor) {
1014     if (myTabbedPane != null && getTabCount() < UISettings.getInstance().EDITOR_TAB_LIMIT && findFileComposite(siblingEditor.getFile()) == null) {
1015       setEditor(siblingEditor, true);
1016     }
1017     else if (myTabbedPane == null && getTabCount() == 0) { // tabless mode and no file opened
1018       setEditor(siblingEditor, true);
1019     }
1020     else {
1021       getManager().disposeComposite(siblingEditor);
1022     }
1023   }
1024
1025   public void unsplitAll() {
1026     checkConsistency();
1027     while (inSplitter()) {
1028       unsplit(true);
1029     }
1030   }
1031
1032   public boolean inSplitter() {
1033     checkConsistency();
1034     return myPanel.getParent() instanceof Splitter;
1035   }
1036
1037   public VirtualFile getSelectedFile() {
1038     checkConsistency();
1039     final EditorWithProviderComposite editor = getSelectedEditor();
1040     return editor == null ? null : editor.getFile();
1041   }
1042
1043   @Nullable
1044   public EditorWithProviderComposite findFileComposite(final VirtualFile file) {
1045     for (int i = 0; i != getTabCount(); ++i) {
1046       final EditorWithProviderComposite editor = getEditorAt(i);
1047       if (editor.getFile().equals(file)) {
1048         return editor;
1049       }
1050     }
1051     return null;
1052   }
1053
1054
1055   public int findComponentIndex(final Component component) {
1056     for (int i = 0; i != getTabCount(); ++i) {
1057       final EditorWithProviderComposite editor = getEditorAt(i);
1058       if (editor.getComponent ().equals (component)) {
1059         return i;
1060       }
1061     }
1062     return -1;
1063   }
1064
1065   public int findEditorIndex(final EditorComposite editorToFind) {
1066     for (int i = 0; i != getTabCount(); ++i) {
1067       final EditorWithProviderComposite editor = getEditorAt(i);
1068       if (editor.equals (editorToFind)) {
1069         return i;
1070       }
1071     }
1072     return -1;
1073   }
1074
1075   public int findFileIndex(final VirtualFile fileToFind) {
1076     for (int i = 0; i != getTabCount(); ++i) {
1077       final VirtualFile file = getFileAt(i);
1078       if (file.equals (fileToFind)) {
1079         return i;
1080       }
1081     }
1082     return -1;
1083   }
1084
1085   private EditorWithProviderComposite getEditorAt(final int i) {
1086     final TComp comp;
1087     if (myTabbedPane != null) {
1088       comp = (TComp)myTabbedPane.getComponentAt(i);
1089     }
1090     else {
1091       LOG.assertTrue(i <= 1);
1092       comp = (TComp)myPanel.getComponent(i);
1093     }
1094     return comp.myEditor;
1095   }
1096
1097   public boolean isFileOpen(final VirtualFile file) {
1098     return findFileComposite(file) != null;
1099   }
1100
1101   public boolean isFilePinned(final VirtualFile file) {
1102     final EditorComposite editorComposite = findFileComposite(file);
1103     if (editorComposite == null) {
1104       throw new IllegalArgumentException("file is not open: " + file.getPath());
1105     }
1106     return editorComposite.isPinned();
1107   }
1108
1109   public void setFilePinned(final VirtualFile file, final boolean pinned) {
1110     final EditorComposite editorComposite = findFileComposite(file);
1111     if (editorComposite == null) {
1112       throw new IllegalArgumentException("file is not open: " + file.getPath());
1113     }
1114     boolean wasPinned = editorComposite.isPinned();
1115     editorComposite.setPinned(pinned);
1116     if (wasPinned != pinned && ApplicationManager.getApplication().isDispatchThread()) {
1117       updateFileIcon(file);
1118     }
1119   }
1120
1121   void trimToSize(final int limit, @Nullable final VirtualFile fileToIgnore, final boolean transferFocus) {
1122     if (myTabbedPane == null) return;
1123
1124     FileEditorManagerEx.getInstanceEx(getManager().getProject()).getReady(this).doWhenDone(new Runnable() {
1125       @Override
1126       public void run() {
1127         if (myTabbedPane == null) return;
1128         final boolean closeNonModifiedFilesFirst = UISettings.getInstance().CLOSE_NON_MODIFIED_FILES_FIRST;
1129         final EditorComposite selectedComposite = getSelectedEditor();
1130         try {
1131           doTrimSize(limit, fileToIgnore, closeNonModifiedFilesFirst, transferFocus);
1132         }
1133         finally {
1134           setSelectedEditor(selectedComposite, false);
1135         }
1136       }
1137     });
1138   }
1139
1140   private void doTrimSize(int limit, @Nullable VirtualFile fileToIgnore, boolean closeNonModifiedFilesFirst, boolean transferFocus) {
1141     LinkedHashSet<VirtualFile> closingOrder = getTabClosingOrder(closeNonModifiedFilesFirst);
1142
1143     for (VirtualFile file : closingOrder) {
1144       if (myTabbedPane.getTabCount() <= limit || myTabbedPane.getTabCount() == 0 || areAllTabsPinned(fileToIgnore)) {
1145         return;
1146       }
1147       if (fileCanBeClosed(file, fileToIgnore)) {
1148         defaultCloseFile(file, transferFocus);
1149       }
1150     }
1151
1152   }
1153
1154   private LinkedHashSet<VirtualFile> getTabClosingOrder(boolean closeNonModifiedFilesFirst) {
1155     final VirtualFile[] allFiles = getFiles();
1156     final Set<VirtualFile> histFiles = EditorHistoryManager.getInstance(getManager().getProject()).getFileSet();
1157
1158     LinkedHashSet<VirtualFile> closingOrder = ContainerUtil.newLinkedHashSet();
1159
1160     // first, we search for files not in history
1161     for (final VirtualFile file : allFiles) {
1162       if (!histFiles.contains(file)) {
1163         closingOrder.add(file);
1164       }
1165     }
1166
1167     if (closeNonModifiedFilesFirst) {
1168       // Search in history
1169       for (final VirtualFile file : histFiles) {
1170         if (isFileModified(findFileComposite(file), file)) {
1171           // we found non modified file
1172           closingOrder.add(file);
1173         }
1174       }
1175
1176       // Search in tabbed pane
1177       for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
1178         final VirtualFile file = getFileAt(i);
1179         if (isFileModified(getEditorAt(i), file)) {
1180           // we found non modified file
1181           closingOrder.add(file);
1182         }
1183       }
1184     }
1185
1186     // If it's not enough to close non-modified files only, try all other files.
1187     // Search in history from less frequently used.
1188     closingOrder.addAll(histFiles);
1189
1190     // finally, close tabs by their order
1191     for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
1192       closingOrder.add(getFileAt(i));
1193     }
1194
1195     final VirtualFile selectedFile = getSelectedFile();
1196     closingOrder.remove(selectedFile);
1197     closingOrder.add(selectedFile); // selected should be closed last
1198     return closingOrder;
1199   }
1200
1201   private static boolean isFileModified(EditorComposite composite, VirtualFile file) {
1202     return composite != null && (composite.getInitialFileTimeStamp() == file.getTimeStamp() || composite.isModified());
1203   }
1204
1205   private boolean areAllTabsPinned(VirtualFile fileToIgnore) {
1206     for (int i = myTabbedPane.getTabCount() - 1; i >= 0; i--) {
1207       if (fileCanBeClosed(getFileAt(i), fileToIgnore)) {
1208         return false;
1209       }
1210     }
1211     return true;
1212   }
1213
1214   private void defaultCloseFile(VirtualFile file, boolean transferFocus) {
1215     closeFile(file, true, transferFocus);
1216   }
1217
1218   private boolean fileCanBeClosed(final VirtualFile file, @Nullable final VirtualFile fileToIgnore) {
1219     return isFileOpen (file) && !file.equals(fileToIgnore) && !isFilePinned(file);
1220   }
1221
1222   protected VirtualFile getFileAt(int i) {
1223     return getEditorAt(i).getFile();
1224   }
1225
1226   @Override
1227   public String toString() {
1228     return "EditorWindow: files=" + Arrays.asList(getFiles());
1229   }
1230 }