import popup fixed
[idea/community.git] / platform / platform-impl / src / com / intellij / codeInsight / hint / HintManagerImpl.java
1 /*
2  * Copyright 2000-2009 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.codeInsight.hint;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
21 import com.intellij.openapi.actionSystem.ex.AnActionListener;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.components.ServiceManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.editor.LogicalPosition;
27 import com.intellij.openapi.editor.event.*;
28 import com.intellij.openapi.editor.ex.EditorEx;
29 import com.intellij.openapi.editor.markup.*;
30 import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
31 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
32 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.project.ProjectManager;
35 import com.intellij.openapi.project.ProjectManagerAdapter;
36 import com.intellij.openapi.ui.popup.JBPopup;
37 import com.intellij.openapi.ui.popup.JBPopupFactory;
38 import com.intellij.ui.HintHint;
39 import com.intellij.ui.HintListener;
40 import com.intellij.ui.LightweightHint;
41 import com.intellij.ui.ListenerUtil;
42 import com.intellij.ui.awt.RelativePoint;
43 import com.intellij.util.Alarm;
44 import org.jetbrains.annotations.NotNull;
45
46 import javax.swing.*;
47 import java.awt.*;
48 import java.awt.event.*;
49 import java.util.ArrayList;
50 import java.util.EventObject;
51 import java.util.List;
52
53 public class HintManagerImpl extends HintManager implements Disposable {
54   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.hint.HintManager");
55
56   private final AnActionListener myAnActionListener;
57   private final MyEditorManagerListener myEditorManagerListener;
58   private final EditorMouseAdapter myEditorMouseListener;
59   private final FocusListener myEditorFocusListener;
60   private final DocumentListener myEditorDocumentListener;
61   private final VisibleAreaListener myVisibleAreaListener;
62   private final CaretListener myCaretMoveListener;
63
64   private LightweightHint myQuestionHint = null;
65   private QuestionAction myQuestionAction = null;
66
67   private final List<HintInfo> myHintsStack = new ArrayList<HintInfo>();
68   private Editor myLastEditor = null;
69   private final Alarm myHideAlarm = new Alarm();
70
71   private static int getPriority(QuestionAction action) {
72     return action instanceof PriorityQuestionAction ? ((PriorityQuestionAction)action).getPriority() : 0;
73   }
74
75   public boolean canShowQuestionAction(QuestionAction action) {
76     ApplicationManager.getApplication().assertIsDispatchThread();
77     return myQuestionAction == null || getPriority(myQuestionAction) <= getPriority(action);
78   }
79
80   public interface ActionToIgnore {
81   }
82
83   private static class HintInfo {
84     final LightweightHint hint;
85     final int flags;
86     private final boolean reviveOnEditorChange;
87
88     private HintInfo(LightweightHint hint, int flags, boolean reviveOnEditorChange) {
89       this.hint = hint;
90       this.flags = flags;
91       this.reviveOnEditorChange = reviveOnEditorChange;
92     }
93   }
94
95   public static HintManagerImpl getInstanceImpl() {
96     return (HintManagerImpl)ServiceManager.getService(HintManager.class);
97   }
98
99   public HintManagerImpl(ActionManagerEx actionManagerEx, ProjectManager projectManager) {
100     myEditorManagerListener = new MyEditorManagerListener();
101
102     myAnActionListener = new MyAnActionListener();
103     actionManagerEx.addAnActionListener(myAnActionListener);
104
105     myCaretMoveListener = new CaretListener() {
106       public void caretPositionChanged(CaretEvent e) {
107         hideHints(HIDE_BY_ANY_KEY, false, false);
108       }
109     };
110
111     projectManager.addProjectManagerListener(new MyProjectManagerListener());
112
113     myEditorMouseListener = new EditorMouseAdapter() {
114       public void mousePressed(EditorMouseEvent event) {
115         hideAllHints();
116       }
117     };
118
119     myVisibleAreaListener = new VisibleAreaListener() {
120       public void visibleAreaChanged(VisibleAreaEvent e) {
121         updateScrollableHints(e);
122         hideHints(HIDE_BY_SCROLLING, false, false);
123       }
124     };
125
126     myEditorFocusListener = new FocusAdapter() {
127       public void focusLost(final FocusEvent e) {
128         myHideAlarm.addRequest(
129           new Runnable() {
130             public void run() {
131               if (!JBPopupFactory.getInstance().isChildPopupFocused(e.getComponent())) {
132                 hideAllHints();
133               }
134             }
135           },
136           200
137         );
138       }
139
140       public void focusGained(FocusEvent e) {
141         myHideAlarm.cancelAllRequests();
142       }
143     };
144
145     myEditorDocumentListener = new DocumentAdapter() {
146       public void documentChanged(DocumentEvent event) {
147         LOG.assertTrue(SwingUtilities.isEventDispatchThread());
148         HintInfo[] infos = myHintsStack.toArray(new HintInfo[myHintsStack.size()]);
149         for (HintInfo info : infos) {
150           if ((info.flags & HIDE_BY_TEXT_CHANGE) != 0) {
151             if (info.hint.isVisible()) {
152               info.hint.hide();
153             }
154             myHintsStack.remove(info);
155           }
156         }
157
158         if (myHintsStack.isEmpty()) {
159           updateLastEditor(null);
160         }
161       }
162     };
163   }
164
165   public boolean performCurrentQuestionAction() {
166     if (myQuestionAction != null && myQuestionHint != null) {
167       if (myQuestionHint.isVisible()) {
168         if (LOG.isDebugEnabled()) {
169           LOG.debug("performing an action:" + myQuestionAction);
170         }
171         if (myQuestionAction.execute()) {
172           if (myQuestionHint != null) {
173             myQuestionHint.hide();
174           }
175         }
176         return true;
177       }
178
179       myQuestionAction = null;
180       myQuestionHint = null;
181     }
182
183     return false;
184   }
185
186
187   private void updateScrollableHints(VisibleAreaEvent e) {
188     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
189     for (HintInfo info : myHintsStack) {
190       if (info.hint instanceof LightweightHint && (info.flags & UPDATE_BY_SCROLLING) != 0) {
191         updateScrollableHintPosition(e, info.hint, (info.flags & HIDE_IF_OUT_OF_EDITOR) != 0);
192       }
193     }
194   }
195
196   public boolean hasShownHintsThatWillHideByOtherHint() {
197     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
198     for (HintInfo hintInfo : myHintsStack) {
199       if (hintInfo.hint.isVisible() && (hintInfo.flags & HIDE_BY_OTHER_HINT) != 0) return true;
200     }
201     return false;
202   }
203
204   public void dispose() {
205     ActionManagerEx.getInstanceEx().removeAnActionListener(myAnActionListener);
206   }
207
208   private static void updateScrollableHintPosition(VisibleAreaEvent e, LightweightHint hint, boolean hideIfOutOfEditor) {
209     if (hint.getComponent() instanceof ScrollAwareHint) {
210       ((ScrollAwareHint)hint.getComponent()).editorScrolled();
211     }
212
213     Editor editor = e.getEditor();
214     if (!editor.getComponent().isShowing() || editor.isOneLineMode()) return;
215     Rectangle newRectangle = e.getOldRectangle();
216     Rectangle oldRectangle = e.getNewRectangle();
217     Rectangle bounds = hint.getBounds();
218     Point location = bounds.getLocation();
219
220     location = SwingUtilities.convertPoint(
221       editor.getComponent().getRootPane().getLayeredPane(),
222       location,
223       editor.getContentComponent()
224     );
225
226
227     int xOffset = location.x - oldRectangle.x;
228     int yOffset = location.y - oldRectangle.y;
229     location = new Point(newRectangle.x + xOffset, newRectangle.y + yOffset);
230
231     Rectangle newBounds = new Rectangle(location.x, location.y, bounds.width, bounds.height);
232
233     final boolean valid = hideIfOutOfEditor ? oldRectangle.contains(newBounds) : oldRectangle.intersects(newBounds);
234     if (valid) {
235       location = SwingUtilities.convertPoint(
236         editor.getContentComponent(),
237         location,
238         editor.getComponent().getRootPane().getLayeredPane()
239       );
240
241       hint.updateBounds(location.x, location.y);
242     }
243     else {
244       hint.hide();
245     }
246   }
247
248   public void showEditorHint(LightweightHint hint, Editor editor, short constraint, int flags, int timeout, boolean reviveOnEditorChange) {
249     LogicalPosition pos = editor.getCaretModel().getLogicalPosition();
250     Point p = getHintPosition(hint, editor, pos, constraint);
251     showEditorHint(hint, editor, p, flags, timeout, reviveOnEditorChange, new HintHint(editor, p));
252   }
253
254   /**
255    * @param p point in layered pane coordinate system.
256    * @param reviveOnEditorChange
257    */
258   public void showEditorHint(@NotNull final LightweightHint hint,
259                              @NotNull Editor editor,
260                              @NotNull Point p,
261                              int flags,
262                              int timeout, boolean reviveOnEditorChange) {
263
264     showEditorHint(hint, editor, p, flags, timeout, reviveOnEditorChange, new HintHint(editor, p));
265   }
266
267   public void showEditorHint(@NotNull final LightweightHint hint,
268                              @NotNull Editor editor,
269                              @NotNull Point p,
270                              int flags,
271                              int timeout,
272                              boolean reviveOnEditorChange, HintHint hintInfo) {
273     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
274     myHideAlarm.cancelAllRequests();
275
276     hideHints(HIDE_BY_OTHER_HINT, false, false);
277
278     if (editor != myLastEditor) {
279       hideAllHints();
280     }
281
282     if (!ApplicationManager.getApplication().isUnitTestMode() && !editor.getContentComponent().isShowing()) return;
283
284     updateLastEditor(editor);
285
286     getPublisher().hintShown(editor.getProject(), hint, flags);
287
288     Component component = hint.getComponent();
289
290     doShowInGivenLocation(hint, editor, p, hintInfo);
291
292     ListenerUtil.addMouseListener(
293       component,
294       new MouseAdapter() {
295         public void mousePressed(MouseEvent e) {
296           myHideAlarm.cancelAllRequests();
297         }
298       }
299     );
300     ListenerUtil.addFocusListener(
301       component,
302       new FocusAdapter() {
303         public void focusGained(FocusEvent e) {
304           myHideAlarm.cancelAllRequests();
305         }
306       }
307     );
308
309     final HintInfo info = new HintInfo(hint, flags, reviveOnEditorChange);
310     myHintsStack.add(info);
311     if (timeout > 0) {
312       Timer timer = new Timer(
313         timeout,
314         new ActionListener() {
315           public void actionPerformed(ActionEvent event) {
316             hint.hide();
317           }
318         }
319       );
320       timer.setRepeats(false);
321       timer.start();
322     }
323   }
324
325   public void showHint(@NotNull final JComponent component, @NotNull RelativePoint p, int flags, int timeout) {
326     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
327     myHideAlarm.cancelAllRequests();
328
329     hideHints(HIDE_BY_OTHER_HINT, false, false);
330
331     final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(component, null)
332         .setRequestFocus(false)
333         .setResizable(false)
334         .setMovable(false)
335         .createPopup();
336     popup.show(p);
337
338     ListenerUtil.addMouseListener(
339       component,
340       new MouseAdapter() {
341         public void mousePressed(MouseEvent e) {
342           myHideAlarm.cancelAllRequests();
343         }
344       }
345     );
346     ListenerUtil.addFocusListener(
347       component,
348       new FocusAdapter() {
349         public void focusGained(FocusEvent e) {
350           myHideAlarm.cancelAllRequests();
351         }
352       }
353     );
354
355     final HintInfo info = new HintInfo(new LightweightHint(component){
356       public void hide() {
357         popup.cancel();
358       }
359     }, flags, false);
360     myHintsStack.add(info);
361     if (timeout > 0) {
362       Timer timer = new Timer(
363         timeout,
364         new ActionListener() {
365           public void actionPerformed(ActionEvent event) {
366             popup.dispose();
367           }
368         }
369       );
370       timer.setRepeats(false);
371       timer.start();
372     }
373   }
374
375   private static void doShowInGivenLocation(final LightweightHint hint, final Editor editor, final Point p, HintHint hintInfo) {
376     if (ApplicationManager.getApplication().isUnitTestMode()) return;
377     JLayeredPane layeredPane = editor.getComponent().getRootPane().getLayeredPane();
378     Dimension size = hint.getComponent().getPreferredSize();
379     if(layeredPane.getWidth() < p.x + size.width) {
380       p.x = Math.max(0, layeredPane.getWidth() - size.width);
381     }
382     if (hint.isVisible()) {
383       hint.updateBounds(p.x, p.y);
384     }
385     else {
386       hint.show(layeredPane, p.x, p.y, editor.getContentComponent(), hintInfo);
387     }
388   }
389
390   public static void adjustEditorHintPosition(final LightweightHint hint, final Editor editor, final Point p) {
391     doShowInGivenLocation(hint, editor, p, new HintHint(editor, p));
392   }
393
394   public void hideAllHints() {
395     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
396     List<HintInfo> hints = new ArrayList<HintInfo>(myHintsStack);
397     for (HintInfo info : hints) {
398         info.hint.hide();
399     }
400     myHintsStack.clear();
401     updateLastEditor(null);
402   }
403
404   /**
405    * @return coordinates in layered pane coordinate system.
406    */
407   public Point getHintPosition(LightweightHint hint, Editor editor, short constraint) {
408     LogicalPosition pos = editor.getCaretModel().getLogicalPosition();
409     final DataContext dataContext = ((EditorEx)editor).getDataContext();
410     final Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
411
412     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
413     if (dominantArea == null) {
414       for (HintInfo info : myHintsStack) {
415         if (!info.hint.isSelectingHint()) continue;
416         final Rectangle rectangle = info.hint.getBounds();
417
418         if (rectangle != null) {
419           return getHintPositionRelativeTo(hint, editor, constraint, rectangle, pos);
420         }
421       }
422     }
423     else {
424       return getHintPositionRelativeTo(hint, editor, constraint, dominantArea, pos);
425     }
426
427     return getHintPosition(hint, editor, pos, constraint);
428   }
429
430   private static Point getHintPositionRelativeTo(final LightweightHint hint,
431                                                  final Editor editor,
432                                                  final short constraint,
433                                                  final Rectangle lookupBounds,
434                                                  final LogicalPosition pos) {
435     Dimension hintSize = hint.getComponent().getPreferredSize();
436     JComponent editorComponent = editor.getComponent();
437     JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
438     int layeredPaneHeight = layeredPane.getHeight();
439
440     switch (constraint) {
441       case LEFT:
442         {
443           int y = lookupBounds.y;
444           if (y < 0) {
445             y = 0;
446           }
447           else if (y + hintSize.height >= layeredPaneHeight) {
448             y = layeredPaneHeight - hintSize.height;
449           }
450           return new Point(lookupBounds.x - hintSize.width, y);
451         }
452
453       case RIGHT:
454         {
455           int y = lookupBounds.y;
456           if (y < 0) {
457             y = 0;
458           }
459           else if (y + hintSize.height >= layeredPaneHeight) {
460             y = layeredPaneHeight - hintSize.height;
461           }
462           return new Point(lookupBounds.x + lookupBounds.width, y);
463         }
464
465       case ABOVE:
466         Point posAboveCaret = getHintPosition(hint, editor, pos, ABOVE);
467         return new Point(lookupBounds.x, Math.min(posAboveCaret.y, lookupBounds.y - hintSize.height));
468
469       case UNDER:
470         Point posUnderCaret = getHintPosition(hint, editor, pos, UNDER);
471         return new Point(lookupBounds.x, Math.max(posUnderCaret.y, lookupBounds.y + lookupBounds.height));
472
473       default:
474         LOG.assertTrue(false);
475         return null;
476     }
477   }
478
479   /**
480    * @return position of hint in layered pane coordinate system
481    */
482   public static Point getHintPosition(LightweightHint hint, Editor editor, LogicalPosition pos, short constraint) {
483     return getHintPosition(hint, editor, pos, pos, constraint);
484   }
485
486   private static Point getHintPosition(LightweightHint hint, Editor editor, LogicalPosition pos1, LogicalPosition pos2, short constraint) {
487     Point p = _getHintPosition(hint, editor, pos1, pos2, constraint);
488     JLayeredPane layeredPane = editor.getComponent().getRootPane().getLayeredPane();
489     Dimension hintSize = hint.getComponent().getPreferredSize();
490     if (constraint == ABOVE) {
491       if (p.y < 0) {
492         Point p1 = _getHintPosition(hint, editor, pos1, pos2, UNDER);
493         if (p1.y + hintSize.height <= layeredPane.getSize().height) {
494           return p1;
495         }
496       }
497     }
498     else if (constraint == UNDER) {
499       if (p.y + hintSize.height > layeredPane.getSize().height) {
500         Point p1 = _getHintPosition(hint, editor, pos1, pos2, ABOVE);
501         if (p1.y >= 0) {
502           return p1;
503         }
504       }
505     }
506
507     return p;
508   }
509
510   private static Point _getHintPosition(LightweightHint hint, Editor editor, LogicalPosition pos1, LogicalPosition pos2, short constraint) {
511     Dimension hintSize = hint.getComponent().getPreferredSize();
512     int line1 = pos1.line;
513     int col1 = pos1.column;
514     int line2 = pos2.line;
515     int col2 = pos2.column;
516
517     Point location;
518     JLayeredPane layeredPane = editor.getComponent().getRootPane().getLayeredPane();
519     JComponent internalComponent = editor.getContentComponent();
520     if (constraint == RIGHT_UNDER) {
521       Point p = editor.logicalPositionToXY(new LogicalPosition(line2, col2));
522       p.y += editor.getLineHeight();
523       location = SwingUtilities.convertPoint(internalComponent, p, layeredPane);
524     }
525     else {
526       Point p = editor.logicalPositionToXY(new LogicalPosition(line1, col1));
527       if (constraint == UNDER){
528         p.y += editor.getLineHeight();
529       }
530       location = SwingUtilities.convertPoint(internalComponent, p, layeredPane);
531     }
532
533     if (constraint == ABOVE) {
534       location.y -= hintSize.height;
535       int diff = location.x + hintSize.width - layeredPane.getWidth();
536       if (diff > 0) {
537         location.x = Math.max (location.x - diff, 0);
538       }
539     }
540
541     if (constraint == LEFT || constraint == RIGHT) {
542       location.y -= hintSize.height / 2;
543       if (constraint == LEFT) {
544         location.x -= hintSize.width;
545       }
546     }
547
548     return location;
549   }
550
551   public void showErrorHint(@NotNull Editor editor, @NotNull String text) {
552     JLabel label = HintUtil.createErrorLabel(text);
553     LightweightHint hint = new LightweightHint(label);
554     Point p = getHintPosition(hint, editor, ABOVE);
555     showEditorHint(hint, editor, p, HIDE_BY_ANY_KEY | HIDE_BY_TEXT_CHANGE | HIDE_BY_SCROLLING, 0, false);
556   }
557
558   public void showInformationHint(@NotNull Editor editor, @NotNull String text) {
559     JLabel label = HintUtil.createInformationLabel(text);
560     showInformationHint(editor, label);
561   }
562
563   @Override
564   public void showInformationHint(@NotNull Editor editor, @NotNull JComponent component) {
565     LightweightHint hint = new LightweightHint(component);
566     Point p = getHintPosition(hint, editor, ABOVE);
567     showEditorHint(hint, editor, p, HIDE_BY_ANY_KEY | HIDE_BY_TEXT_CHANGE | HIDE_BY_SCROLLING, 0, false);
568   }
569
570   public void showErrorHint(@NotNull Editor editor, @NotNull String hintText, int offset1, int offset2, short constraint, int flags, int timeout) {
571     JLabel label = HintUtil.createErrorLabel(hintText);
572     LightweightHint hint = new LightweightHint(label);
573     final LogicalPosition pos1 = editor.offsetToLogicalPosition(offset1);
574     final LogicalPosition pos2 = editor.offsetToLogicalPosition(offset2);
575     final Point p = getHintPosition(hint, editor, pos1, pos2, constraint);
576     showEditorHint(hint, editor, p, flags, timeout, false);
577   }
578
579
580   public void showQuestionHint(@NotNull Editor editor, @NotNull String hintText, int offset1, int offset2, @NotNull QuestionAction action) {
581     JLabel label = HintUtil.createQuestionLabel(hintText);
582     LightweightHint hint = new LightweightHint(label);
583     showQuestionHint(editor, offset1, offset2, hint, action, ABOVE);
584   }
585
586   public void showQuestionHint(@NotNull final Editor editor,
587                                final int offset1,
588                                final int offset2,
589                                @NotNull final LightweightHint hint,
590                                @NotNull final QuestionAction action,
591                                final short constraint) {
592     final LogicalPosition pos1 = editor.offsetToLogicalPosition(offset1);
593     final LogicalPosition pos2 = editor.offsetToLogicalPosition(offset2);
594     final Point p = getHintPosition(hint, editor, pos1, pos2, constraint);
595     showQuestionHint(editor, p, offset1, offset2, hint, action);
596   }
597
598
599   public void showQuestionHint(@NotNull final Editor editor,
600                                @NotNull final Point p,
601                                final int offset1,
602                                final int offset2,
603                                @NotNull final LightweightHint hint,
604                                @NotNull final QuestionAction action) {
605     TextAttributes attributes = new TextAttributes();
606     attributes.setEffectColor(HintUtil.QUESTION_UNDERSCORE_COLOR);
607     attributes.setEffectType(EffectType.LINE_UNDERSCORE);
608     final RangeHighlighter highlighter = editor.getMarkupModel()
609       .addRangeHighlighter(offset1, offset2, HighlighterLayer.ERROR + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
610     if (myQuestionHint != null) {
611       myQuestionHint.hide();
612       myQuestionHint = null;
613       myQuestionAction = null;
614     }
615
616     hint.addHintListener(new HintListener() {
617       public void hintHidden(EventObject event) {
618         if (!editor.isDisposed()) {
619           editor.getMarkupModel().removeHighlighter(highlighter);
620         }
621
622         if (myQuestionHint == hint) {
623           myQuestionAction = null;
624           myQuestionHint = null;
625         }
626         hint.removeHintListener(this);
627       }
628     });
629
630     showEditorHint(hint, editor, p, HIDE_BY_ANY_KEY | HIDE_BY_TEXT_CHANGE | UPDATE_BY_SCROLLING | HIDE_IF_OUT_OF_EDITOR, 0, false);
631     myQuestionAction = action;
632     myQuestionHint = hint;
633   }
634
635   private void updateLastEditor(final Editor editor) {
636     if (myLastEditor != editor) {
637       if (myLastEditor != null) {
638         myLastEditor.removeEditorMouseListener(myEditorMouseListener);
639         myLastEditor.getContentComponent().removeFocusListener(myEditorFocusListener);
640         myLastEditor.getDocument().removeDocumentListener(myEditorDocumentListener);
641         myLastEditor.getScrollingModel().removeVisibleAreaListener(myVisibleAreaListener);
642         myLastEditor.getCaretModel().removeCaretListener(myCaretMoveListener);
643       }
644
645       myLastEditor = editor;
646       if (myLastEditor != null) {
647         myLastEditor.addEditorMouseListener(myEditorMouseListener);
648         myLastEditor.getContentComponent().addFocusListener(myEditorFocusListener);
649         myLastEditor.getDocument().addDocumentListener(myEditorDocumentListener);
650         myLastEditor.getScrollingModel().addVisibleAreaListener(myVisibleAreaListener);
651         myLastEditor.getCaretModel().addCaretListener(myCaretMoveListener);
652       }
653     }
654   }
655
656   private class MyAnActionListener implements AnActionListener {
657     public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
658       if (action instanceof ActionToIgnore) return;
659
660       AnAction escapeAction = ActionManagerEx.getInstanceEx().getAction(IdeActions.ACTION_EDITOR_ESCAPE);
661       if (action == escapeAction) return;
662
663       hideHints(HIDE_BY_ANY_KEY, false, false);
664     }
665
666
667     public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
668     }
669
670     public void beforeEditorTyping(char c, DataContext dataContext) {}
671   }
672
673   /**
674    * Hides all hints when selected editor changes. Unfortunately  user can change
675    * selected editor by mouse. These clicks are not AnActions so they are not
676    * fired by ActionManager.
677    */
678   private final class MyEditorManagerListener extends FileEditorManagerAdapter {
679     public void selectionChanged(FileEditorManagerEvent event) {
680       hideHints(0, false, true);
681     }
682   }
683
684   /**
685    * We have to spy for all opened projects to register MyEditorManagerListener into
686    * all opened projects.
687    */
688   private final class MyProjectManagerListener extends ProjectManagerAdapter {
689     public void projectOpened(Project project) {
690       project.getMessageBus().connect(project).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, myEditorManagerListener);
691     }
692
693     public void projectClosed(Project project) {
694       // avoid leak through com.intellij.codeInsight.hint.TooltipController.myCurrentTooltip
695       TooltipController.getInstance().cancelTooltips();
696
697       myQuestionAction = null;
698       myQuestionHint = null;
699     }
700   }
701
702   boolean isEscapeHandlerEnabled() {
703     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
704     for (int i = myHintsStack.size() - 1; i >= 0; i--) {
705       final HintInfo info = myHintsStack.get(i);
706       if (!info.hint.isVisible()) {
707         myHintsStack.remove(i);
708         continue;
709       }
710
711       if ((info.flags & (HIDE_BY_ESCAPE | HIDE_BY_ANY_KEY)) != 0) {
712         return true;
713       }
714     }
715     return false;
716   }
717
718   public boolean hideHints(int mask, boolean onlyOne, boolean editorChanged) {
719     LOG.assertTrue(SwingUtilities.isEventDispatchThread());
720     try {
721       boolean done = false;
722
723       for (int i = myHintsStack.size() - 1; i >= 0; i--) {
724         final HintInfo info = myHintsStack.get(i);
725         if (!info.hint.isVisible()) {
726           myHintsStack.remove(i);
727           continue;
728         }
729
730         if ((info.flags & mask) != 0 || editorChanged && !info.reviveOnEditorChange) {
731           info.hint.hide();
732           myHintsStack.remove(info);
733           if (onlyOne) {
734             return true;
735           }
736           done = true;
737         }
738       }
739
740       return done;
741     }
742     finally {
743       if (myHintsStack.isEmpty()) {
744         updateLastEditor(null);
745       }
746     }
747   }
748
749   private static class EditorHintListenerHolder {
750     private static final EditorHintListener ourEditorHintPublisher =
751         ApplicationManager.getApplication().getMessageBus().syncPublisher(EditorHintListener.TOPIC);
752
753     private EditorHintListenerHolder() {
754     }
755   }
756
757   private static EditorHintListener getPublisher() {
758     return EditorHintListenerHolder.ourEditorHintPublisher;
759   }
760 }