| /* |
| * 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; |
| } |
| } |
| } |