d779a019cfa8a9e5b5719671068743058165587a
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / evaluate / quick / common / AbstractValueHint.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.xdebugger.impl.evaluate.quick.common;
17
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.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.editor.colors.EditorColors;
25 import com.intellij.openapi.editor.colors.EditorColorsManager;
26 import com.intellij.openapi.editor.colors.EditorColorsScheme;
27 import com.intellij.openapi.editor.event.EditorMouseEvent;
28 import com.intellij.openapi.editor.markup.HighlighterLayer;
29 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
30 import com.intellij.openapi.editor.markup.RangeHighlighter;
31 import com.intellij.openapi.editor.markup.TextAttributes;
32 import com.intellij.openapi.keymap.KeymapManager;
33 import com.intellij.openapi.keymap.KeymapUtil;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.TextRange;
36 import com.intellij.ui.ClickListener;
37 import com.intellij.ui.HintListener;
38 import com.intellij.ui.LightweightHint;
39 import com.intellij.ui.SimpleColoredText;
40 import com.intellij.ui.awt.RelativePoint;
41 import com.intellij.util.IconUtil;
42 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
43 import org.intellij.lang.annotations.JdkConstants;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import javax.swing.*;
48 import java.awt.*;
49 import java.awt.event.KeyAdapter;
50 import java.awt.event.KeyEvent;
51 import java.awt.event.KeyListener;
52 import java.awt.event.MouseEvent;
53 import java.util.EventObject;
54
55 /**
56  * @author nik
57  */
58 public abstract class AbstractValueHint {
59   private static final Logger LOG = Logger.getInstance(AbstractValueHint.class);
60
61   private final KeyListener myEditorKeyListener = new KeyAdapter() {
62     @Override
63     public void keyReleased(KeyEvent e) {
64       if (!isAltMask(e.getModifiers())) {
65         ValueLookupManager.getInstance(myProject).hideHint();
66       }
67     }
68   };
69
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;
80
81   public AbstractValueHint(@NotNull Project project, @NotNull Editor editor, @NotNull Point point, @NotNull ValueHintType type,
82                            final TextRange textRange) {
83     myPoint = point;
84     myProject = project;
85     myEditor = editor;
86     myType = type;
87     myCurrentRange = textRange;
88   }
89
90   protected abstract boolean canShowHint();
91
92   protected abstract void evaluateAndShowHint();
93
94   public boolean isKeepHint(Editor editor, Point point) {
95     if (myCurrentHint != null && myCurrentHint.canControlAutoHide()) {
96       return true;
97     }
98
99     if (myType == ValueHintType.MOUSE_ALT_OVER_HINT) {
100       return false;
101     }
102     else if (myType == ValueHintType.MOUSE_CLICK_HINT) {
103       if (myCurrentHint != null && myCurrentHint.isVisible()) {
104         return true;
105       }
106     }
107     else {
108       int offset = calculateOffset(editor, point);
109       if (myCurrentRange != null && myCurrentRange.getStartOffset() <= offset && offset <= myCurrentRange.getEndOffset()) {
110         return true;
111       }
112     }
113     return false;
114   }
115
116   public static int calculateOffset(@NotNull Editor editor, @NotNull Point point) {
117     return editor.logicalPositionToOffset(editor.xyToLogicalPosition(point));
118   }
119
120   public void hideHint() {
121     myHintHidden = true;
122     myCurrentRange = null;
123     if (myStoredCursor != null) {
124       Component internalComponent = myEditor.getContentComponent();
125       internalComponent.setCursor(myStoredCursor);
126       if (LOG.isDebugEnabled()) {
127         LOG.debug("internalComponent.setCursor(myStoredCursor)");
128       }
129       internalComponent.removeKeyListener(myEditorKeyListener);
130     }
131
132     if (myCurrentHint != null) {
133       myCurrentHint.hide();
134       myCurrentHint = null;
135     }
136     if (myHighlighter != null) {
137       myHighlighter.dispose();
138       myHighlighter = null;
139     }
140   }
141
142   public void invokeHint() {
143     invokeHint(null);
144   }
145
146   public void invokeHint(Runnable hideRunnable) {
147     myHideRunnable = hideRunnable;
148
149     if (!canShowHint()) {
150       hideHint();
151       return;
152     }
153
154     if (myType == ValueHintType.MOUSE_ALT_OVER_HINT) {
155       EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
156       TextAttributes attributes = scheme.getAttributes(EditorColors.REFERENCE_HYPERLINK_COLOR);
157       attributes = NavigationUtil.patchAttributesColor(attributes, myCurrentRange, myEditor);
158
159       myHighlighter = myEditor.getMarkupModel().addRangeHighlighter(myCurrentRange.getStartOffset(), myCurrentRange.getEndOffset(),
160                                                                     HighlighterLayer.SELECTION + 1, attributes,
161                                                                     HighlighterTargetArea.EXACT_RANGE);
162       Component internalComponent = myEditor.getContentComponent();
163       myStoredCursor = internalComponent.getCursor();
164       internalComponent.addKeyListener(myEditorKeyListener);
165       internalComponent.setCursor(hintCursor());
166       if (LOG.isDebugEnabled()) {
167         LOG.debug("internalComponent.setCursor(hintCursor())");
168       }
169     }
170     else {
171       evaluateAndShowHint();
172     }
173   }
174
175   private static Cursor hintCursor() {
176     return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
177   }
178
179   public Project getProject() {
180     return myProject;
181   }
182
183   @NotNull
184   protected Editor getEditor() {
185     return myEditor;
186   }
187
188   protected ValueHintType getType() {
189     return myType;
190   }
191
192   protected boolean showHint(final JComponent component) {
193     if (myCurrentHint != null) {
194       myCurrentHint.hide();
195     }
196     myCurrentHint = new LightweightHint(component);
197     myCurrentHint.addHintListener(new HintListener() {
198       @Override
199       public void hintHidden(EventObject event) {
200         if (myHideRunnable != null) {
201           myHideRunnable.run();
202         }
203         onHintHidden();
204       }
205     });
206
207     // editor may be disposed before later invokator process this action
208     if (myEditor.isDisposed() || myEditor.getComponent().getRootPane() == null) {
209       return false;
210     }
211
212     Point p = HintManagerImpl.getHintPosition(myCurrentHint, myEditor, myEditor.xyToLogicalPosition(myPoint), HintManager.UNDER);
213     HintManagerImpl.getInstanceImpl().showEditorHint(myCurrentHint, myEditor, p,
214                                                      HintManager.HIDE_BY_ANY_KEY |
215                                                      HintManager.HIDE_BY_TEXT_CHANGE |
216                                                      HintManager.HIDE_BY_SCROLLING, 0, false,
217                                                      HintManagerImpl.createHintHint(myEditor, p, myCurrentHint, HintManager.UNDER, true));
218     return true;
219   }
220
221   protected void onHintHidden() {
222
223   }
224
225   protected boolean isHintHidden() {
226     return myHintHidden;
227   }
228
229   protected JComponent createExpandableHintComponent(final SimpleColoredText text, final Runnable expand) {
230     final JComponent component = HintUtil.createInformationLabel(text, IconUtil.getAddIcon());
231     addClickListenerToHierarchy(component, new ClickListener() {
232       @Override
233       public boolean onClick(@NotNull MouseEvent event, int clickCount) {
234         if (myCurrentHint != null) {
235           myCurrentHint.hide();
236         }
237         expand.run();
238         return true;
239       }
240     });
241     return component;
242   }
243
244   private static void addClickListenerToHierarchy(Component c, ClickListener l) {
245     l.installOn(c);
246     if (c instanceof Container) {
247       Component[] children = ((Container)c).getComponents();
248       for (Component child : children) {
249         addClickListenerToHierarchy(child, l);
250       }
251     }
252   }
253
254   @Nullable
255   protected TextRange getCurrentRange() {
256     return myCurrentRange;
257   }
258
259   private static boolean isAltMask(@JdkConstants.InputEventMask int modifiers) {
260     return KeymapUtil.matchActionMouseShortcutsModifiers(KeymapManager.getInstance().getActiveKeymap(),
261                                                          modifiers,
262                                                          XDebuggerActions.QUICK_EVALUATE_EXPRESSION);
263   }
264
265   @Nullable
266   public static ValueHintType getHintType(final EditorMouseEvent e) {
267     int modifiers = e.getMouseEvent().getModifiers();
268     if (modifiers == 0) {
269       return ValueHintType.MOUSE_OVER_HINT;
270     }
271     else if (isAltMask(modifiers)) {
272       return ValueHintType.MOUSE_ALT_OVER_HINT;
273     }
274     return null;
275   }
276
277   public boolean isInsideHint(Editor editor, Point point) {
278     return myCurrentHint != null && myCurrentHint.isInsideHint(new RelativePoint(editor.getContentComponent(), point));
279   }
280
281   protected <D> void showTreePopup(@NotNull DebuggerTreeCreator<D> creator, @NotNull D descriptor) {
282     DebuggerTreeWithHistoryPopup.showTreePopup(creator, descriptor, getEditor(), myPoint, getProject());
283   }
284 }