IDEA-127739 Navigation Tab
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / PreviewPanel.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.ui.UISettings;
20 import com.intellij.ide.ui.UISettingsListener;
21 import com.intellij.openapi.actionSystem.AnAction;
22 import com.intellij.openapi.actionSystem.AnActionEvent;
23 import com.intellij.openapi.actionSystem.DefaultActionGroup;
24 import com.intellij.openapi.actionSystem.ToggleAction;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.fileEditor.FileEditorManager;
27 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.Key;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.openapi.wm.*;
33 import com.intellij.openapi.wm.impl.ToolWindowImpl;
34 import com.intellij.openapi.wm.impl.content.ToolWindowContentUi;
35 import com.intellij.ui.content.Content;
36 import com.intellij.ui.content.ContentManager;
37 import com.intellij.ui.content.ContentManagerAdapter;
38 import com.intellij.ui.content.ContentManagerEvent;
39 import com.intellij.ui.docking.DockManager;
40 import com.intellij.util.ArrayUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import javax.swing.*;
45 import java.awt.*;
46 import java.util.ArrayList;
47 import java.util.EnumSet;
48
49 class PreviewPanel extends JPanel {
50
51   private CardLayout myLayout;
52
53   enum ContentType {Files, Usages, Diagrams, Documentation}
54
55   private static final Key<VirtualFile> FILE_KEY = Key.create("v_file");
56   private static final int HISTORY_LIMIT = 10;
57
58   private final Project myProject;
59   private final FileEditorManagerImpl myManager;
60   private final DockManager myDockManager;
61   private EditorWindow myWindow;
62   private EditorsSplitters myEditorsSplitters;
63   private ArrayList<VirtualFile> myHistory = new ArrayList<VirtualFile>();
64   private VirtualFile myModifiedFile = null;
65   private ToolWindowImpl myToolWindow;
66   private VirtualFile myAwaitingForOpen = null;
67   private ContentManager myContentManager;
68   private Content myStubContent;
69   private boolean myBlocked = false;
70
71   private EnumSet<ContentType> myTypes = EnumSet.noneOf(ContentType.class);
72
73   static boolean isAvailable() {
74     return UISettings.getInstance().NAVIGATE_TO_PREVIEW;
75   }
76
77   PreviewPanel(Project project, FileEditorManagerImpl manager, DockManager dockManager) {
78     myProject = project;
79     myManager = manager;
80     myDockManager = dockManager;
81   }
82
83   /*
84   * @return null if preview is not avalable
85    */
86   @Nullable
87   EditorWindow getWindow() {
88     if (!isAvailable() || isBlocked() || myProject.isDisposed()) return null;
89     initToolWindowIfNeed();
90     return myWindow;
91   }
92
93   boolean isBlocked() {
94     return myBlocked;
95   }
96
97   private void initToolWindowIfNeed() {
98     if (!isAvailable() || ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PREVIEW) != null) return;
99
100     myToolWindow = (ToolWindowImpl)ToolWindowManager.getInstance(myProject)
101       .registerToolWindow(ToolWindowId.PREVIEW, this, ToolWindowAnchor.RIGHT, myProject, false);
102     UISettings.getInstance().addUISettingsListener(new UISettingsListener() {
103       @Override
104       public void uiSettingsChanged(UISettings source) {
105         if (!isAvailable()) {
106           VirtualFile[] files = myWindow.getFiles();
107           for (VirtualFile file : files) {
108             close(file);
109           }
110           ToolWindowManager.getInstance(myProject).unregisterToolWindow(ToolWindowId.PREVIEW);
111         }
112       }
113     }, myProject);
114     myToolWindow.setIcon(AllIcons.Actions.PreviewDetails);
115     myToolWindow.setContentUiType(ToolWindowContentUiType.COMBO, null);
116     myContentManager = myToolWindow.getContentManager();
117     myStubContent = myContentManager.getContent(0);
118     myContentManager.addContentManagerListener(new ContentManagerAdapter() {
119       @Override
120       public void selectionChanged(ContentManagerEvent event) {
121         final VirtualFile file = event.getContent().getUserData(FILE_KEY);
122         if (event.getOperation() == ContentManagerEvent.ContentOperation.remove && file != null && file.equals(myModifiedFile)) {
123           close(file);
124           return;
125         }
126
127         if (event.getOperation() != ContentManagerEvent.ContentOperation.add) return;
128
129         if (file != null) {
130           event.getContent().setComponent(PreviewPanel.this);//Actually we share the same component between contents
131           if (!file.equals(myWindow.getSelectedFile())) {
132             ApplicationManager.getApplication().invokeLater(new Runnable() {
133               @Override
134               public void run() {
135                 myManager.openFileWithProviders(file, false, myWindow);
136               }
137             });
138           }
139         }
140       }
141     });
142
143     myEditorsSplitters = new MyEditorsSplitters();
144
145     myProject.getMessageBus().connect().subscribe(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER,
146                                                   new FileEditorManagerListener.Before() {
147                                                     @Override
148                                                     public void beforeFileOpened(@NotNull FileEditorManager source,
149                                                                                  @NotNull VirtualFile file) {
150                                                       myAwaitingForOpen = file;
151                                                       VirtualFile currentFile = getCurrentFile();
152                                                       if (currentFile != null &&
153                                                           currentFile.equals(myModifiedFile) &&
154                                                           !currentFile.equals(file)) {
155                                                         close(currentFile);
156                                                       }
157                                                     }
158
159                                                     @Override
160                                                     public void beforeFileClosed(@NotNull FileEditorManager source,
161                                                                                  @NotNull VirtualFile file) {
162                                                       checkStubContent();
163                                                     }
164                                                   });
165     myEditorsSplitters.createCurrentWindow();
166     myWindow = myEditorsSplitters.getCurrentWindow();
167     myWindow.setTabsPlacement(UISettings.TABS_NONE);
168     myLayout = new CardLayout();
169     setLayout(myLayout);
170     add(ContentType.Files.toString(), myEditorsSplitters);
171     //add(ContentType.Usages.toString(), myUsagesPreview);??? tree or editor ???
172     //add(ContentType.Diagrams.toString(), myDiagramPanel);
173     //add(ContentType.Documentation.toString(), myDocumentationPanel);//todo
174     myToolWindow.setTitleActions(new MoveToEditorTabsAction(), new CloseFileAction());
175     ArrayList<AnAction> myGearActions = new ArrayList<AnAction>();
176     for (ContentType contentType : ContentType.values()) {
177       myGearActions.add(new ContentTypeToggleAction(contentType));
178     }
179     myToolWindow.setAdditionalGearActions(new DefaultActionGroup("Preview", myGearActions));
180     myToolWindow.hide(null);
181   }
182
183   @Nullable
184   private VirtualFile getCurrentFile() {
185     VirtualFile[] files = myWindow.getFiles();
186     return files.length == 1 ? files[0] : null;
187   }
188
189   @NotNull
190   private Content addContent(VirtualFile file) {
191     myHistory.add(file);
192     while (myHistory.size() > HISTORY_LIMIT) {
193       myHistory.remove(0);
194     }
195     String title =
196       StringUtil.getShortened(EditorTabbedContainer.calcTabTitle(myProject, file), UISettings.getInstance().EDITOR_TAB_TITLE_LIMIT);
197
198     Content content = myContentManager.getFactory().createContent(this, title, false);
199     content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
200     content.putUserData(FILE_KEY, file);
201     content.setIcon(file.getFileType().getIcon());
202     content.setPopupIcon(file.getFileType().getIcon());
203
204     myContentManager.addContent(content);
205     checkStubContent();
206     return content;
207   }
208
209   private void setSelected(VirtualFile file) {
210     Content content = getContent(file);
211     if (content == null) {
212       content = addContent(file);
213     }
214     myContentManager.setSelectedContent(content);
215   }
216
217   @Nullable
218   private Content getContent(VirtualFile file) {
219     Content[] contents = myContentManager.getContents();
220     for (Content content : contents) {
221       if (file.equals(content.getUserData(FILE_KEY))) {
222         return content;
223       }
224     }
225     return null;
226   }
227
228   private void checkStubContent() {
229     if (myContentManager.getContents().length == 0) {
230       myToolWindow.getComponent().putClientProperty(ToolWindowContentUi.HIDE_ID_LABEL, "false");
231       myStubContent.setComponent(this);
232       myContentManager.addContent(myStubContent);
233       ApplicationManager.getApplication().invokeLater(new Runnable() {
234         @Override
235         public void run() {
236           if (myContentManager.getIndexOfContent(myStubContent) != -1) {
237             toggleToolWindow(false);
238           }
239         }
240       });
241     }
242     else if (myContentManager.getContents().length > 1) {
243       myToolWindow.getComponent().putClientProperty(ToolWindowContentUi.HIDE_ID_LABEL, "true");
244       myContentManager.removeContent(myStubContent, false);
245     }
246   }
247
248   private void close(@NotNull VirtualFile file) {
249     myHistory.remove(file);
250     if (ArrayUtil.find(myEditorsSplitters.getOpenFiles(), file) != -1) {
251       myEditorsSplitters.closeFile(file, false);
252     }
253     if (file.equals(myAwaitingForOpen)) {
254       myAwaitingForOpen = null;
255     }
256     if (file.equals(myModifiedFile)) {
257       myBlocked = true;
258       try {
259         myManager.openFileWithProviders(myModifiedFile, false, true);
260       }
261       finally {
262         myBlocked = false;
263       }
264       myModifiedFile = null;
265     }
266     Content content = getContent(file);
267     if (content != null) {
268       myContentManager.removeContent(content, false);
269       checkStubContent();
270     }
271   }
272
273   private void toggleToolWindow(boolean activate) {
274     ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PREVIEW);
275     if (toolWindow != null) {
276       if (activate) {
277         toolWindow.activate(null, false);
278       }
279       else {
280         toolWindow.hide(null);
281       }
282     }
283   }
284
285   private class MoveToEditorTabsAction extends AnAction {
286
287     public MoveToEditorTabsAction() {
288       super("Move to main tabs", "Move to main tabs", AllIcons.Duplicates.SendToTheLeftGrayed);
289     }
290
291     @Override
292     public void actionPerformed(AnActionEvent e) {
293       VirtualFile virtualFile = getCurrentFile();
294       if (virtualFile == null) {
295         return;
296       }
297
298       EditorWindow window = myManager.getCurrentWindow();
299       if (window == null) { //main tab set is still not created, rare situation
300         myManager.getMainSplitters().createCurrentWindow();
301         window = myManager.getCurrentWindow();
302       }
303       myManager.openFileWithProviders(virtualFile, true, window);
304       close(virtualFile);
305       toggleToolWindow(false);
306     }
307   }
308
309
310   private class CloseFileAction extends AnAction {
311     public CloseFileAction() {
312       super("Close", "Close", AllIcons.Actions.Close);
313     }
314
315     @Override
316     public void actionPerformed(AnActionEvent e) {
317       for (VirtualFile file : myHistory.toArray(new VirtualFile[myHistory.size()])) {
318         close(file);
319       }
320       toggleToolWindow(false);
321     }
322   }
323
324   private class ContentTypeToggleAction extends ToggleAction {
325     private final ContentType myContentType;
326
327     ContentTypeToggleAction(ContentType contentType) {
328       super(contentType.toString());
329       myContentType = contentType;
330     }
331
332     @Override
333     public boolean isSelected(AnActionEvent e) {
334       return myTypes.contains(myContentType);
335     }
336
337     @Override
338     public void setSelected(AnActionEvent e, boolean state) {
339       if (state) {
340         myTypes.add(myContentType);
341       } else {
342         myTypes.remove(myContentType);
343       }
344     }
345   }
346
347   private class MyEditorsSplitters extends EditorsSplitters {
348     public MyEditorsSplitters() {
349       super(myManager, myDockManager, false);
350     }
351
352     @Override
353     protected void afterFileOpen(VirtualFile file) {
354       if (file.equals(myAwaitingForOpen)) {
355         setSelected(file);
356       }
357       myAwaitingForOpen = null;
358     }
359
360     @Override
361     protected void afterFileClosed(VirtualFile file) {
362       close(file);
363     }
364
365     @Override
366     public void updateFileIcon(@NotNull VirtualFile file) {
367       EditorWithProviderComposite composite = myWindow.findFileComposite(file);
368       if (composite != null && composite.isModified()) {
369         myModifiedFile = file;
370       }
371     }
372
373     @Override
374     protected EditorWindow createEditorWindow() {
375       return new EditorWindow(this) {
376         @Override
377         protected void onBeforeSetEditor(VirtualFile file) {
378           VirtualFile currentFile = getCurrentFile();
379           if (currentFile != null && currentFile.equals(myModifiedFile)) {
380             myBlocked = true;
381             try {
382               myManager.openFileWithProviders(myModifiedFile, false, true);
383             }
384             finally {
385               myBlocked = false;
386             }
387           }
388           else {
389             toggleToolWindow(true);
390           }
391         }
392       };
393     }
394
395     @Override
396     public void setTabsPlacement(int tabPlacement) {
397       super.setTabsPlacement(UISettings.TABS_NONE);
398     }
399
400     @Override
401     public boolean isPreview() {
402       return true;
403     }
404   }
405 }