cleanup
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / EditorTracker.java
1 /*
2  * Copyright 2000-2009 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
17 package com.intellij.codeInsight.daemon.impl;
18
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.components.AbstractProjectComponent;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.editor.EditorFactory;
25 import com.intellij.openapi.editor.event.EditorFactoryEvent;
26 import com.intellij.openapi.editor.event.EditorFactoryListener;
27 import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
28 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
29 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.Disposer;
32 import com.intellij.openapi.wm.ToolWindowManager;
33 import com.intellij.openapi.wm.WindowManager;
34 import com.intellij.openapi.wm.ex.WindowManagerEx;
35 import com.intellij.openapi.wm.impl.IdeFrameImpl;
36 import com.intellij.psi.PsiDocumentManager;
37 import com.intellij.psi.PsiFile;
38 import com.intellij.util.EventDispatcher;
39 import com.intellij.util.SmartList;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42
43 import javax.swing.*;
44 import java.awt.*;
45 import java.awt.event.*;
46 import java.util.*;
47 import java.util.List;
48
49 public class EditorTracker extends AbstractProjectComponent {
50   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.EditorTracker");
51
52   private final WindowManager myWindowManager;
53   private final EditorFactory myEditorFactory;
54
55   @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) 
56   private final ToolWindowManager myToolwindowManager;
57
58   private final Map<Window, List<Editor>> myWindowToEditorsMap = new HashMap<Window, List<Editor>>();
59   private final Map<Window, WindowFocusListener> myWindowToWindowFocusListenerMap = new HashMap<Window, WindowFocusListener>();
60   private final Map<Editor, Window> myEditorToWindowMap = new HashMap<Editor, Window>();
61   private List<Editor> myActiveEditors = Collections.emptyList();
62
63   private MyEditorFactoryListener myEditorFactoryListener;
64   private final EventDispatcher<EditorTrackerListener> myDispatcher = EventDispatcher.create(EditorTrackerListener.class);
65
66   private IdeFrameImpl myIdeFrame;
67   private Window myActiveWindow = null;
68
69   //todo:
70   //toolwindow manager is unfortunately needed since
71   //it actually initializes frame in WindowManager
72   public EditorTracker(Project project, final WindowManager windowManager, final EditorFactory editorFactory, ToolWindowManager toolwindowManager) {
73     super(project);
74     myWindowManager = windowManager;
75     myEditorFactory = editorFactory;
76     myToolwindowManager = toolwindowManager;
77   }
78
79   public void projectOpened() {
80     myIdeFrame = ((WindowManagerEx)myWindowManager).getFrame(myProject);
81     myProject.getMessageBus().connect(myProject).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() {
82       public void selectionChanged(FileEditorManagerEvent event) {
83         if (myIdeFrame.getFocusOwner() == null) return;
84         setActiveWindow(myIdeFrame);
85       }
86     });
87
88     myEditorFactoryListener = new MyEditorFactoryListener();
89     myEditorFactory.addEditorFactoryListener(myEditorFactoryListener);
90     Disposer.register(myProject, new Disposable() {
91       public void dispose() {
92         myEditorFactoryListener.dispose(null);
93         myEditorFactory.removeEditorFactoryListener(myEditorFactoryListener);
94       }
95     });
96   }
97
98   @NonNls
99   @NotNull
100   public String getComponentName() {
101     return "EditorTracker";
102   }
103
104   private void editorFocused(Editor editor) {
105     ApplicationManager.getApplication().assertIsDispatchThread();
106     Window window = myEditorToWindowMap.get(editor);
107     if (window == null) return;
108
109     List<Editor> list = myWindowToEditorsMap.get(window);
110     int index = list.indexOf(editor);
111     LOG.assertTrue(index >= 0);
112     if (list.isEmpty()) return;
113
114     for (int i = index - 1; i >= 0; i--) {
115       list.set(i + 1, list.get(i));
116     }
117     list.set(0, editor);
118
119     setActiveWindow(window);
120   }
121
122   private void registerEditor(Editor editor) {
123     unregisterEditor(editor);
124
125     final Window window = windowByEditor(editor);
126     if (window == null) return;
127
128     myEditorToWindowMap.put(editor, window);
129     List<Editor> list = myWindowToEditorsMap.get(window);
130     if (list == null) {
131       list = new ArrayList<Editor>();
132       myWindowToEditorsMap.put(window, list);
133
134       if (!(window instanceof IdeFrameImpl)) {
135         WindowFocusListener listener =  new WindowFocusListener() {
136           public void windowGainedFocus(WindowEvent e) {
137             if (LOG.isDebugEnabled()) {
138               LOG.debug("windowGainedFocus:" + window);
139             }
140
141             setActiveWindow(window);
142           }
143
144           public void windowLostFocus(WindowEvent e) {
145             if (LOG.isDebugEnabled()) {
146               LOG.debug("windowLostFocus:" + window);
147             }
148
149             setActiveWindow(null);
150           }
151         };
152         myWindowToWindowFocusListenerMap.put(window, listener);
153         window.addWindowFocusListener(listener);
154       }
155     }
156     list.add(editor);
157
158     if (myActiveWindow == window) {
159       setActiveWindow(window); // to fire event
160     }
161   }
162
163   private void unregisterEditor(Editor editor) {
164     Window oldWindow = myEditorToWindowMap.get(editor);
165     if (oldWindow != null) {
166       myEditorToWindowMap.remove(editor);
167       List<Editor> editorsList = myWindowToEditorsMap.get(oldWindow);
168       boolean removed = editorsList.remove(editor);
169       LOG.assertTrue(removed);
170       
171       if (editorsList.isEmpty()) {
172         myWindowToEditorsMap.remove(oldWindow);
173         final WindowFocusListener listener = myWindowToWindowFocusListenerMap.remove(oldWindow);
174         if (listener != null) oldWindow.removeWindowFocusListener(listener);
175       }
176     }
177   }
178
179   private Window windowByEditor(Editor editor) {
180     Window window = SwingUtilities.windowForComponent(editor.getComponent());
181     if (window instanceof IdeFrameImpl) {
182       if (window != myIdeFrame) return null;
183     }
184     return window;
185   }
186
187   @NotNull
188   public List<Editor> getActiveEditors() {
189     return myActiveEditors;
190   }
191
192   private void setActiveWindow(Window window) {
193     myActiveWindow = window;
194     List<Editor> editors = editorsByWindow(myActiveWindow);
195     setActiveEditors(editors);
196   }
197
198   @NotNull
199   private List<Editor> editorsByWindow(Window window) {
200     List<Editor> list = myWindowToEditorsMap.get(window);
201     if (list == null) return Collections.emptyList();
202     List<Editor> filtered = new SmartList<Editor>();
203     for (Editor editor : list) {
204       if (editor.getContentComponent().isShowing()) {
205         filtered.add(editor);
206       }
207     }
208     return filtered;
209   }
210
211   private void setActiveEditors(@NotNull List<Editor> editors) {
212     myActiveEditors = editors;
213
214     if (LOG.isDebugEnabled()) {
215       LOG.debug("active editors changed:");
216       for (Editor editor : editors) {
217         PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
218         LOG.debug("    " + psiFile);
219       }
220     }
221
222     myDispatcher.getMulticaster().activeEditorsChanged(editors);
223   }
224
225   public void addEditorTrackerListener(EditorTrackerListener listener) {
226     myDispatcher.addListener(listener);
227   }
228
229   public void removeEditorTrackerListener(EditorTrackerListener listener) {
230     myDispatcher.removeListener(listener);
231   }
232
233   private class MyEditorFactoryListener implements EditorFactoryListener {
234     private final Map<Editor, Runnable> myExecuteOnEditorRelease = new HashMap<Editor, Runnable>();
235
236     public void editorCreated(EditorFactoryEvent event) {
237       final Editor editor = event.getEditor();
238       if (editor.getProject() != null && editor.getProject() != myProject) return;
239       PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
240       if (psiFile == null) return;
241
242       final JComponent component = editor.getComponent();
243       final JComponent contentComponent = editor.getContentComponent();
244
245       final HierarchyListener hierarchyListener = new HierarchyListener() {
246         public void hierarchyChanged(HierarchyEvent e) {
247           registerEditor(editor);
248         }
249       };
250       component.addHierarchyListener(hierarchyListener);
251
252       final FocusListener focusListener = new FocusListener() {
253         public void focusGained(FocusEvent e) {
254           editorFocused(editor);
255         }
256
257         public void focusLost(FocusEvent e) {
258         }
259       };
260       contentComponent.addFocusListener(focusListener);
261
262       myExecuteOnEditorRelease.put(event.getEditor(), new Runnable() {
263         public void run() {
264           component.removeHierarchyListener(hierarchyListener);
265           contentComponent.removeFocusListener(focusListener);
266         }
267       });
268     }
269
270     public void editorReleased(EditorFactoryEvent event) {
271       final Editor editor = event.getEditor();
272       if (editor.getProject() != null && editor.getProject() != myProject) return;
273       unregisterEditor(editor);
274       dispose(editor);
275     }
276
277     private void dispose(Editor editor) {
278       if (editor == null) {
279         for (Runnable r : myExecuteOnEditorRelease.values()) {
280           r.run();
281         }
282         myExecuteOnEditorRelease.clear();
283       }
284       else {
285         final Runnable runnable = myExecuteOnEditorRelease.get(editor);
286         if (runnable != null) {
287           runnable.run();
288           myExecuteOnEditorRelease.remove(editor);
289         }
290       }
291     }
292   }
293 }