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