IDEADEV-41249
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / intention / impl / IntentionHintComponent.java
blob5fa9bc51f1b7f44e126328dbbbe9c41795426ce9
1 /*
2 * Copyright 2000-2009 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.
17 package com.intellij.codeInsight.intention.impl;
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.*;
23 import com.intellij.codeInsight.intention.IntentionAction;
24 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.actionSystem.ActionManager;
27 import com.intellij.openapi.actionSystem.IdeActions;
28 import com.intellij.openapi.application.ApplicationManager;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.editor.Editor;
31 import com.intellij.openapi.editor.LogicalPosition;
32 import com.intellij.openapi.keymap.KeymapUtil;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.ui.popup.JBPopupFactory;
35 import com.intellij.openapi.ui.popup.JBPopupListener;
36 import com.intellij.openapi.ui.popup.LightweightWindowEvent;
37 import com.intellij.openapi.ui.popup.ListPopup;
38 import com.intellij.openapi.util.Disposer;
39 import com.intellij.openapi.util.IconLoader;
40 import com.intellij.psi.PsiFile;
41 import com.intellij.ui.LightweightHint;
42 import com.intellij.ui.RowIcon;
43 import com.intellij.ui.awt.RelativePoint;
44 import com.intellij.util.Alarm;
45 import com.intellij.util.IncorrectOperationException;
46 import org.jetbrains.annotations.NotNull;
48 import javax.swing.*;
49 import javax.swing.border.Border;
50 import java.awt.*;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.MouseAdapter;
54 import java.awt.event.MouseEvent;
56 /**
57 * @author max
58 * @author Mike
59 * @author Valentin
60 * @author Eugene Belyaev
61 * @author Konstantin Bulenkov
62 * @author and me too (Chinee?)
64 public class IntentionHintComponent extends JPanel implements Disposable, ScrollAwareHint {
65 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.IntentionHintComponent.ListPopupRunnable");
67 static final Icon ourIntentionIcon = IconLoader.getIcon("/actions/realIntentionBulb.png");
68 static final Icon ourBulbIcon = IconLoader.getIcon("/actions/intentionBulb.png");
69 static final Icon ourQuickFixIcon = IconLoader.getIcon("/actions/quickfixBulb.png");
70 static final Icon ourIntentionOffIcon = IconLoader.getIcon("/actions/realIntentionOffBulb.png");
71 static final Icon ourQuickFixOffIcon = IconLoader.getIcon("/actions/quickfixOffBulb.png");
72 static final Icon ourArrowIcon = IconLoader.getIcon("/general/arrowDown.png");
73 private static final Border INACTIVE_BORDER = null;
74 private static final Insets INACTIVE_MARGIN = new Insets(0, 0, 0, 0);
75 private static final Insets ACTIVE_MARGIN = new Insets(0, 0, 0, 0);
77 private final Editor myEditor;
79 private static final Alarm myAlarm = new Alarm();
81 private final RowIcon myHighlightedIcon;
82 private final JButton myButton;
84 private final Icon mySmartTagIcon;
86 private static final int DELAY = 500;
87 private final MyComponentHint myComponentHint;
88 private static final Color BACKGROUND_COLOR = new Color(255, 255, 255, 0);
89 private boolean myPopupShown = false;
90 private boolean myDisposed = false;
91 private ListPopup myPopup;
92 private final PsiFile myFile;
94 public static IntentionHintComponent showIntentionHint(Project project, final PsiFile file, Editor editor, ShowIntentionsPass.IntentionsInfo intentions,
95 boolean showExpanded) {
96 final Point position = getHintPosition(editor);
97 return showIntentionHint(project, file, editor, intentions, showExpanded, position);
100 public static IntentionHintComponent showIntentionHint(Project project, final PsiFile file, Editor editor,
101 ShowIntentionsPass.IntentionsInfo intentions,
102 boolean showExpanded,
103 final Point position) {
104 final IntentionHintComponent component = new IntentionHintComponent(project, file, editor, intentions);
106 if (showExpanded) {
107 component.showIntentionHintImpl(false, position);
108 ApplicationManager.getApplication().invokeLater(new Runnable() {
109 public void run() {
110 component.showPopup();
114 else {
115 component.showIntentionHintImpl(true, position);
117 Disposer.register(project, component);
119 return component;
122 public void dispose() {
123 myDisposed = true;
124 myComponentHint.hide();
125 super.hide();
128 public void editorScrolled() {
129 closePopup();
132 //true if actions updated, there is nothing to do
133 //false if has to recreate popup, no need to reshow
134 //null if has to reshow
135 public synchronized Boolean updateActions(ShowIntentionsPass.IntentionsInfo intentions) {
136 if (myPopup.isDisposed()) return null;
137 if (!myFile.isValid()) return null;
138 IntentionListStep step = (IntentionListStep)myPopup.getListStep();
139 if (!step.updateActions(intentions)) {
140 return Boolean.TRUE;
142 if (!myPopupShown) {
143 return Boolean.FALSE;
145 return null;
148 public synchronized void recreate() {
149 IntentionListStep step = (IntentionListStep)myPopup.getListStep();
150 recreateMyPopup(step);
153 private void showIntentionHintImpl(final boolean delay, final Point position) {
154 final int offset = myEditor.getCaretModel().getOffset();
156 myComponentHint.setShouldDelay(delay);
158 HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
159 PriorityQuestionAction action = new PriorityQuestionAction() {
160 public boolean execute() {
161 showPopup();
162 return true;
165 public int getPriority() {
166 return 0;
169 if (hintManager.canShowQuestionAction(action)) {
170 hintManager.showQuestionHint(myEditor, position, offset, offset, myComponentHint, action);
174 private static Point getHintPosition(Editor editor) {
176 final int offset = editor.getCaretModel().getOffset();
177 final LogicalPosition pos = editor.offsetToLogicalPosition(offset);
178 int line = pos.line;
180 final Point position = editor.logicalPositionToXY(new LogicalPosition(line, 0));
181 final int yShift = (ourIntentionIcon.getIconHeight() - editor.getLineHeight() - 1) / 2 - 1;
182 final int xShift = ourIntentionIcon.getIconWidth();
184 LOG.assertTrue(editor.getComponent().isDisplayable());
185 Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
186 Point realPoint = new Point(Math.max(0,visibleArea.x - xShift), position.y + yShift-20);
187 Point location = SwingUtilities.convertPoint(editor.getContentComponent(), realPoint, editor.getComponent().getRootPane().getLayeredPane());
189 return new Point(location.x, location.y);
192 private IntentionHintComponent(@NotNull Project project, @NotNull PsiFile file, @NotNull Editor editor, ShowIntentionsPass.IntentionsInfo intentions) {
193 ApplicationManager.getApplication().assertReadAccessAllowed();
194 myFile = file;
195 myEditor = editor;
197 setLayout(new BorderLayout());
198 setOpaque(false);
200 boolean showFix = false;
201 for (final HighlightInfo.IntentionActionDescriptor pairs : intentions.errorFixesToShow) {
202 IntentionAction fix = pairs.getAction();
203 if (IntentionManagerSettings.getInstance().isShowLightBulb(fix)) {
204 showFix = true;
205 break;
208 mySmartTagIcon = showFix ? ourQuickFixIcon : ourBulbIcon;
210 myHighlightedIcon = new RowIcon(2);
211 myHighlightedIcon.setIcon(mySmartTagIcon, 0);
212 myHighlightedIcon.setIcon(ourArrowIcon, 1);
214 myButton = new JButton(mySmartTagIcon);
215 myButton.setFocusable(false);
216 myButton.setMargin(INACTIVE_MARGIN);
217 myButton.setBorderPainted(false);
218 myButton.setContentAreaFilled(false);
220 add(myButton, BorderLayout.CENTER);
221 setBorder(INACTIVE_BORDER);
223 myButton.addActionListener(new ActionListener() {
224 public void actionPerformed(ActionEvent e) {
225 showPopup();
229 myButton.addMouseListener(new MouseAdapter() {
230 public void mouseEntered(MouseEvent e) {
231 onMouseEnter();
234 public void mouseExited(MouseEvent e) {
235 onMouseExit();
239 myComponentHint = new MyComponentHint(this);
240 IntentionListStep step = new IntentionListStep(this, intentions, myEditor, myFile, project);
241 recreateMyPopup(step);
244 public void hide() {
245 Disposer.dispose(this);
248 private void onMouseExit() {
249 Window ancestor = SwingUtilities.getWindowAncestor(myPopup.getContent());
250 if (ancestor == null) {
251 myButton.setBackground(BACKGROUND_COLOR);
252 myButton.setIcon(mySmartTagIcon);
253 setBorder(INACTIVE_BORDER);
254 myButton.setMargin(INACTIVE_MARGIN);
255 updateComponentHintSize();
259 private void onMouseEnter() {
260 myButton.setBackground(HintUtil.QUESTION_COLOR);
261 myButton.setIcon(myHighlightedIcon);
262 setBorder(BorderFactory.createLineBorder(Color.black));
263 myButton.setMargin(ACTIVE_MARGIN);
264 updateComponentHintSize();
266 String acceleratorsText = KeymapUtil.getFirstKeyboardShortcutText(
267 ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS));
268 if (acceleratorsText.length() > 0) {
269 myButton.setToolTipText(CodeInsightBundle.message("lightbulb.tooltip", acceleratorsText));
273 private void updateComponentHintSize() {
274 Component component = myComponentHint.getComponent();
275 component.setSize(getPreferredSize().width, getHeight());
278 private void closePopup() {
279 myPopup.cancel();
280 myPopupShown = false;
283 private void showPopup() {
284 if (myPopup == null || myPopup.isDisposed()) return;
286 if (isShowing()) {
287 myPopup.show(RelativePoint.getSouthWestOf(this));
289 else {
290 myPopup.showInBestPositionFor(myEditor);
293 myPopupShown = true;
296 void recreateMyPopup(IntentionListStep step) {
297 if (myPopup != null) {
298 Disposer.dispose(myPopup);
300 myPopup = JBPopupFactory.getInstance().createListPopup(step);
301 myPopup.addListener(new JBPopupListener.Adapter() {
302 @Override
303 public void onClosed(LightweightWindowEvent event) {
304 myPopupShown = false;
307 Disposer.register(this, myPopup);
308 Disposer.register(myPopup, new Disposable() {
309 public void dispose() {
310 ApplicationManager.getApplication().assertIsDispatchThread();
315 void canceled(IntentionListStep intentionListStep) {
316 if (myPopup.getListStep() != intentionListStep || myDisposed) {
317 return;
319 // Root canceled. Create new popup. This one cannot be reused.
320 recreateMyPopup(intentionListStep);
323 private class MyComponentHint extends LightweightHint {
324 private boolean myVisible = false;
325 private boolean myShouldDelay;
327 private MyComponentHint(JComponent component) {
328 super(component);
331 public void show(@NotNull final JComponent parentComponent, final int x, final int y, final JComponent focusBackComponent) {
332 myVisible = true;
333 if (myShouldDelay) {
334 myAlarm.cancelAllRequests();
335 myAlarm.addRequest(new Runnable() {
336 public void run() {
337 showImpl(parentComponent, x, y, focusBackComponent);
339 }, DELAY);
341 else {
342 showImpl(parentComponent, x, y, focusBackComponent);
346 private void showImpl(JComponent parentComponent, int x, int y, JComponent focusBackComponent) {
347 if (!parentComponent.isShowing()) return;
348 super.show(parentComponent, x, y, focusBackComponent);
351 public void hide() {
352 super.hide();
353 myVisible = false;
354 myAlarm.cancelAllRequests();
357 public boolean isVisible() {
358 return myVisible || super.isVisible();
361 public void setShouldDelay(boolean shouldDelay) {
362 myShouldDelay = shouldDelay;
366 public static class EnableDisableIntentionAction implements IntentionAction{
367 private final String myActionFamilyName;
368 private final IntentionManagerSettings mySettings = IntentionManagerSettings.getInstance();
369 private final IntentionAction myAction;
371 public EnableDisableIntentionAction(IntentionAction action) {
372 myActionFamilyName = action.getFamilyName();
373 myAction = action;
374 // needed for checking errors in user written actions
375 //noinspection ConstantConditions
376 LOG.assertTrue(myActionFamilyName != null, "action "+action.getClass()+" family returned null");
379 @NotNull
380 public String getText() {
381 return mySettings.isEnabled(myAction) ?
382 CodeInsightBundle.message("disable.intention.action", myActionFamilyName) :
383 CodeInsightBundle.message("enable.intention.action", myActionFamilyName);
386 @NotNull
387 public String getFamilyName() {
388 return getText();
391 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
392 return true;
395 public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
396 mySettings.setEnabled(myAction, !mySettings.isEnabled(myAction));
399 public boolean startInWriteAction() {
400 return false;
403 @Override
404 public String toString() {
405 return getText();