blob: c88ac636243acbdf378effcb1ae3a4b1db36926e [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.impl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.editor.event.EditorFactoryListener;
import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.EventDispatcher;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
public class EditorTracker extends AbstractProjectComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.EditorTracker");
private final WindowManager myWindowManager;
private final EditorFactory myEditorFactory;
private final Map<Window, List<Editor>> myWindowToEditorsMap = new HashMap<Window, List<Editor>>();
private final Map<Window, WindowFocusListener> myWindowToWindowFocusListenerMap = new HashMap<Window, WindowFocusListener>();
private final Map<Editor, Window> myEditorToWindowMap = new HashMap<Editor, Window>();
private List<Editor> myActiveEditors = Collections.emptyList();
private final EventDispatcher<EditorTrackerListener> myDispatcher = EventDispatcher.create(EditorTrackerListener.class);
private IdeFrameImpl myIdeFrame;
private Window myActiveWindow = null;
public EditorTracker(Project project,
final WindowManager windowManager,
final EditorFactory editorFactory) {
super(project);
myWindowManager = windowManager;
myEditorFactory = editorFactory;
}
@Override
public void projectOpened() {
myIdeFrame = ((WindowManagerEx)myWindowManager).getFrame(myProject);
myProject.getMessageBus().connect(myProject).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() {
@Override
public void selectionChanged(@NotNull FileEditorManagerEvent event) {
if (myIdeFrame == null || myIdeFrame.getFocusOwner() == null) return;
setActiveWindow(myIdeFrame);
}
});
final MyEditorFactoryListener myEditorFactoryListener = new MyEditorFactoryListener();
myEditorFactory.addEditorFactoryListener(myEditorFactoryListener,myProject);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
myEditorFactoryListener.dispose(null);
}
});
}
@Override
@NonNls
@NotNull
public String getComponentName() {
return "EditorTracker";
}
private void editorFocused(Editor editor) {
ApplicationManager.getApplication().assertIsDispatchThread();
Window window = myEditorToWindowMap.get(editor);
if (window == null) return;
List<Editor> list = myWindowToEditorsMap.get(window);
int index = list.indexOf(editor);
LOG.assertTrue(index >= 0);
if (list.isEmpty()) return;
for (int i = index - 1; i >= 0; i--) {
list.set(i + 1, list.get(i));
}
list.set(0, editor);
setActiveWindow(window);
}
private void registerEditor(Editor editor) {
unregisterEditor(editor);
final Window window = windowByEditor(editor);
if (window == null) return;
myEditorToWindowMap.put(editor, window);
List<Editor> list = myWindowToEditorsMap.get(window);
if (list == null) {
list = new ArrayList<Editor>();
myWindowToEditorsMap.put(window, list);
if (!(window instanceof IdeFrameImpl)) {
WindowFocusListener listener = new WindowFocusListener() {
@Override
public void windowGainedFocus(WindowEvent e) {
if (LOG.isDebugEnabled()) {
LOG.debug("windowGainedFocus:" + window);
}
setActiveWindow(window);
}
@Override
public void windowLostFocus(WindowEvent e) {
if (LOG.isDebugEnabled()) {
LOG.debug("windowLostFocus:" + window);
}
setActiveWindow(null);
}
};
myWindowToWindowFocusListenerMap.put(window, listener);
window.addWindowFocusListener(listener);
if (window.isFocused()) { // windowGainedFocus is missed; activate by force
setActiveWindow(window);
}
}
}
list.add(editor);
if (myActiveWindow == window) {
setActiveWindow(window); // to fire event
}
}
private void unregisterEditor(Editor editor) {
Window oldWindow = myEditorToWindowMap.get(editor);
if (oldWindow != null) {
myEditorToWindowMap.remove(editor);
List<Editor> editorsList = myWindowToEditorsMap.get(oldWindow);
boolean removed = editorsList.remove(editor);
LOG.assertTrue(removed);
if (editorsList.isEmpty()) {
myWindowToEditorsMap.remove(oldWindow);
final WindowFocusListener listener = myWindowToWindowFocusListenerMap.remove(oldWindow);
if (listener != null) oldWindow.removeWindowFocusListener(listener);
}
}
}
private Window windowByEditor(Editor editor) {
Window window = SwingUtilities.windowForComponent(editor.getComponent());
if (window instanceof IdeFrameImpl) {
if (window != myIdeFrame) return null;
}
return window;
}
@NotNull
public List<Editor> getActiveEditors() {
return myActiveEditors;
}
private void setActiveWindow(Window window) {
myActiveWindow = window;
List<Editor> editors = editorsByWindow(myActiveWindow);
setActiveEditors(editors);
}
@NotNull
private List<Editor> editorsByWindow(Window window) {
List<Editor> list = myWindowToEditorsMap.get(window);
if (list == null) return Collections.emptyList();
List<Editor> filtered = new SmartList<Editor>();
for (Editor editor : list) {
if (editor.getContentComponent().isShowing()) {
filtered.add(editor);
}
}
return filtered;
}
void setActiveEditors(@NotNull List<Editor> editors) {
myActiveEditors = editors;
if (LOG.isDebugEnabled()) {
LOG.debug("active editors changed:");
for (Editor editor : editors) {
PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
LOG.debug(" " + psiFile);
}
}
myDispatcher.getMulticaster().activeEditorsChanged(editors);
}
public void addEditorTrackerListener(@NotNull EditorTrackerListener listener, @NotNull Disposable parentDisposable) {
myDispatcher.addListener(listener,parentDisposable);
}
private class MyEditorFactoryListener implements EditorFactoryListener {
private final Map<Editor, Runnable> myExecuteOnEditorRelease = new HashMap<Editor, Runnable>();
@Override
public void editorCreated(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
if (editor.getProject() != null && editor.getProject() != myProject) return;
PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
if (psiFile == null) return;
final JComponent component = editor.getComponent();
final JComponent contentComponent = editor.getContentComponent();
final HierarchyListener hierarchyListener = new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
registerEditor(editor);
}
};
component.addHierarchyListener(hierarchyListener);
final FocusListener focusListener = new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
editorFocused(editor);
}
@Override
public void focusLost(FocusEvent e) {
}
};
contentComponent.addFocusListener(focusListener);
myExecuteOnEditorRelease.put(event.getEditor(), new Runnable() {
@Override
public void run() {
component.removeHierarchyListener(hierarchyListener);
contentComponent.removeFocusListener(focusListener);
}
});
}
@Override
public void editorReleased(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
if (editor.getProject() != null && editor.getProject() != myProject) return;
unregisterEditor(editor);
dispose(editor);
}
private void dispose(Editor editor) {
if (editor == null) {
for (Runnable r : myExecuteOnEditorRelease.values()) {
r.run();
}
myExecuteOnEditorRelease.clear();
}
else {
final Runnable runnable = myExecuteOnEditorRelease.get(editor);
if (runnable != null) {
runnable.run();
myExecuteOnEditorRelease.remove(editor);
}
}
}
}
}