blob: 0beefbbf1a4c1e072a0dc3601edde82fbda0bfc3 [file] [log] [blame]
/*
* Copyright 2000-2013 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.preview;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
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.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseMotionListener;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.util.Alarm;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
public class ImageOrColorPreviewManager implements Disposable, EditorMouseMotionListener {
private static final Logger LOG = Logger.getInstance(ImageOrColorPreviewManager.class);
private static final Key<KeyListener> EDITOR_LISTENER_ADDED = Key.create("previewManagerListenerAdded");
private final Alarm alarm = new Alarm();
/**
* this collection should not keep strong references to the elements
* @link getPsiElementsAt()
*/
@Nullable
private Collection<PsiElement> myElements;
public ImageOrColorPreviewManager(EditorFactory editorFactory) {
// we don't use multicaster because we don't want to serve all editors - only supported
editorFactory.addEditorFactoryListener(new EditorFactoryListener() {
@Override
public void editorCreated(@NotNull EditorFactoryEvent event) {
registerListeners(event.getEditor());
}
@Override
public void editorReleased(@NotNull EditorFactoryEvent event) {
Editor editor = event.getEditor();
if (editor.isOneLineMode()) {
return;
}
KeyListener keyListener = EDITOR_LISTENER_ADDED.get(editor);
if (keyListener != null) {
EDITOR_LISTENER_ADDED.set(editor, null);
editor.getContentComponent().removeKeyListener(keyListener);
editor.removeEditorMouseMotionListener(ImageOrColorPreviewManager.this);
}
}
}, this);
}
private void registerListeners(final Editor editor) {
if (editor.isOneLineMode()) {
return;
}
Project project = editor.getProject();
if (project == null || project.isDisposed()) {
return;
}
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (psiFile == null || psiFile instanceof PsiCompiledElement || !isSupportedFile(psiFile)) {
return;
}
editor.addEditorMouseMotionListener(this);
KeyListener keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SHIFT && !editor.isOneLineMode()) {
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
if (pointerInfo != null) {
Point location = pointerInfo.getLocation();
SwingUtilities.convertPointFromScreen(location, editor.getContentComponent());
alarm.cancelAllRequests();
alarm.addRequest(new PreviewRequest(location, editor, true), 100);
}
}
}
};
editor.getContentComponent().addKeyListener(keyListener);
EDITOR_LISTENER_ADDED.set(editor, keyListener);
}
private static boolean isSupportedFile(PsiFile psiFile) {
for (PsiFile file : psiFile.getViewProvider().getAllFiles()) {
for (ElementPreviewProvider provider : Extensions.getExtensions(ElementPreviewProvider.EP_NAME)) {
if (provider.isSupportedFile(file)) {
return true;
}
}
}
return false;
}
@NotNull
private static Collection<PsiElement> getPsiElementsAt(Point point, Editor editor) {
if (editor.isDisposed()) {
return Collections.emptySet();
}
Project project = editor.getProject();
if (project == null || project.isDisposed()) {
return Collections.emptySet();
}
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final Document document = editor.getDocument();
PsiFile psiFile = documentManager.getPsiFile(document);
if (psiFile == null || psiFile instanceof PsiCompiledElement || !psiFile.isValid()) {
return Collections.emptySet();
}
final Set<PsiElement> elements = Collections.newSetFromMap(new WeakHashMap<PsiElement, Boolean>());
final int offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(point));
if (documentManager.isCommitted(document)) {
ContainerUtil.addIfNotNull(elements, InjectedLanguageUtil.findElementAtNoCommit(psiFile, offset));
}
for (PsiFile file : psiFile.getViewProvider().getAllFiles()) {
ContainerUtil.addIfNotNull(elements, file.findElementAt(offset));
}
return elements;
}
@Override
public void dispose() {
alarm.cancelAllRequests();
myElements = null;
}
@Override
public void mouseMoved(@NotNull EditorMouseEvent event) {
Editor editor = event.getEditor();
if (editor.isOneLineMode()) {
return;
}
alarm.cancelAllRequests();
Point point = event.getMouseEvent().getPoint();
if (myElements == null && event.getMouseEvent().isShiftDown()) {
alarm.addRequest(new PreviewRequest(point, editor, false), 100);
}
else {
Collection<PsiElement> elements = myElements;
if (!getPsiElementsAt(point, editor).equals(elements)) {
myElements = null;
for (ElementPreviewProvider provider : Extensions.getExtensions(ElementPreviewProvider.EP_NAME)) {
try {
if (elements != null) {
for (PsiElement element : elements) {
provider.hide(element, editor);
}
} else {
provider.hide(null, editor);
}
}
catch (Exception e) {
LOG.error(e);
}
}
}
}
}
@Override
public void mouseDragged(EditorMouseEvent e) {
// nothing
}
private final class PreviewRequest implements Runnable {
private final Point point;
private final Editor editor;
private final boolean keyTriggered;
public PreviewRequest(Point point, Editor editor, boolean keyTriggered) {
this.point = point;
this.editor = editor;
this.keyTriggered = keyTriggered;
}
@Override
public void run() {
Collection<PsiElement> elements = getPsiElementsAt(point, editor);
if (elements.equals(myElements)) return;
for (PsiElement element : elements) {
if (element == null || !element.isValid()) {
return;
}
if (PsiDocumentManager.getInstance(element.getProject()).isUncommited(editor.getDocument()) ||
DumbService.getInstance(element.getProject()).isDumb()) {
return;
}
for (ElementPreviewProvider provider : ElementPreviewProvider.EP_NAME.getExtensions()) {
if (!provider.isSupportedFile(element.getContainingFile())) continue;
try {
provider.show(element, editor, point, keyTriggered);
}
catch (Exception e) {
LOG.error(e);
}
}
}
myElements = elements;
}
}
}