2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.daemon.impl;
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;
45 import java.awt.event.*;
47 import java.util.List;
49 public class EditorTracker extends AbstractProjectComponent {
50 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.EditorTracker");
52 private final WindowManager myWindowManager;
53 private final EditorFactory myEditorFactory;
55 @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
56 private final ToolWindowManager myToolwindowManager;
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();
63 private MyEditorFactoryListener myEditorFactoryListener;
64 private final EventDispatcher<EditorTrackerListener> myDispatcher = EventDispatcher.create(EditorTrackerListener.class);
66 private IdeFrameImpl myIdeFrame;
67 private Window myActiveWindow = null;
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) {
74 myWindowManager = windowManager;
75 myEditorFactory = editorFactory;
76 myToolwindowManager = toolwindowManager;
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);
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);
100 public String getComponentName() {
101 return "EditorTracker";
104 private void editorFocused(Editor editor) {
105 ApplicationManager.getApplication().assertIsDispatchThread();
106 Window window = myEditorToWindowMap.get(editor);
107 if (window == null) return;
109 List<Editor> list = myWindowToEditorsMap.get(window);
110 int index = list.indexOf(editor);
111 LOG.assertTrue(index >= 0);
112 if (list.isEmpty()) return;
114 for (int i = index - 1; i >= 0; i--) {
115 list.set(i + 1, list.get(i));
119 setActiveWindow(window);
122 private void registerEditor(Editor editor) {
123 unregisterEditor(editor);
125 final Window window = windowByEditor(editor);
126 if (window == null) return;
128 myEditorToWindowMap.put(editor, window);
129 List<Editor> list = myWindowToEditorsMap.get(window);
131 list = new ArrayList<Editor>();
132 myWindowToEditorsMap.put(window, list);
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);
141 setActiveWindow(window);
144 public void windowLostFocus(WindowEvent e) {
145 if (LOG.isDebugEnabled()) {
146 LOG.debug("windowLostFocus:" + window);
149 setActiveWindow(null);
152 myWindowToWindowFocusListenerMap.put(window, listener);
153 window.addWindowFocusListener(listener);
158 if (myActiveWindow == window) {
159 setActiveWindow(window); // to fire event
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);
171 if (editorsList.isEmpty()) {
172 myWindowToEditorsMap.remove(oldWindow);
173 final WindowFocusListener listener = myWindowToWindowFocusListenerMap.remove(oldWindow);
174 if (listener != null) oldWindow.removeWindowFocusListener(listener);
179 private Window windowByEditor(Editor editor) {
180 Window window = SwingUtilities.windowForComponent(editor.getComponent());
181 if (window instanceof IdeFrameImpl) {
182 if (window != myIdeFrame) return null;
188 public List<Editor> getActiveEditors() {
189 return myActiveEditors;
192 private void setActiveWindow(Window window) {
193 myActiveWindow = window;
194 List<Editor> editors = editorsByWindow(myActiveWindow);
195 setActiveEditors(editors);
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);
211 private void setActiveEditors(@NotNull List<Editor> editors) {
212 myActiveEditors = editors;
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);
222 myDispatcher.getMulticaster().activeEditorsChanged(editors);
225 public void addEditorTrackerListener(EditorTrackerListener listener) {
226 myDispatcher.addListener(listener);
229 public void removeEditorTrackerListener(EditorTrackerListener listener) {
230 myDispatcher.removeListener(listener);
233 private class MyEditorFactoryListener implements EditorFactoryListener {
234 private final Map<Editor, Runnable> myExecuteOnEditorRelease = new HashMap<Editor, Runnable>();
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;
242 final JComponent component = editor.getComponent();
243 final JComponent contentComponent = editor.getContentComponent();
245 final HierarchyListener hierarchyListener = new HierarchyListener() {
246 public void hierarchyChanged(HierarchyEvent e) {
247 registerEditor(editor);
250 component.addHierarchyListener(hierarchyListener);
252 final FocusListener focusListener = new FocusListener() {
253 public void focusGained(FocusEvent e) {
254 editorFocused(editor);
257 public void focusLost(FocusEvent e) {
260 contentComponent.addFocusListener(focusListener);
262 myExecuteOnEditorRelease.put(event.getEditor(), new Runnable() {
264 component.removeHierarchyListener(hierarchyListener);
265 contentComponent.removeFocusListener(focusListener);
270 public void editorReleased(EditorFactoryEvent event) {
271 final Editor editor = event.getEditor();
272 if (editor.getProject() != null && editor.getProject() != myProject) return;
273 unregisterEditor(editor);
277 private void dispose(Editor editor) {
278 if (editor == null) {
279 for (Runnable r : myExecuteOnEditorRelease.values()) {
282 myExecuteOnEditorRelease.clear();
285 final Runnable runnable = myExecuteOnEditorRelease.get(editor);
286 if (runnable != null) {
288 myExecuteOnEditorRelease.remove(editor);