2 * Copyright 2000-2015 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.
16 package com.intellij.xdebugger.impl.evaluate.quick.common;
18 import com.intellij.codeInsight.hint.HintManager;
19 import com.intellij.codeInsight.hint.HintManagerImpl;
20 import com.intellij.codeInsight.hint.HintUtil;
21 import com.intellij.codeInsight.navigation.NavigationUtil;
22 import com.intellij.ide.TooltipEvent;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.editor.colors.EditorColors;
26 import com.intellij.openapi.editor.colors.EditorColorsManager;
27 import com.intellij.openapi.editor.colors.EditorColorsScheme;
28 import com.intellij.openapi.editor.event.EditorMouseEvent;
29 import com.intellij.openapi.editor.impl.EditorComponentImpl;
30 import com.intellij.openapi.editor.impl.EditorImpl;
31 import com.intellij.openapi.editor.markup.HighlighterLayer;
32 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
33 import com.intellij.openapi.editor.markup.RangeHighlighter;
34 import com.intellij.openapi.editor.markup.TextAttributes;
35 import com.intellij.openapi.keymap.KeymapManager;
36 import com.intellij.openapi.keymap.KeymapUtil;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.util.TextRange;
39 import com.intellij.ui.ClickListener;
40 import com.intellij.ui.HintListener;
41 import com.intellij.ui.LightweightHint;
42 import com.intellij.ui.SimpleColoredText;
43 import com.intellij.ui.awt.RelativePoint;
44 import com.intellij.util.IconUtil;
45 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
46 import org.intellij.lang.annotations.JdkConstants;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
52 import java.awt.event.*;
53 import java.util.EventObject;
58 public abstract class AbstractValueHint {
59 private static final Logger LOG = Logger.getInstance(AbstractValueHint.class);
61 private final KeyListener myEditorKeyListener = new KeyAdapter() {
63 public void keyReleased(KeyEvent e) {
64 if (!isAltMask(e.getModifiers())) {
65 ValueLookupManager.getInstance(myProject).hideHint();
70 private RangeHighlighter myHighlighter;
71 private Cursor myStoredCursor;
72 private final Project myProject;
73 private final Editor myEditor;
74 private final ValueHintType myType;
75 protected final Point myPoint;
76 private LightweightHint myCurrentHint;
77 private boolean myHintHidden;
78 private TextRange myCurrentRange;
79 private Runnable myHideRunnable;
81 public AbstractValueHint(@NotNull Project project, @NotNull Editor editor, @NotNull Point point, @NotNull ValueHintType type,
82 final TextRange textRange) {
87 myCurrentRange = textRange;
90 protected abstract boolean canShowHint();
92 protected abstract void evaluateAndShowHint();
94 public boolean isKeepHint(Editor editor, Point point) {
95 if (myCurrentHint != null && myCurrentHint.canControlAutoHide()) {
99 if (myType == ValueHintType.MOUSE_ALT_OVER_HINT) {
102 else if (myType == ValueHintType.MOUSE_CLICK_HINT) {
103 if (myCurrentHint != null && myCurrentHint.isVisible()) {
108 if (isInsideCurrentRange(editor, point)) {
115 boolean isInsideCurrentRange(Editor editor, Point point) {
116 return myCurrentRange != null && myCurrentRange.contains(calculateOffset(editor, point));
119 public static int calculateOffset(@NotNull Editor editor, @NotNull Point point) {
120 return editor.logicalPositionToOffset(editor.xyToLogicalPosition(point));
123 public void hideHint() {
125 myCurrentRange = null;
126 if (myStoredCursor != null) {
127 Component internalComponent = myEditor.getContentComponent();
128 internalComponent.setCursor(myStoredCursor);
129 if (LOG.isDebugEnabled()) {
130 LOG.debug("internalComponent.setCursor(myStoredCursor)");
132 internalComponent.removeKeyListener(myEditorKeyListener);
135 if (myCurrentHint != null) {
136 myCurrentHint.hide();
137 myCurrentHint = null;
139 if (myHighlighter != null) {
140 myHighlighter.dispose();
141 myHighlighter = null;
145 public void invokeHint() {
149 public void invokeHint(Runnable hideRunnable) {
150 myHideRunnable = hideRunnable;
152 if (!canShowHint()) {
157 if (myType == ValueHintType.MOUSE_ALT_OVER_HINT) {
158 EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
159 TextAttributes attributes = scheme.getAttributes(EditorColors.REFERENCE_HYPERLINK_COLOR);
160 attributes = NavigationUtil.patchAttributesColor(attributes, myCurrentRange, myEditor);
162 myHighlighter = myEditor.getMarkupModel().addRangeHighlighter(myCurrentRange.getStartOffset(), myCurrentRange.getEndOffset(),
163 HighlighterLayer.SELECTION + 1, attributes,
164 HighlighterTargetArea.EXACT_RANGE);
165 Component internalComponent = myEditor.getContentComponent();
166 myStoredCursor = internalComponent.getCursor();
167 internalComponent.addKeyListener(myEditorKeyListener);
168 internalComponent.setCursor(hintCursor());
169 if (LOG.isDebugEnabled()) {
170 LOG.debug("internalComponent.setCursor(hintCursor())");
174 evaluateAndShowHint();
178 private static Cursor hintCursor() {
179 return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
182 public Project getProject() {
187 protected Editor getEditor() {
191 protected ValueHintType getType() {
195 protected boolean showHint(final JComponent component) {
196 if (myCurrentHint != null) {
197 myCurrentHint.hide();
199 myCurrentHint = new LightweightHint(component) {
201 protected boolean canAutoHideOn(TooltipEvent event) {
202 InputEvent inputEvent = event.getInputEvent();
203 if (inputEvent instanceof MouseEvent) {
204 Component comp = inputEvent.getComponent();
205 if (comp instanceof EditorComponentImpl) {
206 EditorImpl editor = ((EditorComponentImpl)comp).getEditor();
207 return !isInsideCurrentRange(editor, ((MouseEvent)inputEvent).getPoint());
213 myCurrentHint.addHintListener(new HintListener() {
215 public void hintHidden(EventObject event) {
216 if (myHideRunnable != null) {
217 myHideRunnable.run();
223 // editor may be disposed before later invokator process this action
224 if (myEditor.isDisposed() || myEditor.getComponent().getRootPane() == null) {
228 Point p = HintManagerImpl.getHintPosition(myCurrentHint, myEditor, myEditor.xyToLogicalPosition(myPoint), HintManager.UNDER);
229 HintManagerImpl.getInstanceImpl().showEditorHint(myCurrentHint, myEditor, p,
230 HintManager.HIDE_BY_ANY_KEY |
231 HintManager.HIDE_BY_TEXT_CHANGE |
232 HintManager.HIDE_BY_SCROLLING, 0, false,
233 HintManagerImpl.createHintHint(myEditor, p, myCurrentHint, HintManager.UNDER, true));
237 protected void onHintHidden() {
241 protected boolean isHintHidden() {
245 protected JComponent createExpandableHintComponent(final SimpleColoredText text, final Runnable expand) {
246 final JComponent component = HintUtil.createInformationLabel(text, IconUtil.getAddIcon());
247 addClickListenerToHierarchy(component, new ClickListener() {
249 public boolean onClick(@NotNull MouseEvent event, int clickCount) {
250 if (myCurrentHint != null) {
251 myCurrentHint.hide();
260 private static void addClickListenerToHierarchy(Component c, ClickListener l) {
262 if (c instanceof Container) {
263 Component[] children = ((Container)c).getComponents();
264 for (Component child : children) {
265 addClickListenerToHierarchy(child, l);
271 protected TextRange getCurrentRange() {
272 return myCurrentRange;
275 private static boolean isAltMask(@JdkConstants.InputEventMask int modifiers) {
276 return KeymapUtil.matchActionMouseShortcutsModifiers(KeymapManager.getInstance().getActiveKeymap(),
278 XDebuggerActions.QUICK_EVALUATE_EXPRESSION);
282 public static ValueHintType getHintType(final EditorMouseEvent e) {
283 int modifiers = e.getMouseEvent().getModifiers();
284 if (modifiers == 0) {
285 return ValueHintType.MOUSE_OVER_HINT;
287 else if (isAltMask(modifiers)) {
288 return ValueHintType.MOUSE_ALT_OVER_HINT;
293 public boolean isInsideHint(Editor editor, Point point) {
294 return myCurrentHint != null && myCurrentHint.isInsideHint(new RelativePoint(editor.getContentComponent(), point));
297 protected <D> void showTreePopup(@NotNull DebuggerTreeCreator<D> creator, @NotNull D descriptor) {
298 DebuggerTreeWithHistoryPopup.showTreePopup(creator, descriptor, getEditor(), myPoint, getProject());