/* * 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.xdebugger.impl.evaluate.quick.common; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.hint.HintManagerImpl; import com.intellij.codeInsight.hint.HintUtil; import com.intellij.codeInsight.navigation.NavigationUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.markup.HighlighterLayer; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.ui.ClickListener; import com.intellij.ui.HintListener; import com.intellij.ui.LightweightHint; import com.intellij.ui.SimpleColoredText; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.IconUtil; import org.intellij.lang.annotations.JdkConstants; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.EventObject; /** * @author nik */ public abstract class AbstractValueHint { private static final Logger LOG = Logger.getInstance(AbstractValueHint.class); private final KeyListener myEditorKeyListener = new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if (!isAltMask(e.getModifiers())) { ValueLookupManager.getInstance(myProject).hideHint(); } } }; private RangeHighlighter myHighlighter; private Cursor myStoredCursor; private final Project myProject; private final Editor myEditor; private final ValueHintType myType; private final Point myPoint; private LightweightHint myCurrentHint; private boolean myHintHidden; private TextRange myCurrentRange; private Runnable myHideRunnable; public AbstractValueHint(@NotNull Project project, @NotNull Editor editor, @NotNull Point point, @NotNull ValueHintType type, final TextRange textRange) { myPoint = point; myProject = project; myEditor = editor; myType = type; myCurrentRange = textRange; } protected abstract boolean canShowHint(); protected abstract void evaluateAndShowHint(); public boolean isKeepHint(Editor editor, Point point) { if (myCurrentHint != null && myCurrentHint.canControlAutoHide()) { return true; } if (myType == ValueHintType.MOUSE_ALT_OVER_HINT) { return false; } else if (myType == ValueHintType.MOUSE_CLICK_HINT) { if (myCurrentHint != null && myCurrentHint.isVisible()) { return true; } } else { int offset = calculateOffset(editor, point); if (myCurrentRange != null && myCurrentRange.getStartOffset() <= offset && offset <= myCurrentRange.getEndOffset()) { return true; } } return false; } public static int calculateOffset(@NotNull Editor editor, @NotNull Point point) { return editor.logicalPositionToOffset(editor.xyToLogicalPosition(point)); } public void hideHint() { myHintHidden = true; myCurrentRange = null; if (myStoredCursor != null) { Component internalComponent = myEditor.getContentComponent(); internalComponent.setCursor(myStoredCursor); if (LOG.isDebugEnabled()) { LOG.debug("internalComponent.setCursor(myStoredCursor)"); } internalComponent.removeKeyListener(myEditorKeyListener); } if (myCurrentHint != null) { myCurrentHint.hide(); myCurrentHint = null; } if (myHighlighter != null) { myHighlighter.dispose(); myHighlighter = null; } } public void invokeHint(Runnable hideRunnable) { myHideRunnable = hideRunnable; if (!canShowHint()) { hideHint(); return; } if (myType == ValueHintType.MOUSE_ALT_OVER_HINT) { EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); TextAttributes attributes = scheme.getAttributes(EditorColors.REFERENCE_HYPERLINK_COLOR); attributes = NavigationUtil.patchAttributesColor(attributes, myCurrentRange, myEditor); myHighlighter = myEditor.getMarkupModel().addRangeHighlighter(myCurrentRange.getStartOffset(), myCurrentRange.getEndOffset(), HighlighterLayer.SELECTION + 1, attributes, HighlighterTargetArea.EXACT_RANGE); Component internalComponent = myEditor.getContentComponent(); myStoredCursor = internalComponent.getCursor(); internalComponent.addKeyListener(myEditorKeyListener); internalComponent.setCursor(hintCursor()); if (LOG.isDebugEnabled()) { LOG.debug("internalComponent.setCursor(hintCursor())"); } } else { evaluateAndShowHint(); } } private static Cursor hintCursor() { return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } public Project getProject() { return myProject; } protected Editor getEditor() { return myEditor; } protected ValueHintType getType() { return myType; } protected boolean showHint(final JComponent component) { if (myCurrentHint != null) { myCurrentHint.hide(); } myCurrentHint = new LightweightHint(component); myCurrentHint.addHintListener(new HintListener() { @Override public void hintHidden(EventObject event) { if (myHideRunnable != null) { myHideRunnable.run(); } } }); // editor may be disposed before later invokator process this action if (myEditor.isDisposed() || myEditor.getComponent().getRootPane() == null) { return false; } Point p = HintManagerImpl.getHintPosition(myCurrentHint, myEditor, myEditor.xyToLogicalPosition(myPoint), HintManager.UNDER); HintManagerImpl.getInstanceImpl().showEditorHint(myCurrentHint, myEditor, p, HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING, 0, false, HintManagerImpl.createHintHint(myEditor, p, myCurrentHint, HintManager.UNDER, true)); return true; } protected boolean isHintHidden() { return myHintHidden; } protected JComponent createExpandableHintComponent(final SimpleColoredText text, final Runnable expand) { final JComponent component = HintUtil.createInformationLabel(text, IconUtil.getAddIcon()); addClickListenerToHierarchy(component, new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent event, int clickCount) { if (myCurrentHint != null) { myCurrentHint.hide(); } expand.run(); return true; } }); return component; } private static void addClickListenerToHierarchy(Component c, ClickListener l) { l.installOn(c); if (c instanceof Container) { Component[] children = ((Container)c).getComponents(); for (Component child : children) { addClickListenerToHierarchy(child, l); } } } protected TextRange getCurrentRange() { return myCurrentRange; } private static boolean isAltMask(@JdkConstants.InputEventMask int modifiers) { return modifiers == InputEvent.ALT_MASK; } public static ValueHintType getType(final EditorMouseEvent e) { return isAltMask(e.getMouseEvent().getModifiers()) ? ValueHintType.MOUSE_ALT_OVER_HINT : ValueHintType.MOUSE_OVER_HINT; } public boolean isInsideHint(Editor editor, Point point) { return myCurrentHint != null && myCurrentHint.isInsideHint(new RelativePoint(editor.getContentComponent(), point)); } protected void showTreePopup(@NotNull DebuggerTreeCreator creator, @NotNull D descriptor) { DebuggerTreeWithHistoryPopup.showTreePopup(creator, descriptor, getEditor(), myPoint, getProject()); } }