IDEA-127739 Navigation Tab
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / EditorsSplitters.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.ide.IdeBundle;
19 import com.intellij.ide.ui.UISettings;
20 import com.intellij.ide.ui.UISettingsListener;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ModalityState;
23 import com.intellij.openapi.components.ServiceManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Document;
26 import com.intellij.openapi.fileEditor.FileDocumentManager;
27 import com.intellij.openapi.fileEditor.FileEditor;
28 import com.intellij.openapi.fileEditor.impl.text.FileDropHandler;
29 import com.intellij.openapi.keymap.Keymap;
30 import com.intellij.openapi.keymap.KeymapManager;
31 import com.intellij.openapi.keymap.KeymapManagerListener;
32 import com.intellij.openapi.progress.ProgressIndicator;
33 import com.intellij.openapi.progress.ProgressManager;
34 import com.intellij.openapi.project.DumbService;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.ui.Splitter;
37 import com.intellij.openapi.util.*;
38 import com.intellij.openapi.vfs.VfsUtilCore;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.openapi.wm.FocusWatcher;
41 import com.intellij.openapi.wm.IdeFrame;
42 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
43 import com.intellij.openapi.wm.ex.WindowManagerEx;
44 import com.intellij.openapi.wm.impl.FrameTitleBuilder;
45 import com.intellij.openapi.wm.impl.IdePanePanel;
46 import com.intellij.ui.JBColor;
47 import com.intellij.ui.awt.RelativePoint;
48 import com.intellij.ui.docking.DockManager;
49 import com.intellij.ui.tabs.JBTabs;
50 import com.intellij.util.Alarm;
51 import com.intellij.util.containers.ArrayListSet;
52 import com.intellij.util.containers.ContainerUtil;
53 import com.intellij.util.ui.UIUtil;
54 import org.jdom.Element;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import java.awt.*;
60 import java.awt.datatransfer.DataFlavor;
61 import java.awt.datatransfer.Transferable;
62 import java.awt.event.ContainerEvent;
63 import java.io.File;
64 import java.util.*;
65 import java.util.List;
66 import java.util.concurrent.CopyOnWriteArraySet;
67
68
69 /**
70  * Author: msk
71  */
72 public class EditorsSplitters extends IdePanePanel implements UISettingsListener {
73   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorsSplitters");
74   private static final String PINNED = "pinned";
75   private static final String CURRENT_IN_TAB = "current-in-tab";
76
77   private static final Key<Object> DUMMY_KEY = Key.create("EditorsSplitters.dummy.key");
78
79   private final static EditorEmptyTextPainter ourPainter = ServiceManager.getService(EditorEmptyTextPainter.class);
80
81   private EditorWindow myCurrentWindow;
82   final Set<EditorWindow> myWindows = new CopyOnWriteArraySet<EditorWindow>();
83
84   private final FileEditorManagerImpl myManager;
85   private Element mySplittersElement;  // temporarily used during initialization
86   int myInsideChange = 0;
87   private final MyFocusWatcher myFocusWatcher;
88   private final Alarm myIconUpdaterAlarm = new Alarm();
89   private final KeymapManagerListener myKeymapListener;
90   private final UIBuilder myUIBuilder = new UIBuilder();
91
92   public EditorsSplitters(final FileEditorManagerImpl manager, DockManager dockManager, boolean createOwnDockableContainer) {
93     super(new BorderLayout());
94     myManager = manager;
95     myFocusWatcher = new MyFocusWatcher();
96     setFocusTraversalPolicy(new MyFocusTraversalPolicy());
97     setTransferHandler(new MyTransferHandler());
98     clear();
99
100     if (createOwnDockableContainer) {
101       DockableEditorTabbedContainer dockable = new DockableEditorTabbedContainer(myManager.getProject(), this, false);
102       Disposer.register(manager.getProject(), dockable);
103       dockManager.register(dockable);
104     }
105     myKeymapListener = new KeymapManagerListener() {
106       @Override
107       public void activeKeymapChanged(Keymap keymap) {
108         invalidate();
109         repaint();
110       }
111     };
112     KeymapManager.getInstance().addKeymapManagerListener(myKeymapListener);
113     UISettings.getInstance().addUISettingsListener(this);
114   }
115   
116   public FileEditorManagerImpl getManager() {
117     return myManager;
118   }
119
120   public void clear() {
121     removeAll();
122     myWindows.clear();
123     setCurrentWindow(null);
124     repaint (); // revalidate doesn't repaint correctly after "Close All"
125   }
126
127   public void startListeningFocus() {
128     myFocusWatcher.install(this);
129   }
130
131   private void stopListeningFocus() {
132     myFocusWatcher.deinstall(this);
133   }
134
135   public void dispose() {
136     myIconUpdaterAlarm.cancelAllRequests();
137     stopListeningFocus();
138     KeymapManager.getInstance().removeKeymapManagerListener(myKeymapListener);
139     UISettings.getInstance().removeUISettingsListener(this);
140   }
141
142   @Nullable
143   public VirtualFile getCurrentFile() {
144     if (myCurrentWindow != null) {
145       return myCurrentWindow.getSelectedFile();
146     }
147     return null;
148   }
149
150
151   protected boolean showEmptyText() {
152     return myCurrentWindow == null || myCurrentWindow.getFiles().length == 0;
153   }
154
155   @Override
156   protected void paintComponent(Graphics g) {
157     super.paintComponent(g);
158
159     if (myCurrentWindow == null || myCurrentWindow.getFiles().length == 0) {
160       g.setColor(UIUtil.isUnderDarcula()? UIUtil.getBorderColor() : new Color(0, 0, 0, 50));
161       g.drawLine(0, 0, getWidth(), 0);
162     }
163
164     if (showEmptyText()) {
165       ourPainter.paintEmptyText(this, g);
166     }
167   }
168
169   public void writeExternal(final Element element) {
170     if (getComponentCount() != 0) {
171       final Component comp = getComponent(0);
172       LOG.assertTrue(comp instanceof JPanel);
173       final JPanel panel = (JPanel)comp;
174       if (panel.getComponentCount() != 0) {
175         final Element res = writePanel(panel);
176         element.addContent(res);
177       }
178     }
179   }
180
181   @SuppressWarnings({"HardCodedStringLiteral"})
182   private Element writePanel(final JPanel panel) {
183     final Component comp = panel.getComponent(0);
184     if (comp instanceof Splitter) {
185       final Splitter splitter = (Splitter)comp;
186       final Element res = new Element("splitter");
187       res.setAttribute("split-orientation", splitter.getOrientation() ? "vertical" : "horizontal");
188       res.setAttribute("split-proportion", Float.toString(splitter.getProportion()));
189       final Element first = new Element("split-first");
190       first.addContent(writePanel((JPanel)splitter.getFirstComponent()));
191       final Element second = new Element("split-second");
192       second.addContent(writePanel((JPanel)splitter.getSecondComponent()));
193       res.addContent(first);
194       res.addContent(second);
195       return res;
196     }
197     else if (comp instanceof JBTabs) {
198       final Element res = new Element("leaf");
199       final EditorWindow window = findWindowWith(comp);
200       writeWindow(res, window);
201       return res;
202     }
203     else if (comp instanceof EditorWindow.TCompForTablessMode) {
204       final EditorWithProviderComposite composite = ((EditorWindow.TCompForTablessMode)comp).myEditor;
205       final Element res = new Element("leaf");
206       writeComposite(res, composite.getFile(), composite, false, composite);
207       return res;
208     }
209     else {
210       LOG.error(comp != null ? comp.getClass().getName() : null);
211       return null;
212     }
213   }
214
215   private void writeWindow(final Element res, final EditorWindow window) {
216     if (window != null) {
217       final EditorWithProviderComposite[] composites = window.getEditors();
218       for (int i = 0; i < composites.length; i++) {
219         final VirtualFile file = window.getFileAt(i);
220         final boolean isPinned = window.isFilePinned(file);
221         final EditorWithProviderComposite composite = composites[i];
222         final EditorWithProviderComposite selectedEditor = window.getSelectedEditor();
223
224         writeComposite(res, file, composite, isPinned, selectedEditor);
225       }
226     }
227   }
228
229   private void writeComposite(final Element res, final VirtualFile file, final EditorWithProviderComposite composite,
230                               final boolean pinned,
231                               final EditorWithProviderComposite selectedEditor) {
232     final Element fileElement = new Element("file");
233     fileElement.setAttribute("leaf-file-name", file.getName()); // TODO: all files
234     final HistoryEntry entry = composite.currentStateAsHistoryEntry();
235     entry.writeExternal(fileElement, getManager().getProject());
236     fileElement.setAttribute(PINNED,         Boolean.toString(pinned));
237     fileElement.setAttribute(CURRENT_IN_TAB, Boolean.toString(composite.equals(selectedEditor)));
238     res.addContent(fileElement);
239   }
240
241   public void openFiles() {
242     if (mySplittersElement != null) {
243       initializeProgress();
244       final JPanel comp = myUIBuilder.process(mySplittersElement, getTopPanel());
245       UIUtil.invokeAndWaitIfNeeded(new Runnable() {
246         @Override
247         public void run() {
248           if (comp != null) {
249             removeAll();
250             add(comp, BorderLayout.CENTER);
251             mySplittersElement = null;
252           }
253           // clear empty splitters
254           for (EditorWindow window : getWindows()) {
255             if (window.getEditors().length == 0) {
256               for (EditorWindow sibling : window.findSiblings()) {
257                 sibling.unsplit(false);
258               }
259             }
260           }
261         }
262       });
263     }
264   }
265
266   private double myProgressMaximum;
267   private int myCurrentProgress;
268
269   private void initializeProgress() {
270     ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
271     if (indicator != null) {
272       indicator.setText(IdeBundle.message("loading.editors"));
273       indicator.setText2("");
274       indicator.setIndeterminate(false);
275       indicator.setFraction(0);
276
277       myProgressMaximum = countFiles(mySplittersElement);
278       myCurrentProgress = 0;
279     }
280   }
281
282   private void updateProgress() {
283     ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
284     if (indicator != null) {
285       myCurrentProgress++;
286       indicator.setFraction(myCurrentProgress / myProgressMaximum);
287     }
288   }
289
290   private static int countFiles(Element element) {
291     Integer value = new ConfigTreeReader<Integer>() {
292       @Override
293       protected Integer processFiles(@NotNull List<Element> fileElements, @Nullable Integer context) {
294         return fileElements.size();
295       }
296
297       @Override
298       protected Integer processSplitter(@NotNull Element element, @Nullable Element firstChild, @Nullable Element secondChild, @Nullable Integer context) {
299         Integer first = process(firstChild, null);
300         Integer second = process(secondChild, null);
301         return (first == null ? 0 : first) + (second == null ? 0 : second);
302       }
303     }.process(element, null);
304     return value == null ? 0 : value;
305   }
306
307   public void readExternal(final Element element) {
308     mySplittersElement = element;
309   }
310
311   @NotNull public VirtualFile[] getOpenFiles() {
312     final ArrayListSet<VirtualFile> files = new ArrayListSet<VirtualFile>();
313     for (final EditorWindow myWindow : myWindows) {
314       final EditorWithProviderComposite[] editors = myWindow.getEditors();
315       for (final EditorWithProviderComposite editor : editors) {
316         VirtualFile file = editor.getFile();
317         // background thread may call this method when invalid file is being removed
318         // do not return it here as it will quietly drop out soon
319         if (file.isValid()) {
320           files.add(file);
321         }
322       }
323     }
324     return VfsUtilCore.toVirtualFileArray(files);
325   }
326
327   @NotNull public VirtualFile[] getSelectedFiles() {
328     final ArrayListSet<VirtualFile> files = new ArrayListSet<VirtualFile>();
329     for (final EditorWindow window : myWindows) {
330       final VirtualFile file = window.getSelectedFile();
331       if (file != null) {
332         files.add(file);
333       }
334     }
335     final VirtualFile[] virtualFiles = VfsUtilCore.toVirtualFileArray(files);
336     final VirtualFile currentFile = getCurrentFile();
337     if (currentFile != null) {
338       for (int i = 0; i != virtualFiles.length; ++i) {
339         if (Comparing.equal(virtualFiles[i], currentFile)) {
340           virtualFiles[i] = virtualFiles[0];
341           virtualFiles[0] = currentFile;
342           break;
343         }
344       }
345     }
346     return virtualFiles;
347   }
348
349   @NotNull public FileEditor[] getSelectedEditors() {
350     final List<FileEditor> editors = new ArrayList<FileEditor>();
351     final EditorWindow currentWindow = getCurrentWindow();
352     if (currentWindow != null) {
353       final EditorWithProviderComposite composite = currentWindow.getSelectedEditor();
354       if (composite != null) {
355         editors.add (composite.getSelectedEditor());
356       }
357     }
358
359     for (final EditorWindow window : myWindows) {
360       if (!window.equals(currentWindow)) {
361         final EditorWithProviderComposite composite = window.getSelectedEditor();
362         if (composite != null) {
363           editors.add(composite.getSelectedEditor());
364         }
365       }
366     }
367     return editors.toArray(new FileEditor[editors.size()]);
368   }
369
370   public void updateFileIcon(@NotNull final VirtualFile file) {
371     updateFileIconLater(file);
372   }
373
374   private void updateFileIconImmediately(final VirtualFile file) {
375     final Collection<EditorWindow> windows = findWindows(file);
376     for (EditorWindow window : windows) {
377       window.updateFileIcon(file);
378     }
379   }
380
381   private final Set<VirtualFile> myFilesToUpdateIconsFor = new HashSet<VirtualFile>();
382
383   private void updateFileIconLater(VirtualFile file) {
384     myFilesToUpdateIconsFor.add(file);
385     myIconUpdaterAlarm.cancelAllRequests();
386     myIconUpdaterAlarm.addRequest(new Runnable() {
387       @Override
388       public void run() {
389         if (myManager.getProject().isDisposed()) return;
390         for (VirtualFile file : myFilesToUpdateIconsFor) {
391           updateFileIconImmediately(file);
392         }
393         myFilesToUpdateIconsFor.clear();
394       }
395     }, 200, ModalityState.stateForComponent(this));
396   }
397
398   public void updateFileColor(@NotNull final VirtualFile file) {
399     final Collection<EditorWindow> windows = findWindows(file);
400     for (final EditorWindow window : windows) {
401       final int index = window.findEditorIndex(window.findFileComposite(file));
402       LOG.assertTrue(index != -1);
403       window.setForegroundAt(index, getManager().getFileColor(file));
404       window.setWaveColor(index, getManager().isProblem(file) ? JBColor.red : null);
405     }
406   }
407
408   public void trimToSize(final int editor_tab_limit) {
409     for (final EditorWindow window : myWindows) {
410       window.trimToSize(editor_tab_limit, null, true);
411     }
412   }
413
414   public void setTabsPlacement(final int tabPlacement) {
415     final EditorWindow[] windows = getWindows();
416     for (int i = 0; i != windows.length; ++ i) {
417       windows[i].setTabsPlacement(tabPlacement);
418     }
419   }
420
421   public void setTabLayoutPolicy(int scrollTabLayout) {
422     final EditorWindow[] windows = getWindows();
423     for (int i = 0; i != windows.length; ++ i) {
424       windows[i].setTabLayoutPolicy(scrollTabLayout);
425     }
426   }
427
428   public void updateFileName(final VirtualFile updatedFile) {
429     final EditorWindow[] windows = getWindows();
430     for (int i = 0; i != windows.length; ++ i) {
431       windows [i].updateFileName(updatedFile);
432     }
433
434     Project project = myManager.getProject();
435
436     final IdeFrame frame = getFrame(project);
437     if (frame != null) {
438       VirtualFile file = getCurrentFile();
439
440       File ioFile = file == null ? null : new File(file.getPresentableUrl());
441       String fileTitle = null;
442       if (file != null) {
443         fileTitle = DumbService.isDumb(project) ? file.getName()
444                                                 : FrameTitleBuilder.getInstance().getFileTitle(project, file);
445       }
446
447       frame.setFileTitle(fileTitle, ioFile);
448     }
449   }
450
451   protected IdeFrame getFrame(Project project) {
452     final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx();
453     final IdeFrame frame = windowManagerEx.getFrame(project);
454     LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || frame != null);
455     return frame;
456   }
457
458   public boolean isInsideChange() {
459     return myInsideChange > 0;
460   }
461
462   private void setCurrentWindow(@Nullable final EditorWindow currentWindow) {
463     myCurrentWindow = currentWindow;
464   }
465
466   public void updateFileBackgroundColor(final VirtualFile file) {
467     final EditorWindow[] windows = getWindows();
468     for (int i = 0; i != windows.length; ++ i) {
469       windows [i].updateFileBackgroundColor(file);
470     }
471   }
472
473   public int getSplitCount() {
474     if (getComponentCount() > 0) {
475       JPanel panel = (JPanel) getComponent(0);
476       return getSplitCount(panel);
477     }
478     return 0;
479   }
480
481   private static int getSplitCount(JComponent component) {
482     if (component.getComponentCount() > 0) {
483       final JComponent firstChild = (JComponent)component.getComponent(0);
484       if (firstChild instanceof Splitter) {
485         final Splitter splitter = (Splitter)firstChild;
486         return getSplitCount(splitter.getFirstComponent()) + getSplitCount(splitter.getSecondComponent());
487       }
488       return 1;
489     }
490     return 0;
491   }
492
493   protected void afterFileClosed(VirtualFile file) {
494   }
495
496   protected void afterFileOpen(VirtualFile file) {
497   }
498
499   @Nullable
500   public JBTabs getTabsAt(RelativePoint point) {
501     Point thisPoint = point.getPoint(this);
502     Component c = SwingUtilities.getDeepestComponentAt(this, thisPoint.x, thisPoint.y);
503     while (c != null) {
504       if (c instanceof JBTabs) {
505         return (JBTabs)c;
506       }
507       c = c.getParent();
508     }
509
510     return null;
511   }
512
513   public boolean isEmptyVisible() {
514     EditorWindow[] windows = getWindows();
515     for (EditorWindow each : windows) {
516       if (!each.isEmptyVisible()) {
517         return false;
518       }
519     }
520     return true;
521   }
522
523   @Nullable
524   public VirtualFile findNextFile(final VirtualFile file) {
525     final EditorWindow[] windows = getWindows(); // TODO: use current file as base
526     for (int i = 0; i != windows.length; ++i) {
527       final VirtualFile[] files = windows[i].getFiles();
528       for (final VirtualFile fileAt : files) {
529         if (!Comparing.equal(fileAt, file)) {
530           return fileAt;
531         }
532       }
533     }
534     return null;
535   }
536
537   void closeFile(VirtualFile file, boolean moveFocus) {
538     final List<EditorWindow> windows = findWindows(file);
539     if (!windows.isEmpty()) {
540       final VirtualFile nextFile = findNextFile(file);
541       for (final EditorWindow window : windows) {
542         LOG.assertTrue(window.getSelectedEditor() != null);
543         window.closeFile(file, false, moveFocus);
544         if (window.getTabCount() == 0 && nextFile != null && myManager.getProject().isOpen()) {
545           EditorWithProviderComposite newComposite = myManager.newEditorComposite(nextFile);
546           window.setEditor(newComposite, moveFocus); // newComposite can be null
547         }
548       }
549       // cleanup windows with no tabs
550       for (final EditorWindow window : windows) {
551         if (window.isDisposed()) {
552           // call to window.unsplit() which might make its sibling disposed
553           continue;
554         }
555         if (window.getTabCount() == 0) {
556           window.unsplit(false);
557         }
558       }
559     }
560   }
561
562   @Override
563   public void uiSettingsChanged(UISettings source) {
564     for (VirtualFile file : getOpenFiles()) {
565       updateFileBackgroundColor(file);
566       updateFileIcon(file);
567       updateFileColor(file);
568     }
569   }
570
571   private final class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
572     @Override
573     public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
574       if (myCurrentWindow != null) {
575         final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor();
576         if (selectedEditor != null) {
577           return IdeFocusTraversalPolicy.getPreferredFocusedComponent(selectedEditor.getComponent(), this);
578         }
579       }
580       return IdeFocusTraversalPolicy.getPreferredFocusedComponent(EditorsSplitters.this, this);
581     }
582   }
583
584   @Nullable
585   public JPanel getTopPanel() {
586     return getComponentCount() > 0 ? (JPanel)getComponent(0) : null;
587   }
588
589   public EditorWindow getCurrentWindow() {
590     return myCurrentWindow;
591   }
592
593   public EditorWindow getOrCreateCurrentWindow(final VirtualFile file) {
594     final List<EditorWindow> windows = findWindows(file);
595     if (getCurrentWindow() == null) {
596       final Iterator<EditorWindow> iterator = myWindows.iterator();
597       if (!windows.isEmpty()) {
598         setCurrentWindow(windows.get(0), false);
599       }
600       else if (iterator.hasNext()) {
601         setCurrentWindow(iterator.next(), false);
602       }
603       else {
604         createCurrentWindow();
605       }
606     }
607     else if (!windows.isEmpty()) {
608       if (!windows.contains(getCurrentWindow())) {
609         setCurrentWindow(windows.get(0), false);
610       }
611     }
612     return getCurrentWindow();
613   }
614
615   public void createCurrentWindow() {
616     LOG.assertTrue(myCurrentWindow == null);
617     setCurrentWindow(createEditorWindow());
618     add(myCurrentWindow.myPanel, BorderLayout.CENTER);
619   }
620
621   protected EditorWindow createEditorWindow() {
622     return new EditorWindow(this);
623   }
624
625   /**
626    * sets the window passed as a current ('focused') window among all splitters. All file openings will be done inside this
627    * current window
628    * @param window a window to be set as current
629    * @param requestFocus whether to request focus to the editor currently selected in this window
630    */
631   public void setCurrentWindow(@Nullable final EditorWindow window, final boolean requestFocus) {
632     final EditorWithProviderComposite newEditor = window != null? window.getSelectedEditor() : null;
633
634     Runnable fireRunnable = new Runnable() {
635       @Override
636       public void run() {
637         getManager().fireSelectionChanged(newEditor);
638       }
639     };
640
641     setCurrentWindow(window);
642
643     getManager().updateFileName(window == null ? null : window.getSelectedFile());
644
645     if (window != null) {
646       final EditorWithProviderComposite selectedEditor = window.getSelectedEditor();
647       if (selectedEditor != null) {
648         fireRunnable.run();
649       }
650
651       if (requestFocus) {
652         window.requestFocus(true);
653       }
654     } else {
655       fireRunnable.run();
656     }
657   }
658
659   //---------------------------------------------------------
660
661   public EditorWithProviderComposite[] getEditorsComposites() {
662     final ArrayList<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>();
663
664     for (final EditorWindow myWindow : myWindows) {
665       final EditorWithProviderComposite[] editors = myWindow.getEditors();
666       ContainerUtil.addAll(res, editors);
667     }
668     return res.toArray(new EditorWithProviderComposite[res.size()]);
669   }
670
671   //---------------------------------------------------------
672
673   @NotNull
674   public List<EditorWithProviderComposite> findEditorComposites(final VirtualFile file) {
675     final ArrayList<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>();
676     for (final EditorWindow window : myWindows) {
677       final EditorWithProviderComposite fileComposite = window.findFileComposite(file);
678       if (fileComposite != null) {
679         res.add(fileComposite);
680       }
681     }
682     return res;
683   }
684
685   @NotNull
686   public List<EditorWindow> findWindows(final VirtualFile file) {
687     final ArrayList<EditorWindow> res = new ArrayList<EditorWindow>();
688     for (final EditorWindow window : myWindows) {
689       if (window.findFileComposite(file) != null) {
690         res.add(window);
691       }
692     }
693     return res;
694   }
695
696   @NotNull public EditorWindow [] getWindows() {
697     return myWindows.toArray(new EditorWindow [myWindows.size()]);
698   }
699
700   @NotNull public EditorWindow[] getOrderedWindows() {
701     final ArrayList<EditorWindow> res = new ArrayList<EditorWindow>();
702
703     // Collector for windows in tree ordering:
704     class Inner{
705       final void collect(final JPanel panel){
706         final Component comp = panel.getComponent(0);
707         if (comp instanceof Splitter) {
708           final Splitter splitter = (Splitter)comp;
709           collect((JPanel)splitter.getFirstComponent());
710           collect((JPanel)splitter.getSecondComponent());
711         }
712         else if (comp instanceof JPanel || comp instanceof JBTabs) {
713           final EditorWindow window = findWindowWith(comp);
714           if (window != null) {
715             res.add(window);
716           }
717         }
718       }
719     }
720
721     // get root component and traverse splitters tree:
722     if (getComponentCount() != 0) {
723       final Component comp = getComponent(0);
724       LOG.assertTrue(comp instanceof JPanel);
725       final JPanel panel = (JPanel)comp;
726       if (panel.getComponentCount() != 0) {
727         new Inner().collect (panel);
728       }
729     }
730
731     LOG.assertTrue(res.size() == myWindows.size());
732     return res.toArray(new EditorWindow [res.size()]);
733   }
734
735   @Nullable
736   private EditorWindow findWindowWith(final Component component) {
737     if (component != null) {
738       for (final EditorWindow window : myWindows) {
739         if (SwingUtilities.isDescendingFrom(component, window.myPanel)) {
740           return window;
741         }
742       }
743     }
744     return null;
745   }
746
747   public boolean isFloating() {
748     return false;
749   }
750
751   public boolean isPreview() {
752     return false;
753   }
754
755   private final class MyFocusWatcher extends FocusWatcher {
756     @Override
757     protected void focusedComponentChanged(final Component component, final AWTEvent cause) {
758       EditorWindow newWindow = null;
759
760       if (component != null) {
761         newWindow = findWindowWith(component);
762       }
763       else if (cause instanceof ContainerEvent && cause.getID() == ContainerEvent.COMPONENT_REMOVED) {
764         // do not change current window in case of child removal as in JTable.removeEditor
765         // otherwise Escape in a toolwindow will not focus editor with JTable content
766         return;
767       }
768
769       setCurrentWindow(newWindow);
770       setCurrentWindow(newWindow, false);
771     }
772   }
773
774   private final class MyTransferHandler extends TransferHandler {
775     private final FileDropHandler myFileDropHandler = new FileDropHandler(null);
776
777     @Override
778     public boolean importData(JComponent comp, Transferable t) {
779       if (myFileDropHandler.canHandleDrop(t.getTransferDataFlavors())) {
780         myFileDropHandler.handleDrop(t, myManager.getProject(), myCurrentWindow);
781         return true;
782       }
783       return false;
784     }
785
786     @Override
787     public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
788       return myFileDropHandler.canHandleDrop(transferFlavors);
789     }
790   }
791
792   private abstract static class ConfigTreeReader<T> {
793     @Nullable
794     public T process(@Nullable Element element, @Nullable T context) {
795       if (element == null) {
796         return null;
797       }
798       final Element splitterElement = element.getChild("splitter");
799       if (splitterElement != null) {
800         final Element first = splitterElement.getChild("split-first");
801         final Element second = splitterElement.getChild("split-second");
802         return processSplitter(splitterElement, first, second, context);
803       }
804
805       final Element leaf = element.getChild("leaf");
806       if (leaf == null) {
807         return null;
808       }
809
810       List<Element> fileElements = leaf.getChildren("file");
811       final List<Element> children = new ArrayList<Element>(fileElements.size());
812
813       // trim to EDITOR_TAB_LIMIT, ignoring CLOSE_NON_MODIFIED_FILES_FIRST policy
814       int toRemove = fileElements.size() - UISettings.getInstance().EDITOR_TAB_LIMIT;
815       for (Element fileElement : fileElements) {
816         if (toRemove <= 0 || Boolean.valueOf(fileElement.getAttributeValue(PINNED)).booleanValue()) {
817           children.add(fileElement);
818         }
819         else {
820           toRemove--;
821         }
822       }
823
824       return processFiles(children, context);
825     }
826
827     protected abstract @Nullable T processFiles(@NotNull List<Element> fileElements, @Nullable T context);
828     protected abstract @Nullable T processSplitter(@NotNull Element element, @Nullable Element firstChild, @Nullable Element secondChild, @Nullable T context);
829   }
830
831   private class UIBuilder extends ConfigTreeReader<JPanel> {
832
833     @Override
834     protected JPanel processFiles(@NotNull List<Element> fileElements, final JPanel context) {
835       final Ref<EditorWindow> windowRef = new Ref<EditorWindow>();
836       UIUtil.invokeAndWaitIfNeeded(new Runnable() {
837         @Override
838         public void run() {
839           windowRef.set(context == null ? createEditorWindow() : findWindowWith(context));
840         }
841       });
842       final EditorWindow window = windowRef.get();
843       LOG.assertTrue(window != null);
844       VirtualFile focusedFile = null;
845
846       for (int i = 0; i < fileElements.size(); i++) {
847         final Element file = fileElements.get(i);
848         try {
849           final FileEditorManagerImpl fileEditorManager = getManager();
850           Element historyElement = file.getChild(HistoryEntry.TAG);
851           VirtualFile virtualFile = HistoryEntry.getVirtualFile(historyElement);
852           Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
853           final HistoryEntry entry = new HistoryEntry(fileEditorManager.getProject(), historyElement);
854           final boolean isCurrentInTab = Boolean.valueOf(file.getAttributeValue(CURRENT_IN_TAB)).booleanValue();
855           Boolean pin = Boolean.valueOf(file.getAttributeValue(PINNED));
856           fileEditorManager.openFileImpl4(window, entry.myFile, entry, isCurrentInTab, isCurrentInTab, pin, i);
857           if (isCurrentInTab) {
858             focusedFile = entry.myFile;
859           }
860           if (document != null) {
861             // This is just to make sure document reference is kept on stack till this point
862             // so that document is available for folding state deserialization in HistoryEntry constructor
863             // and that document will be created only once during file opening
864             document.putUserData(DUMMY_KEY, null);
865           }
866           updateProgress();
867         }
868         catch (InvalidDataException e) {
869           if (ApplicationManager.getApplication().isUnitTestMode()) {
870             LOG.error(e);
871           }
872         }
873       }
874       if (focusedFile != null) {
875         getManager().addSelectionRecord(focusedFile, window);
876       }
877       return window.myPanel;
878     }
879
880     @Override
881     protected JPanel processSplitter(@NotNull Element splitterElement, Element firstChild, Element secondChild, final JPanel context) {
882       if (context == null) {
883         final boolean orientation = "vertical".equals(splitterElement.getAttributeValue("split-orientation"));
884         final float proportion = Float.valueOf(splitterElement.getAttributeValue("split-proportion")).floatValue();
885         final JPanel firstComponent = process(firstChild, null);
886         final JPanel secondComponent = process(secondChild, null);
887         final Ref<JPanel> panelRef = new Ref<JPanel>();
888         UIUtil.invokeAndWaitIfNeeded(new Runnable() {
889           @Override
890           public void run() {
891             JPanel panel = new JPanel(new BorderLayout());
892             panel.setOpaque(false);
893             Splitter splitter = new Splitter(orientation, proportion, 0.1f, 0.9f);
894             panel.add(splitter, BorderLayout.CENTER);
895             splitter.setFirstComponent(firstComponent);
896             splitter.setSecondComponent(secondComponent);
897             panelRef.set(panel);
898           }
899         });
900         return panelRef.get();
901       }
902       final Ref<JPanel> firstComponent = new Ref<JPanel>();
903       final Ref<JPanel> secondComponent = new Ref<JPanel>();
904       UIUtil.invokeAndWaitIfNeeded(new Runnable() {
905         @Override
906         public void run() {
907           if (context.getComponent(0) instanceof Splitter) {
908             Splitter splitter = (Splitter)context.getComponent(0);
909             firstComponent.set((JPanel)splitter.getFirstComponent());
910             secondComponent.set((JPanel)splitter.getSecondComponent());
911           }
912           else {
913             firstComponent.set(context);
914             secondComponent.set(context);
915           }
916         }
917       });
918       process(firstChild, firstComponent.get());
919       process(secondChild, secondComponent.get());
920       return context;
921     }
922   }
923 }