Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / intention / impl / IntentionHintComponent.java
1 /*
2  * Copyright 2000-2015 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
17 package com.intellij.codeInsight.intention.impl;
18
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
21 import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass;
22 import com.intellij.codeInsight.hint.HintManager;
23 import com.intellij.codeInsight.hint.HintManagerImpl;
24 import com.intellij.codeInsight.hint.PriorityQuestionAction;
25 import com.intellij.codeInsight.hint.ScrollAwareHint;
26 import com.intellij.codeInsight.intention.HighPriorityAction;
27 import com.intellij.codeInsight.intention.IntentionAction;
28 import com.intellij.codeInsight.intention.impl.config.IntentionActionWrapper;
29 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
30 import com.intellij.codeInsight.intention.impl.config.IntentionSettingsConfigurable;
31 import com.intellij.codeInsight.unwrap.ScopeHighlighter;
32 import com.intellij.codeInspection.SuppressIntentionActionFromFix;
33 import com.intellij.icons.AllIcons;
34 import com.intellij.openapi.Disposable;
35 import com.intellij.openapi.actionSystem.ActionManager;
36 import com.intellij.openapi.actionSystem.DataProvider;
37 import com.intellij.openapi.actionSystem.IdeActions;
38 import com.intellij.openapi.actionSystem.PlatformDataKeys;
39 import com.intellij.openapi.application.ApplicationManager;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.Editor;
42 import com.intellij.openapi.editor.EditorFactory;
43 import com.intellij.openapi.editor.VisualPosition;
44 import com.intellij.openapi.editor.actions.EditorActionUtil;
45 import com.intellij.openapi.editor.colors.EditorColors;
46 import com.intellij.openapi.editor.colors.EditorColorsManager;
47 import com.intellij.openapi.editor.event.EditorFactoryAdapter;
48 import com.intellij.openapi.editor.event.EditorFactoryEvent;
49 import com.intellij.openapi.keymap.KeymapUtil;
50 import com.intellij.openapi.options.ShowSettingsUtil;
51 import com.intellij.openapi.project.Project;
52 import com.intellij.openapi.ui.popup.JBPopupFactory;
53 import com.intellij.openapi.ui.popup.JBPopupListener;
54 import com.intellij.openapi.ui.popup.LightweightWindowEvent;
55 import com.intellij.openapi.ui.popup.ListPopup;
56 import com.intellij.openapi.util.Comparing;
57 import com.intellij.openapi.util.Condition;
58 import com.intellij.openapi.util.Disposer;
59 import com.intellij.psi.PsiElement;
60 import com.intellij.psi.PsiFile;
61 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
62 import com.intellij.refactoring.BaseRefactoringIntentionAction;
63 import com.intellij.ui.HintHint;
64 import com.intellij.ui.LightweightHint;
65 import com.intellij.ui.PopupMenuListenerAdapter;
66 import com.intellij.ui.RowIcon;
67 import com.intellij.ui.awt.RelativePoint;
68 import com.intellij.util.Alarm;
69 import com.intellij.util.IncorrectOperationException;
70 import com.intellij.util.ThreeState;
71 import com.intellij.util.containers.ContainerUtil;
72 import com.intellij.util.ui.EmptyIcon;
73 import org.jetbrains.annotations.NotNull;
74 import org.jetbrains.annotations.Nullable;
75 import org.jetbrains.annotations.TestOnly;
76
77 import javax.swing.*;
78 import javax.swing.border.Border;
79 import javax.swing.event.ListSelectionEvent;
80 import javax.swing.event.ListSelectionListener;
81 import javax.swing.event.PopupMenuEvent;
82 import javax.swing.event.PopupMenuListener;
83 import java.awt.*;
84 import java.awt.event.MouseAdapter;
85 import java.awt.event.MouseEvent;
86 import java.util.Collections;
87 import java.util.List;
88
89 /**
90  * @author max
91  * @author Mike
92  * @author Valentin
93  * @author Eugene Belyaev
94  * @author Konstantin Bulenkov
95  * @author and me too (Chinee?)
96  */
97 public class IntentionHintComponent extends JPanel implements Disposable, ScrollAwareHint {
98   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.IntentionHintComponent.ListPopupRunnable");
99
100   static final Icon ourInactiveArrowIcon = new EmptyIcon(AllIcons.General.ArrowDown.getIconWidth(), AllIcons.General.ArrowDown.getIconHeight());
101
102   private static final int NORMAL_BORDER_SIZE = 6;
103   private static final int SMALL_BORDER_SIZE = 4;
104
105   private static final Border INACTIVE_BORDER = BorderFactory.createEmptyBorder(NORMAL_BORDER_SIZE, NORMAL_BORDER_SIZE, NORMAL_BORDER_SIZE, NORMAL_BORDER_SIZE);
106   private static final Border INACTIVE_BORDER_SMALL = BorderFactory.createEmptyBorder(SMALL_BORDER_SIZE, SMALL_BORDER_SIZE, SMALL_BORDER_SIZE, SMALL_BORDER_SIZE);
107   
108   private static Border createActiveBorder() {
109     return BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(getBorderColor(), 1), BorderFactory.createEmptyBorder(NORMAL_BORDER_SIZE - 1, NORMAL_BORDER_SIZE-1, NORMAL_BORDER_SIZE-1, NORMAL_BORDER_SIZE-1));
110   }
111   
112   private static  Border createActiveBorderSmall() {
113     return BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(getBorderColor(), 1), BorderFactory.createEmptyBorder(SMALL_BORDER_SIZE-1, SMALL_BORDER_SIZE-1, SMALL_BORDER_SIZE-1, SMALL_BORDER_SIZE-1));
114   }
115
116   private static Color getBorderColor() {
117     return EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.SELECTED_TEARLINE_COLOR);
118   }
119
120   private final Editor myEditor;
121
122   private static final Alarm myAlarm = new Alarm();
123
124   private final RowIcon myHighlightedIcon;
125   private final JLabel myIconLabel;
126
127   private final RowIcon myInactiveIcon;
128
129   private static final int DELAY = 500;
130   private final MyComponentHint myComponentHint;
131   private volatile boolean myPopupShown = false;
132   private boolean myDisposed = false;
133   private volatile ListPopup myPopup;
134   private final PsiFile myFile;
135
136   private PopupMenuListener myOuterComboboxPopupListener;
137
138   @NotNull
139   public static IntentionHintComponent showIntentionHint(@NotNull Project project,
140                                                          @NotNull PsiFile file,
141                                                          @NotNull Editor editor,
142                                                          @NotNull ShowIntentionsPass.IntentionsInfo intentions,
143                                                          boolean showExpanded) {
144     ApplicationManager.getApplication().assertIsDispatchThread();
145     final Point position = getHintPosition(editor);
146     return showIntentionHint(project, file, editor, intentions, showExpanded, position);
147   }
148
149   @NotNull
150   public static IntentionHintComponent showIntentionHint(@NotNull final Project project,
151                                                          @NotNull PsiFile file,
152                                                          @NotNull final Editor editor,
153                                                          @NotNull ShowIntentionsPass.IntentionsInfo intentions,
154                                                          boolean showExpanded,
155                                                          @NotNull Point position) {
156     ApplicationManager.getApplication().assertIsDispatchThread();
157     final IntentionHintComponent component = new IntentionHintComponent(project, file, editor, intentions);
158
159     component.showIntentionHintImpl(!showExpanded, position);
160     Disposer.register(project, component);
161     if (showExpanded) {
162       ApplicationManager.getApplication().invokeLater(new Runnable() {
163         @Override
164         public void run() {
165           if (!editor.isDisposed() && editor.getComponent().isShowing()) {
166             component.showPopup(false);
167           }
168         }
169       }, project.getDisposed());
170     }
171
172     return component;
173   }
174
175   @TestOnly
176   public boolean isDisposed() {
177     return myDisposed;
178   }
179
180   @Override
181   public void dispose() {
182     ApplicationManager.getApplication().assertIsDispatchThread();
183     myDisposed = true;
184     myComponentHint.hide();
185     super.hide();
186
187     if (myOuterComboboxPopupListener != null) {
188       final Container ancestor = SwingUtilities.getAncestorOfClass(JComboBox.class, myEditor.getContentComponent());
189       if (ancestor != null) {
190         ((JComboBox)ancestor).removePopupMenuListener(myOuterComboboxPopupListener);
191       }
192
193       myOuterComboboxPopupListener = null;
194     }
195   }
196
197   @Override
198   public void editorScrolled() {
199     closePopup();
200   }
201
202   //true if actions updated, there is nothing to do
203   //false if has to recreate popup, no need to reshow
204   //null if has to reshow
205   public Boolean updateActions(@NotNull ShowIntentionsPass.IntentionsInfo intentions) {
206     if (myPopup.isDisposed()) return null;
207     if (!myFile.isValid()) return null;
208     IntentionListStep step = (IntentionListStep)myPopup.getListStep();
209     if (!step.updateActions(intentions)) {
210       return Boolean.TRUE;
211     }
212     if (!myPopupShown) {
213       return Boolean.FALSE;
214     }
215     return null;
216   }
217
218   // for using in tests !
219   @Nullable
220   public IntentionAction getAction(int index) {
221     if (myPopup == null || myPopup.isDisposed()) {
222       return null;
223     }
224     IntentionListStep listStep = (IntentionListStep)myPopup.getListStep();
225     List<IntentionActionWithTextCaching> values = listStep.getValues();
226     if (values.size() <= index) {
227       return null;
228     }
229     return values.get(index).getAction();
230   }
231
232   public void recreate() {
233     ApplicationManager.getApplication().assertIsDispatchThread();
234     IntentionListStep step = (IntentionListStep)myPopup.getListStep();
235     recreateMyPopup(step);
236   }
237
238   private void showIntentionHintImpl(final boolean delay, final Point position) {
239     final int offset = myEditor.getCaretModel().getOffset();
240
241     myComponentHint.setShouldDelay(delay);
242
243     HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
244
245     PriorityQuestionAction action = new PriorityQuestionAction() {
246       @Override
247       public boolean execute() {
248         showPopup(false);
249         return true;
250       }
251
252       @Override
253       public int getPriority() {
254         return -10;
255       }
256     };
257     if (hintManager.canShowQuestionAction(action)) {
258       hintManager.showQuestionHint(myEditor, position, offset, offset, myComponentHint, action, HintManager.ABOVE);
259     }
260   }
261
262   @NotNull
263   private static Point getHintPosition(Editor editor) {
264     if (ApplicationManager.getApplication().isUnitTestMode()) return new Point();
265     final int offset = editor.getCaretModel().getOffset();
266     final VisualPosition pos = editor.offsetToVisualPosition(offset);
267     int line = pos.line;
268
269     final Point position = editor.visualPositionToXY(new VisualPosition(line, 0));
270     LOG.assertTrue(editor.getComponent().isDisplayable());
271
272     JComponent convertComponent = editor.getContentComponent();
273
274     Point realPoint;
275     final boolean oneLineEditor = editor.isOneLineMode();
276     if (oneLineEditor) {
277       // place bulb at the corner of the surrounding component
278       final JComponent contentComponent = editor.getContentComponent();
279       Container ancestorOfClass = SwingUtilities.getAncestorOfClass(JComboBox.class, contentComponent);
280
281       if (ancestorOfClass != null) {
282         convertComponent = (JComponent) ancestorOfClass;
283       } else {
284         ancestorOfClass = SwingUtilities.getAncestorOfClass(JTextField.class, contentComponent);
285         if (ancestorOfClass != null) {
286           convertComponent = (JComponent) ancestorOfClass;
287         }
288       }
289
290       realPoint = new Point(- (AllIcons.Actions.RealIntentionBulb.getIconWidth() / 2) - 4, - (AllIcons.Actions.RealIntentionBulb
291                                                                                                 .getIconHeight() / 2));
292     } else {
293       // try to place bulb on the same line
294       final int borderHeight = NORMAL_BORDER_SIZE;
295
296       int yShift = -(NORMAL_BORDER_SIZE + AllIcons.Actions.RealIntentionBulb.getIconHeight());
297       if (canPlaceBulbOnTheSameLine(editor)) {
298         yShift = -(borderHeight + (AllIcons.Actions.RealIntentionBulb.getIconHeight() - editor.getLineHeight()) /2 + 3);
299       }
300
301       final int xShift = AllIcons.Actions.RealIntentionBulb.getIconWidth();
302
303       Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
304       realPoint = new Point(Math.max(0,visibleArea.x - xShift), position.y + yShift);
305     }
306
307     Point location = SwingUtilities.convertPoint(convertComponent, realPoint, editor.getComponent().getRootPane().getLayeredPane());
308     return new Point(location.x, location.y);
309   }
310
311   private static boolean canPlaceBulbOnTheSameLine(Editor editor) {
312     if (ApplicationManager.getApplication().isUnitTestMode() || editor.isOneLineMode()) return false;
313     final int offset = editor.getCaretModel().getOffset();
314     final VisualPosition pos = editor.offsetToVisualPosition(offset);
315     int line = pos.line;
316
317     final int firstNonSpaceColumnOnTheLine = EditorActionUtil.findFirstNonSpaceColumnOnTheLine(editor, line);
318     if (firstNonSpaceColumnOnTheLine == -1) return false;
319     final Point point = editor.visualPositionToXY(new VisualPosition(line, firstNonSpaceColumnOnTheLine));
320     return point.x > AllIcons.Actions.RealIntentionBulb.getIconWidth() + (editor.isOneLineMode() ? SMALL_BORDER_SIZE : NORMAL_BORDER_SIZE) * 2;
321   }
322
323   private IntentionHintComponent(@NotNull Project project,
324                                  @NotNull PsiFile file,
325                                  @NotNull final Editor editor,
326                                  @NotNull ShowIntentionsPass.IntentionsInfo intentions) {
327     ApplicationManager.getApplication().assertIsDispatchThread();
328     myFile = file;
329     myEditor = editor;
330
331     setLayout(new BorderLayout());
332     setOpaque(false);
333
334     boolean showRefactoringsBulb = ContainerUtil.exists(intentions.inspectionFixesToShow, new Condition<HighlightInfo.IntentionActionDescriptor>() {
335       @Override
336       public boolean value(HighlightInfo.IntentionActionDescriptor descriptor) {
337         return descriptor.getAction() instanceof BaseRefactoringIntentionAction;
338       }
339     });
340     boolean showFix = !showRefactoringsBulb && ContainerUtil.exists(intentions.errorFixesToShow, new Condition<HighlightInfo.IntentionActionDescriptor>() {
341       @Override
342       public boolean value(HighlightInfo.IntentionActionDescriptor descriptor) {
343         return IntentionManagerSettings.getInstance().isShowLightBulb(descriptor.getAction());
344       }
345     });
346
347     Icon smartTagIcon = showRefactoringsBulb ? AllIcons.Actions.RefactoringBulb : showFix ? AllIcons.Actions.QuickfixBulb : AllIcons.Actions.IntentionBulb;
348
349     myHighlightedIcon = new RowIcon(2);
350     myHighlightedIcon.setIcon(smartTagIcon, 0);
351     myHighlightedIcon.setIcon(AllIcons.General.ArrowDown, 1);
352
353     myInactiveIcon = new RowIcon(2);
354     myInactiveIcon.setIcon(smartTagIcon, 0);
355     myInactiveIcon.setIcon(ourInactiveArrowIcon, 1);
356
357     myIconLabel = new JLabel(myInactiveIcon);
358     myIconLabel.setOpaque(false);
359
360     add(myIconLabel, BorderLayout.CENTER);
361
362     setBorder(editor.isOneLineMode() ? INACTIVE_BORDER_SMALL : INACTIVE_BORDER);
363
364     myIconLabel.addMouseListener(new MouseAdapter() {
365       @Override
366       public void mousePressed(MouseEvent e) {
367         if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1) {
368           showPopup(true);
369         }
370       }
371
372       @Override
373       public void mouseEntered(MouseEvent e) {
374         onMouseEnter(editor.isOneLineMode());
375       }
376
377       @Override
378       public void mouseExited(MouseEvent e) {
379         onMouseExit(editor.isOneLineMode());
380       }
381     });
382
383     myComponentHint = new MyComponentHint(this);
384     IntentionListStep step = new IntentionListStep(this, intentions, myEditor, myFile, project);
385     recreateMyPopup(step);
386     // dispose myself when editor closed
387     EditorFactory.getInstance().addEditorFactoryListener(new EditorFactoryAdapter() {
388       @Override
389       public void editorReleased(@NotNull EditorFactoryEvent event) {
390         if (event.getEditor() == myEditor) {
391           hide();
392         }
393       }
394     }, this);
395   }
396
397   @Override
398   public void hide() {
399     Disposer.dispose(this);
400   }
401
402   private void onMouseExit(final boolean small) {
403     Window ancestor = SwingUtilities.getWindowAncestor(myPopup.getContent());
404     if (ancestor == null) {
405       myIconLabel.setIcon(myInactiveIcon);
406       setBorder(small ? INACTIVE_BORDER_SMALL : INACTIVE_BORDER);
407     }
408   }
409
410   private void onMouseEnter(final boolean small) {
411     myIconLabel.setIcon(myHighlightedIcon);
412     setBorder(small ? createActiveBorderSmall() : createActiveBorder());
413
414     String acceleratorsText = KeymapUtil.getFirstKeyboardShortcutText(
415       ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS));
416     if (!acceleratorsText.isEmpty()) {
417       myIconLabel.setToolTipText(CodeInsightBundle.message("lightbulb.tooltip", acceleratorsText));
418     }
419   }
420
421   @TestOnly
422   public LightweightHint getComponentHint() {
423     return myComponentHint;
424   }
425
426   private void closePopup() {
427     ApplicationManager.getApplication().assertIsDispatchThread();
428     myPopup.cancel();
429     myPopupShown = false;
430   }
431
432   private void showPopup(boolean mouseClick) {
433     ApplicationManager.getApplication().assertIsDispatchThread();
434     if (myPopup == null || myPopup.isDisposed()) return;
435
436     if (mouseClick && isShowing()) {
437       final RelativePoint swCorner = RelativePoint.getSouthWestOf(this);
438       final int yOffset = canPlaceBulbOnTheSameLine(myEditor) ? 0 : myEditor.getLineHeight() - (myEditor.isOneLineMode() ? SMALL_BORDER_SIZE : NORMAL_BORDER_SIZE);
439       myPopup.show(new RelativePoint(swCorner.getComponent(), new Point(swCorner.getPoint().x, swCorner.getPoint().y + yOffset)));
440     }
441     else {
442       myPopup.showInBestPositionFor(myEditor);
443     }
444
445     myPopupShown = true;
446   }
447
448   private void recreateMyPopup(@NotNull IntentionListStep step) {
449     ApplicationManager.getApplication().assertIsDispatchThread();
450     if (myPopup != null) {
451       Disposer.dispose(myPopup);
452     }
453     myPopup = JBPopupFactory.getInstance().createListPopup(step);
454     
455     final PsiFile injectedFile = InjectedLanguageUtil.findInjectedPsiNoCommit(myFile, myEditor.getCaretModel().getOffset());
456     final Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(myEditor, injectedFile);
457     
458     final ScopeHighlighter highlighter = new ScopeHighlighter(myEditor);
459     final ScopeHighlighter injectionHighlighter = new ScopeHighlighter(injectedEditor);
460     
461     myPopup.addListener(new JBPopupListener.Adapter() {
462       @Override
463       public void onClosed(LightweightWindowEvent event) {
464         highlighter.dropHighlight();
465         injectionHighlighter.dropHighlight();
466         myPopupShown = false;
467       }
468     });
469     myPopup.addListSelectionListener(new ListSelectionListener() {
470       @Override
471       public void valueChanged(ListSelectionEvent e) {
472         final Object source = e.getSource();
473         highlighter.dropHighlight();
474         injectionHighlighter.dropHighlight();
475         
476         if (source instanceof DataProvider) {
477           final Object selectedItem = PlatformDataKeys.SELECTED_ITEM.getData((DataProvider)source);
478           if (selectedItem instanceof IntentionActionWithTextCaching) {
479             final IntentionAction action = ((IntentionActionWithTextCaching)selectedItem).getAction();
480             if (action instanceof SuppressIntentionActionFromFix) {
481               if (injectedFile != null && ((SuppressIntentionActionFromFix)action).isShouldBeAppliedToInjectionHost() == ThreeState.NO) {
482                 final PsiElement at = injectedFile.findElementAt(injectedEditor.getCaretModel().getOffset());
483                 final PsiElement container = ((SuppressIntentionActionFromFix)action).getContainer(at);
484                 if (container != null) {
485                   injectionHighlighter.highlight(container, Collections.singletonList(container));
486                 }
487               }
488               else {
489                 final PsiElement at = myFile.findElementAt(myEditor.getCaretModel().getOffset());
490                 final PsiElement container = ((SuppressIntentionActionFromFix)action).getContainer(at);
491                 if (container != null) {
492                   highlighter.highlight(container, Collections.singletonList(container));
493                 }
494               }
495             }
496           }
497         }
498       }
499     });
500
501     if (myEditor.isOneLineMode()) {
502       // hide popup on combobox popup show
503       final Container ancestor = SwingUtilities.getAncestorOfClass(JComboBox.class, myEditor.getContentComponent());
504       if (ancestor != null) {
505         final JComboBox comboBox = (JComboBox)ancestor;
506         myOuterComboboxPopupListener = new PopupMenuListenerAdapter() {
507           @Override
508           public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
509             hide();
510           }
511         };
512
513         comboBox.addPopupMenuListener(myOuterComboboxPopupListener);
514       }
515     }
516
517     Disposer.register(this, myPopup);
518     Disposer.register(myPopup, new Disposable() {
519       @Override
520       public void dispose() {
521         ApplicationManager.getApplication().assertIsDispatchThread();
522       }
523     });
524   }
525
526   void canceled(@NotNull IntentionListStep intentionListStep) {
527     if (myPopup.getListStep() != intentionListStep || myDisposed) {
528       return;
529     }
530     // Root canceled. Create new popup. This one cannot be reused.
531     recreateMyPopup(intentionListStep);
532   }
533
534   private static class MyComponentHint extends LightweightHint {
535     private boolean myVisible = false;
536     private boolean myShouldDelay;
537
538     private MyComponentHint(JComponent component) {
539       super(component);
540     }
541
542     @Override
543     public void show(@NotNull final JComponent parentComponent,
544                      final int x,
545                      final int y,
546                      final JComponent focusBackComponent,
547                      @NotNull HintHint hintHint) {
548       myVisible = true;
549       if (myShouldDelay) {
550         myAlarm.cancelAllRequests();
551         myAlarm.addRequest(new Runnable() {
552           @Override
553           public void run() {
554             showImpl(parentComponent, x, y, focusBackComponent);
555           }
556         }, DELAY);
557       }
558       else {
559         showImpl(parentComponent, x, y, focusBackComponent);
560       }
561     }
562
563     private void showImpl(JComponent parentComponent, int x, int y, JComponent focusBackComponent) {
564       if (!parentComponent.isShowing()) return;
565       super.show(parentComponent, x, y, focusBackComponent, new HintHint(parentComponent, new Point(x, y)));
566     }
567
568     @Override
569     public void hide() {
570       super.hide();
571       myVisible = false;
572       myAlarm.cancelAllRequests();
573     }
574
575     @Override
576     public boolean isVisible() {
577       return myVisible || super.isVisible();
578     }
579
580     public void setShouldDelay(boolean shouldDelay) {
581       myShouldDelay = shouldDelay;
582     }
583   }
584
585   public static class EnableDisableIntentionAction extends AbstractEditIntentionSettingsAction {
586     private final IntentionManagerSettings mySettings = IntentionManagerSettings.getInstance();
587     private final IntentionAction myAction;
588
589     public EnableDisableIntentionAction(IntentionAction action) {
590       super(action);
591       myAction = action;
592       // needed for checking errors in user written actions
593       //noinspection ConstantConditions
594       LOG.assertTrue(myFamilyName != null, "action "+action.getClass()+" family returned null");
595     }
596
597     @Override
598     @NotNull
599     public String getText() {
600       return mySettings.isEnabled(myAction) ?
601              CodeInsightBundle.message("disable.intention.action", myFamilyName) :
602              CodeInsightBundle.message("enable.intention.action", myFamilyName);
603     }
604
605     @Override
606     public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
607       mySettings.setEnabled(myAction, !mySettings.isEnabled(myAction));
608     }
609
610     @Override
611     public String toString() {
612       return getText();
613     }
614   }
615
616   public static class EditIntentionSettingsAction extends AbstractEditIntentionSettingsAction implements HighPriorityAction {
617     public EditIntentionSettingsAction(IntentionAction action) {
618       super(action);
619     }
620
621     @NotNull
622     @Override
623     public String getText() {
624       return "Edit intention settings";
625     }
626
627     @Override
628     public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
629       final IntentionSettingsConfigurable configurable = new IntentionSettingsConfigurable();
630       ShowSettingsUtil.getInstance().editConfigurable(project, configurable, new Runnable() {
631         @Override
632         public void run() {
633           SwingUtilities.invokeLater(new Runnable() {
634             @Override
635             public void run() {
636               configurable.selectIntention(myFamilyName);
637             }
638           });
639         }
640       });
641     }
642   }
643
644   private static abstract class AbstractEditIntentionSettingsAction implements IntentionAction {
645     protected final String myFamilyName;
646     private final boolean myDisabled;
647
648     public AbstractEditIntentionSettingsAction(IntentionAction action) {
649       myFamilyName = action.getFamilyName();
650       myDisabled = action instanceof IntentionActionWrapper &&
651                    Comparing.equal(action.getFamilyName(), ((IntentionActionWrapper)action).getFullFamilyName());
652     }
653
654     @NotNull
655     @Override
656     public String getFamilyName() {
657       return getText();
658     }
659
660     @Override
661     public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
662       return !myDisabled;
663     }
664
665     @Override
666     public boolean startInWriteAction() {
667       return false;
668     }
669   }
670 }