IDEA-134480 Make one-row IntelliJ tabs shorter if there is no space in tabs container
[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.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;
55
56 import javax.swing.*;
57 import javax.swing.border.Border;
58 import java.awt.*;
59 import java.awt.event.FocusAdapter;
60 import java.awt.event.FocusEvent;
61 import java.util.*;
62 import java.util.List;
63
64 /**
65  * Author: msk
66  */
67 public class EditorWindow {
68   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorWindow");
69
70   public static final DataKey<EditorWindow> DATA_KEY = DataKey.create("editorWindow");
71
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() {
76     @Override
77     public void paintIcon(Component c, Graphics g, int x, int y) {
78       GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
79       Font oldFont = g.getFont();
80       try {
81         g.setFont(UIUtil.getLabelFont());
82         g.setColor(JBColor.foreground());
83         g.drawString("*", 0, 10);
84       } finally {
85         config.restore();
86         g.setFont(oldFont);
87       }
88     }
89
90     @Override
91     public int getIconWidth() {
92       return 9;
93     }
94
95     @Override
96     public int getIconHeight() {
97       return 9;
98     }
99   } : AllIcons.General.Modified;
100   private static final Icon GAP_ICON = new EmptyIcon(MODIFIED_ICON.getIconWidth(), MODIFIED_ICON.getIconHeight());
101
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>>() {
105     @Override
106     public void push(Pair<String, Integer> pair) {
107       if (size() >= UISettings.getInstance().EDITOR_TAB_LIMIT) {
108         remove(0);
109       }
110       super.push(pair);
111     }
112   };
113
114   protected EditorWindow(final EditorsSplitters owner) {
115     myOwner = owner;
116     myPanel = new JPanel(new BorderLayout());
117     myPanel.setBorder(new AdaptiveBorder());
118     myPanel.setOpaque(false);
119
120     myTabbedPane = null;
121
122     final int tabPlacement = UISettings.getInstance().EDITOR_TAB_PLACEMENT;
123     if (tabPlacement != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE) {
124       createTabs(tabPlacement);
125     }
126
127     // Tab layout policy
128     if (UISettings.getInstance().SCROLL_TAB_LAYOUT_IN_EDITOR) {
129       setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
130     } else {
131       setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
132     }
133
134     myOwner.addWindow(this);
135     if (myOwner.getCurrentWindow() == null) {
136       myOwner.setCurrentWindow(this, false);
137     }
138   }
139
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);
144   }
145
146   public boolean isShowing() {
147     return myPanel.isShowing();
148   }
149
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)) {
154         closeFile(file);
155       }
156     }
157   }
158
159   private static class AdaptiveBorder implements Border {
160     @Override
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);
167     }
168
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);
172       }
173
174       if (insets.right == 1) {
175         g.drawLine(x + width - 1, y, x + width - 1, y + height);
176       }
177
178       if (insets.bottom == 1) {
179         g.drawLine(x, y + height - 1, x + width, y + height - 1);
180       }
181     }
182
183     @Override
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;
190
191         Splitter splitter = (Splitter)parent;
192
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;
199           }
200           if (splitter.getSecondComponent() == c) editorToTheLeft = true;
201         }
202
203
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;
215         //      }
216         //    }
217         //  }
218         //
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;
226         //      }
227         //    }
228         //  }
229         //}
230
231         Splitter outer = nextOuterSplitter(splitter);
232         if (outer != null) {
233           boolean outerVertical = outer.getOrientation();
234           if (!outerVertical) {
235             if (splitter.getParent() == outer.getFirstComponent()) editorToTheRight = true;
236             if (splitter.getParent() == outer.getSecondComponent()) editorToTheLeft = true;
237           } else {
238             if (splitter.getParent() == outer.getFirstComponent()) {
239               editorToTheDown = true;
240             }
241           }
242         }
243
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);
248       }
249
250       return new Insets(0, 0, 0, 0);
251     }
252
253     @Nullable
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;
259       return null;
260     }
261
262     @Override
263     public boolean isBorderOpaque() {
264       return true;
265     }
266   }
267
268   void dispose() {
269     try {
270       disposeTabs();
271       myOwner.removeWindow(this);
272     }
273     finally {
274       myIsDisposed = true;
275     }
276   }
277
278   public boolean isDisposed() {
279     return myIsDisposed;
280   }
281
282   private void disposeTabs() {
283     if (myTabbedPane != null) {
284       Disposer.dispose(myTabbedPane);
285       myTabbedPane = null;
286     }
287     myPanel.removeAll();
288     myPanel.revalidate();
289   }
290
291   public void closeFile(final VirtualFile file) {
292     closeFile(file, true);
293   }
294
295   public void closeFile(final VirtualFile file, final boolean disposeIfNeeded) {
296     closeFile(file, disposeIfNeeded, true);
297   }
298
299   public boolean hasClosedTabs() {
300     return !myRemovedTabs.empty();
301   }
302
303   public void restoreClosedTab() {
304     assert hasClosedTabs() : "Nothing to restore";
305
306     final Pair<String, Integer> info = myRemovedTabs.pop();
307     final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(info.getFirst());
308     final Integer second = info.getSecond();
309     if (file != null) {
310       getManager().openFileImpl4(this, file, null, true, true, null, second == null ? -1 : second.intValue());
311     }
312   }
313
314   public void closeFile(@NotNull final VirtualFile file, final boolean disposeIfNeeded, final boolean transferFocus) {
315     final FileEditorManagerImpl editorManager = getManager();
316     editorManager.runChange(new FileEditorManagerChange() {
317       @Override
318       public void run(EditorsSplitters splitters) {
319         final List<EditorWithProviderComposite> editors = splitters.findEditorComposites(file);
320         if (editors.isEmpty()) return;
321         try {
322           final EditorWithProviderComposite editor = findFileComposite(file);
323
324           final FileEditorManagerListener.Before beforePublisher =
325             editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER);
326
327           beforePublisher.beforeFileClosed(editorManager, file);
328
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);
336             }
337           }
338           else {
339             myPanel.removeAll ();
340             if (editor != null) {
341               editorManager.disposeComposite(editor);
342             }
343           }
344
345           if (disposeIfNeeded && getTabCount() == 0) {
346             removeFromSplitter();
347             if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE) {
348               final EditorsSplitters owner = getOwner();
349               if (owner != null) {
350                 final ThreeComponentsSplitter splitter = UIUtil.getParentOfType(ThreeComponentsSplitter.class, owner);
351                 if (splitter != null) {
352                   splitter.revalidate();
353                   splitter.repaint();
354                 }
355               }
356             }
357           }
358           else {
359             myPanel.revalidate();
360             if (myTabbedPane == null) {
361               // in tabless mode
362               myPanel.repaint();
363             }
364           }
365         }
366         finally {
367           editorManager.removeSelectionRecord(file, EditorWindow.this);
368
369           editorManager.notifyPublisher(new Runnable() {
370             @Override
371             public void run() {
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);
377               }
378             }
379           });
380
381           splitters.afterFileClosed(file);
382         }
383       }
384     }, myOwner);
385   }
386
387   private void removeFromSplitter() {
388     if (!inSplitter()) return;
389
390     if (myOwner.getCurrentWindow() == this) {
391       EditorWindow[] siblings = findSiblings();
392       myOwner.setCurrentWindow(siblings[0], false);
393     }
394
395     Splitter splitter = (Splitter)myPanel.getParent();
396     JComponent otherComponent = splitter.getOtherComponent(myPanel);
397
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);
403       }
404       else {
405         parentSplitter.setSecondComponent(otherComponent);
406       }
407     }
408     else if (parent instanceof EditorsSplitters) {
409       parent.removeAll();
410       parent.add(otherComponent, BorderLayout.CENTER);
411       ((JComponent)parent).revalidate();
412     }
413     else {
414       throw new IllegalStateException("Unknown container: " + parent);
415     }
416
417     dispose();
418   }
419
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;
425     }
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)) {
433           continue;
434         }
435         final EditorWithProviderComposite editor = findFileComposite(histFile);
436         if (editor == null) {
437           continue; // ????
438         }
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;
443         }
444       }
445     } else
446     if (uiSettings.ACTIVATE_RIGHT_EDITOR_ON_CLOSE && (fileIndex + 1 < myTabbedPane.getTabCount())) {
447       return fileIndex + 1;
448     }
449
450     // by default select previous neighbour
451     if (fileIndex > 0) {
452       return fileIndex - 1;
453     }
454     // do nothing
455     return -1;
456   }
457
458   public FileEditorManagerImpl getManager() { return myOwner.getManager(); }
459
460   public int getTabCount() {
461     if (myTabbedPane != null) {
462       return myTabbedPane.getTabCount();
463     }
464     return myPanel.getComponentCount();
465   }
466
467   public void setForegroundAt(final int index, final Color color) {
468     if (myTabbedPane != null) {
469       myTabbedPane.setForegroundAt(index, color);
470     }
471   }
472
473   public void setWaveColor(final int index, @Nullable final Color color) {
474     if (myTabbedPane != null) {
475       myTabbedPane.setWaveColor(index, color);
476     }
477   }
478
479   private void setIconAt(final int index, final Icon icon) {
480     if (myTabbedPane != null) {
481       myTabbedPane.setIconAt(index, icon);
482     }
483   }
484
485   private void setTitleAt(final int index, final String text) {
486     if (myTabbedPane != null) {
487       myTabbedPane.setTitleAt(index, text);
488     }
489   }
490
491   private boolean isTitleShortenedAt(int index) {
492     return myTabbedPane != null && myTabbedPane.isTitleShortened(index);
493   }
494
495   private void setBackgroundColorAt(final int index, final Color color) {
496     if (myTabbedPane != null) {
497       myTabbedPane.setBackgroundColorAt(index, color);
498     }
499   }
500
501   private void setToolTipTextAt(final int index, final String text) {
502     if (myTabbedPane != null) {
503       myTabbedPane.setToolTipTextAt(index, text);
504     }
505   }
506
507
508   public void setTabLayoutPolicy(final int policy) {
509     if (myTabbedPane != null) {
510       myTabbedPane.setTabLayoutPolicy(policy);
511     }
512   }
513
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();
518         myPanel.removeAll();
519         createTabs(tabPlacement);
520         setEditor (editor, true);
521       }
522       else {
523         myTabbedPane.setTabPlacement(tabPlacement);
524       }
525     }
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);
532       }
533       final VirtualFile[] files = getFiles();
534       for (VirtualFile file : files) {
535         closeFile(file, false);
536       }
537       disposeTabs();
538       if (currentFile != null) {
539         currentFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null);
540         getManager().openFileImpl2(this, currentFile, focusEditor && myOwner.getCurrentWindow() == this);
541       }
542       else {
543         myPanel.repaint();
544       }
545     }
546   }
547
548   public void setAsCurrentWindow(final boolean requestFocus) {
549     myOwner.setCurrentWindow(this, requestFocus);
550   }
551
552   public void updateFileBackgroundColor(@NotNull VirtualFile file) {
553     final int index = findEditorIndex(findFileComposite(file));
554     if (index != -1) {
555       final Color color = EditorTabbedContainer.calcTabColor(getManager().getProject(), file);
556       setBackgroundColorAt(index, color);
557     }
558   }
559
560   public EditorsSplitters getOwner() {
561     return myOwner;
562   }
563
564   public boolean isEmptyVisible() {
565     return myTabbedPane != null ? myTabbedPane.isEmptyVisible() : getFiles().length == 0;
566   }
567
568   public Dimension getSize() {
569     return myPanel.getSize();
570   }
571
572   @Nullable
573   public EditorTabbedContainer getTabbedPane() {
574     return myTabbedPane;
575   }
576
577   public void requestFocus(boolean forced) {
578     if (myTabbedPane != null) {
579       myTabbedPane.requestFocus(forced);
580     }
581     else {
582       EditorWithProviderComposite editor = getSelectedEditor();
583       JComponent preferred = editor == null ? null : editor.getPreferredFocusedComponent();
584       IdeFocusManager.findInstanceByComponent(preferred == null ? myPanel : preferred).requestFocus(myPanel, forced);
585     }
586   }
587
588   public boolean isValid() {
589     return myPanel.isShowing();
590   }
591
592   public void setPaintBlocked(boolean blocked) {
593     if (myTabbedPane != null) {
594       myTabbedPane.setPaintBlocked(blocked);
595     }
596   }
597
598   protected static class TComp extends JPanel implements DataProvider, EditorWindowHolder {
599     @NotNull final EditorWithProviderComposite myEditor;
600     protected final EditorWindow myWindow;
601
602     TComp(@NotNull EditorWindow window, @NotNull EditorWithProviderComposite editor) {
603       super(new BorderLayout());
604       myEditor = editor;
605       myWindow = window;
606       add(editor.getComponent(), BorderLayout.CENTER);
607       addFocusListener(new FocusAdapter() {
608         @Override
609         public void focusGained(FocusEvent e) {
610           ApplicationManager.getApplication().invokeLater(new Runnable() {
611             @Override
612             public void run() {
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);
617               }
618             }
619           });
620         }
621       });
622     }
623
624     @NotNull
625     @Override
626     public EditorWindow getEditorWindow() {
627       return myWindow;
628     }
629
630     @Override
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;
635       }
636       else if (CommonDataKeys.PROJECT.is(dataId)) {
637         return myEditor.getFileEditorManager().getProject();
638       }
639       return null;
640     }
641   }
642
643   protected static class TCompForTablessMode extends TComp implements CloseAction.CloseTarget {
644     TCompForTablessMode(@NotNull EditorWindow window, @NotNull EditorWithProviderComposite editor) {
645       super(window, editor);
646     }
647
648     @Override
649     public Object getData(String dataId) {
650       // this is essential for ability to close opened file
651       if (DATA_KEY.is(dataId)){
652         return myWindow;
653       }
654       if (CloseAction.CloseTarget.KEY.is(dataId)) {
655         return this;
656       }
657       return super.getData(dataId);
658     }
659
660     @Override
661     public void close() {
662       myWindow.closeFile(myEditor.getFile());
663     }
664   }
665
666   private void checkConsistency() {
667     LOG.assertTrue(myOwner.containsWindow(this), "EditorWindow not in collection");
668   }
669
670   public EditorWithProviderComposite getSelectedEditor() {
671     final TComp comp;
672     if (myTabbedPane != null) {
673       comp = (TComp)myTabbedPane.getSelectedComponent();
674     }
675     else if (myPanel.getComponentCount() != 0) {
676       final Component component = myPanel.getComponent(0);
677       comp = component instanceof TComp ? (TComp)component : null;
678     }
679     else {
680       return null;
681     }
682
683     if (comp != null) {
684       return comp.myEditor;
685     }
686     return null;
687   }
688
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);
694     }
695     return res;
696   }
697
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();
703     }
704     return res;
705   }
706
707   public void setSelectedEditor(final EditorComposite editor, final boolean focusEditor) {
708     if (myTabbedPane == null) {
709       return;
710     }
711     if (editor != null) {
712       final int index = findFileIndex(editor.getFile());
713       if (index != -1) {
714         UIUtil.invokeLaterIfNeeded(new Runnable() {
715           @Override
716           public void run() {
717             if (myTabbedPane != null) {
718               myTabbedPane.setSelectedIndex(index, focusEditor);
719             }
720           }
721         });
722       }
723     }
724   }
725
726   public void setEditor(@Nullable final EditorWithProviderComposite editor, final boolean focusEditor) {
727     setEditor(editor, true, focusEditor);
728   }
729
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);
736         myOwner.validate();
737         return;
738       }
739
740       final int index = findEditorIndex(editor);
741       if (index != -1) {
742         if (selectEditor) {
743           setSelectedEditor(editor, focusEditor);
744         }
745       }
746       else {
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();
751         } else {
752           if (initialIndex == null) {
753             int selectedIndex = myTabbedPane.getSelectedIndex();
754             if (selectedIndex >= 0) {
755               indexToInsert = selectedIndex + 1;
756             }
757           } else {
758             indexToInsert = initialIndex;
759           }
760         }
761
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);
766         if (selectEditor) {
767           setSelectedEditor(editor, focusEditor);
768         }
769         myOwner.updateFileIcon(file);
770         myOwner.updateFileColor(file);
771       }
772       myOwner.setCurrentWindow(this, false);
773     }
774     myOwner.validate();
775   }
776
777   protected void onBeforeSetEditor(VirtualFile file) {
778   }
779
780   private boolean splitAvailable() {
781     return getTabCount() >= 1;
782   }
783
784   @Nullable
785   public EditorWindow split(final int orientation, boolean forceSplit, @Nullable VirtualFile virtualFile, boolean focusNew) {
786     checkConsistency();
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);
795         }
796         return target;
797       }
798       final JPanel panel = myPanel;
799       panel.setBorder(null);
800       final int tabCount = getTabCount();
801       if (tabCount != 0) {
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);
807
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);
817           /*
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));
823           }
824           */
825           // open only selected file in the new splitter instead of opening all tabs
826           final VirtualFile file = selectedEditor.getFile();
827
828           if (virtualFile == null) {
829             for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
830               VirtualFile associatedFile = finder.getAssociatedFileToOpen(fileEditorManager.getProject(), file);
831
832               if (associatedFile != null) {
833                 virtualFile = associatedFile;
834                 break;
835               }
836             }
837           }
838
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));
843           if (!focusNew) {
844             res.setSelectedEditor(selectedEditor, true);
845             selectedEditor.getComponent().requestFocus();
846           }
847           panel.revalidate();
848         }
849         else {
850           panel.removeAll();
851           panel.add(splitter, BorderLayout.CENTER);
852           splitter.setFirstComponent(myPanel);
853           splitter.setSecondComponent(res.myPanel);
854           panel.revalidate();
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);
861         }
862         return res;
863       }
864     }
865     return null;
866   }
867
868   /**
869    * Tries to setup caret and viewport for the given editor from the selected one.
870    *
871    * @param toSync    editor to setup caret and viewport for
872    */
873   private void syncCaretIfPossible(@Nullable FileEditor[] toSync) {
874     if (toSync == null) {
875       return;
876     }
877
878     final EditorWithProviderComposite from = getSelectedEditor();
879     if (from == null) {
880       return;
881     }
882
883     final FileEditor caretSource = from.getSelectedEditor();
884     if (!(caretSource instanceof TextEditor)) {
885       return;
886     }
887
888     final Editor editorFrom = ((TextEditor)caretSource).getEditor();
889     final int offset = editorFrom.getCaretModel().getOffset();
890     if (offset <= 0) {
891       return;
892     }
893
894     final int scrollOffset = editorFrom.getScrollingModel().getVerticalScrollOffset();
895
896     for (FileEditor fileEditor : toSync) {
897       if (!(fileEditor instanceof TextEditor)) {
898         continue;
899       }
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);
905
906         SwingUtilities.invokeLater(new Runnable() {
907           @Override
908           public void run() {
909             if (!editor.isDisposed()) {
910               scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE);
911             }
912           }
913         });
914       }
915     }
916   }
917
918   public EditorWindow[] findSiblings() {
919     checkConsistency();
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)) {
925           res.add(win);
926         }
927       }
928     }
929     return res.toArray(new EditorWindow[res.size()]);
930   }
931
932   public void changeOrientation() {
933     checkConsistency();
934     final Container parent = myPanel.getParent();
935     if (parent instanceof Splitter) {
936       final Splitter splitter = (Splitter)parent;
937       splitter.setOrientation(!splitter.getOrientation());
938     }
939   }
940
941   protected void updateFileIcon(VirtualFile file) {
942     final int index = findEditorIndex(findFileComposite(file));
943     LOG.assertTrue(index != -1);
944     setIconAt(index, getFileIcon(file));
945   }
946
947   protected void updateFileName(VirtualFile file) {
948     final int index = findEditorIndex(findFileComposite(file));
949     if (index != -1) {
950       setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file));
951       setToolTipTextAt(index, UISettings.getInstance().SHOW_TABS_TOOLTIPS || isTitleShortenedAt(index)
952                               ? getManager().getFileTooltipText(file)
953                               : null);
954     }
955   }
956
957   /**
958    * @return icon which represents file's type and modification status
959    */
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";
964       return fakeIcon;
965     }
966
967     final Icon baseIcon = IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, getManager().getProject());
968
969     int count = 1;
970
971     final Icon pinIcon;
972     final EditorComposite composite = findFileComposite(file);
973     if (composite != null && composite.isPinned()) {
974       count++;
975       pinIcon = AllIcons.Nodes.TabPin;
976     }
977     else {
978       pinIcon = null;
979     }
980
981     final Icon modifiedIcon;
982     if (UISettings.getInstance().MARK_MODIFIED_TABS_WITH_ASTERISK) {
983       modifiedIcon = composite != null && composite.isModified() ? MODIFIED_ICON : GAP_ICON;
984       count++;
985     }
986     else {
987       modifiedIcon = null;
988     }
989
990     if (count == 1) return baseIcon;
991
992     int i = 0;
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++);
998
999     return result;
1000   }
1001
1002   public void unsplit(boolean setCurrent) {
1003     checkConsistency();
1004     final Container splitter = myPanel.getParent();
1005
1006     if (!(splitter instanceof Splitter)) return;
1007
1008     EditorWithProviderComposite editorToSelect = getSelectedEditor();
1009     final EditorWindow[] siblings = findSiblings();
1010     final JPanel parent = (JPanel)splitter.getParent();
1011
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;
1017       }
1018     }
1019
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;
1025         }
1026         processSiblingEditor(siblingEditor);
1027       }
1028       LOG.assertTrue(sibling != this);
1029       sibling.dispose();
1030     }
1031     parent.remove(splitter);
1032     if (myTabbedPane != null) {
1033       parent.add(myTabbedPane.getComponent(), BorderLayout.CENTER);
1034     }
1035     else {
1036       if (myPanel.getComponentCount() > 0) {
1037         parent.add(myPanel.getComponent(0), BorderLayout.CENTER);
1038       }
1039     }
1040     parent.revalidate();
1041     myPanel = parent;
1042     if (editorToSelect != null) {
1043       setSelectedEditor(editorToSelect, true);
1044     }
1045     if (setCurrent) {
1046       myOwner.setCurrentWindow(this, false);
1047     }
1048   }
1049
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);
1053     }
1054     else if (myTabbedPane == null && getTabCount() == 0) { // tabless mode and no file opened
1055       setEditor(siblingEditor, true);
1056     }
1057     else {
1058       getManager().disposeComposite(siblingEditor);
1059     }
1060   }
1061
1062   public void unsplitAll() {
1063     checkConsistency();
1064     while (inSplitter()) {
1065       unsplit(true);
1066     }
1067   }
1068
1069   public boolean inSplitter() {
1070     checkConsistency();
1071     return myPanel.getParent() instanceof Splitter;
1072   }
1073
1074   public VirtualFile getSelectedFile() {
1075     checkConsistency();
1076     final EditorWithProviderComposite editor = getSelectedEditor();
1077     return editor == null ? null : editor.getFile();
1078   }
1079
1080   @Nullable
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)) {
1085         return editor;
1086       }
1087     }
1088     return null;
1089   }
1090
1091
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)) {
1096         return i;
1097       }
1098     }
1099     return -1;
1100   }
1101
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)) {
1106         return i;
1107       }
1108     }
1109     return -1;
1110   }
1111
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)) {
1116         return i;
1117       }
1118     }
1119     return -1;
1120   }
1121
1122   private EditorWithProviderComposite getEditorAt(final int i) {
1123     final TComp comp;
1124     if (myTabbedPane != null) {
1125       comp = (TComp)myTabbedPane.getComponentAt(i);
1126     }
1127     else {
1128       LOG.assertTrue(i <= 1);
1129       comp = (TComp)myPanel.getComponent(i);
1130     }
1131     return comp.myEditor;
1132   }
1133
1134   public boolean isFileOpen(final VirtualFile file) {
1135     return findFileComposite(file) != null;
1136   }
1137
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());
1142     }
1143     return editorComposite.isPinned();
1144   }
1145
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());
1150     }
1151     boolean wasPinned = editorComposite.isPinned();
1152     editorComposite.setPinned(pinned);
1153     if (wasPinned != pinned && ApplicationManager.getApplication().isDispatchThread()) {
1154       updateFileIcon(file);
1155     }
1156   }
1157
1158   void trimToSize(final int limit, @Nullable final VirtualFile fileToIgnore, final boolean transferFocus) {
1159     if (myTabbedPane == null) return;
1160
1161     FileEditorManagerEx.getInstanceEx(getManager().getProject()).getReady(this).doWhenDone(new Runnable() {
1162       @Override
1163       public void run() {
1164         if (myTabbedPane == null) return;
1165         final boolean closeNonModifiedFilesFirst = UISettings.getInstance().CLOSE_NON_MODIFIED_FILES_FIRST;
1166         final EditorComposite selectedComposite = getSelectedEditor();
1167         try {
1168           doTrimSize(limit, fileToIgnore, closeNonModifiedFilesFirst, transferFocus);
1169         }
1170         finally {
1171           setSelectedEditor(selectedComposite, false);
1172         }
1173       }
1174     });
1175   }
1176
1177   private void doTrimSize(int limit, @Nullable VirtualFile fileToIgnore, boolean closeNonModifiedFilesFirst, boolean transferFocus) {
1178     LinkedHashSet<VirtualFile> closingOrder = getTabClosingOrder(closeNonModifiedFilesFirst);
1179
1180     for (VirtualFile file : closingOrder) {
1181       if (myTabbedPane.getTabCount() <= limit || myTabbedPane.getTabCount() == 0 || areAllTabsPinned(fileToIgnore)) {
1182         return;
1183       }
1184       if (fileCanBeClosed(file, fileToIgnore)) {
1185         defaultCloseFile(file, transferFocus);
1186       }
1187     }
1188
1189   }
1190
1191   private LinkedHashSet<VirtualFile> getTabClosingOrder(boolean closeNonModifiedFilesFirst) {
1192     final VirtualFile[] allFiles = getFiles();
1193     final Set<VirtualFile> histFiles = EditorHistoryManager.getInstance(getManager().getProject()).getFileSet();
1194
1195     LinkedHashSet<VirtualFile> closingOrder = ContainerUtil.newLinkedHashSet();
1196
1197     // first, we search for files not in history
1198     for (final VirtualFile file : allFiles) {
1199       if (!histFiles.contains(file)) {
1200         closingOrder.add(file);
1201       }
1202     }
1203
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);
1210         }
1211       }
1212
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);
1219         }
1220       }
1221     }
1222
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);
1226
1227     // finally, close tabs by their order
1228     for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
1229       closingOrder.add(getFileAt(i));
1230     }
1231
1232     final VirtualFile selectedFile = getSelectedFile();
1233     closingOrder.remove(selectedFile);
1234     closingOrder.add(selectedFile); // selected should be closed last
1235     return closingOrder;
1236   }
1237
1238   private static boolean isFileModified(EditorComposite composite, VirtualFile file) {
1239     return composite != null && (composite.getInitialFileTimeStamp() == file.getTimeStamp() || composite.isModified());
1240   }
1241
1242   private boolean areAllTabsPinned(VirtualFile fileToIgnore) {
1243     for (int i = myTabbedPane.getTabCount() - 1; i >= 0; i--) {
1244       if (fileCanBeClosed(getFileAt(i), fileToIgnore)) {
1245         return false;
1246       }
1247     }
1248     return true;
1249   }
1250
1251   private void defaultCloseFile(VirtualFile file, boolean transferFocus) {
1252     closeFile(file, true, transferFocus);
1253   }
1254
1255   private boolean fileCanBeClosed(final VirtualFile file, @Nullable final VirtualFile fileToIgnore) {
1256     return isFileOpen (file) && !file.equals(fileToIgnore) && !isFilePinned(file);
1257   }
1258
1259   protected VirtualFile getFileAt(int i) {
1260     return getEditorAt(i).getFile();
1261   }
1262
1263   @Override
1264   public String toString() {
1265     return "EditorWindow: files=" + Arrays.asList(getFiles());
1266   }
1267 }