file level inspection ui
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / intention / impl / IntentionHintComponent.java
blobbf9f82d76cf66ab9b9074fa7d20a4bf6294c5041
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.HintManagerImpl;
23 import com.intellij.codeInsight.hint.HintUtil;
24 import com.intellij.codeInsight.hint.PriorityQuestionAction;
25 import com.intellij.codeInsight.hint.ScrollAwareHint;
26 import com.intellij.codeInsight.intention.IntentionAction;
27 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
28 import com.intellij.openapi.Disposable;
29 import com.intellij.openapi.actionSystem.ActionManager;
30 import com.intellij.openapi.actionSystem.IdeActions;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.editor.LogicalPosition;
35 import com.intellij.openapi.keymap.KeymapUtil;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.ui.popup.JBPopupFactory;
38 import com.intellij.openapi.ui.popup.JBPopupListener;
39 import com.intellij.openapi.ui.popup.LightweightWindowEvent;
40 import com.intellij.openapi.ui.popup.ListPopup;
41 import com.intellij.openapi.util.Disposer;
42 import com.intellij.openapi.util.IconLoader;
43 import com.intellij.psi.PsiFile;
44 import com.intellij.ui.LightweightHint;
45 import com.intellij.ui.RowIcon;
46 import com.intellij.ui.awt.RelativePoint;
47 import com.intellij.util.Alarm;
48 import com.intellij.util.IncorrectOperationException;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.TestOnly;
52 import javax.swing.*;
53 import javax.swing.border.Border;
54 import java.awt.*;
55 import java.awt.event.ActionEvent;
56 import java.awt.event.ActionListener;
57 import java.awt.event.MouseAdapter;
58 import java.awt.event.MouseEvent;
60 /**
61 * @author max
62 * @author Mike
63 * @author Valentin
64 * @author Eugene Belyaev
65 * @author Konstantin Bulenkov
66 * @author and me too (Chinee?)
68 public class IntentionHintComponent extends JPanel implements Disposable, ScrollAwareHint {
69 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.IntentionHintComponent.ListPopupRunnable");
71 static final Icon ourIntentionIcon = IconLoader.getIcon("/actions/realIntentionBulb.png");
72 static final Icon ourBulbIcon = IconLoader.getIcon("/actions/intentionBulb.png");
73 static final Icon ourQuickFixIcon = IconLoader.getIcon("/actions/quickfixBulb.png");
74 static final Icon ourIntentionOffIcon = IconLoader.getIcon("/actions/realIntentionOffBulb.png");
75 static final Icon ourQuickFixOffIcon = IconLoader.getIcon("/actions/quickfixOffBulb.png");
76 static final Icon ourArrowIcon = IconLoader.getIcon("/general/arrowDown.png");
77 private static final Border INACTIVE_BORDER = null;
78 private static final Insets INACTIVE_MARGIN = new Insets(0, 0, 0, 0);
79 private static final Insets ACTIVE_MARGIN = new Insets(0, 0, 0, 0);
81 private final Editor myEditor;
83 private static final Alarm myAlarm = new Alarm();
85 private final RowIcon myHighlightedIcon;
86 private final JButton myButton;
88 private final Icon mySmartTagIcon;
90 private static final int DELAY = 500;
91 private final MyComponentHint myComponentHint;
92 private static final Color BACKGROUND_COLOR = new Color(255, 255, 255, 0);
93 private boolean myPopupShown = false;
94 private boolean myDisposed = false;
95 private ListPopup myPopup;
96 private final PsiFile myFile;
98 public static IntentionHintComponent showIntentionHint(Project project, final PsiFile file, Editor editor, ShowIntentionsPass.IntentionsInfo intentions,
99 boolean showExpanded) {
100 final Point position = getHintPosition(editor);
101 return showIntentionHint(project, file, editor, intentions, showExpanded, position);
104 public static IntentionHintComponent showIntentionHint(Project project, final PsiFile file, Editor editor,
105 @NotNull ShowIntentionsPass.IntentionsInfo intentions,
106 boolean showExpanded,
107 final Point position) {
108 final IntentionHintComponent component = new IntentionHintComponent(project, file, editor, intentions);
110 if (showExpanded) {
111 component.showIntentionHintImpl(false, position);
112 ApplicationManager.getApplication().invokeLater(new Runnable() {
113 public void run() {
114 component.showPopup();
118 else {
119 component.showIntentionHintImpl(true, position);
121 Disposer.register(project, component);
123 return component;
126 @TestOnly
127 public boolean isDisposed() {
128 return myDisposed;
131 public void dispose() {
132 myDisposed = true;
133 myComponentHint.hide();
134 super.hide();
137 public void editorScrolled() {
138 closePopup();
141 //true if actions updated, there is nothing to do
142 //false if has to recreate popup, no need to reshow
143 //null if has to reshow
144 public synchronized Boolean updateActions(ShowIntentionsPass.IntentionsInfo intentions) {
145 if (myPopup.isDisposed()) return null;
146 if (!myFile.isValid()) return null;
147 IntentionListStep step = (IntentionListStep)myPopup.getListStep();
148 if (!step.updateActions(intentions)) {
149 return Boolean.TRUE;
151 if (!myPopupShown) {
152 return Boolean.FALSE;
154 return null;
157 public synchronized void recreate() {
158 IntentionListStep step = (IntentionListStep)myPopup.getListStep();
159 recreateMyPopup(step);
162 private void showIntentionHintImpl(final boolean delay, final Point position) {
163 final int offset = myEditor.getCaretModel().getOffset();
165 myComponentHint.setShouldDelay(delay);
167 HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
169 PriorityQuestionAction action = new PriorityQuestionAction() {
170 public boolean execute() {
171 showPopup();
172 return true;
175 public int getPriority() {
176 return -10;
179 if (hintManager.canShowQuestionAction(action)) {
180 hintManager.showQuestionHint(myEditor, position, offset, offset, myComponentHint, action);
184 private static Point getHintPosition(Editor editor) {
185 if (ApplicationManager.getApplication().isUnitTestMode()) return new Point();
186 final int offset = editor.getCaretModel().getOffset();
187 final LogicalPosition pos = editor.offsetToLogicalPosition(offset);
188 int line = pos.line;
190 final Point position = editor.logicalPositionToXY(new LogicalPosition(line, 0));
191 final int yShift = (ourIntentionIcon.getIconHeight() - editor.getLineHeight() - 1) / 2 - 1;
192 final int xShift = ourIntentionIcon.getIconWidth();
194 LOG.assertTrue(editor.getComponent().isDisplayable());
195 Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
196 Point realPoint = new Point(Math.max(0,visibleArea.x - xShift), position.y + yShift-20);
197 Point location = SwingUtilities.convertPoint(editor.getContentComponent(), realPoint, editor.getComponent().getRootPane().getLayeredPane());
199 return new Point(location.x, location.y);
202 private IntentionHintComponent(@NotNull Project project, @NotNull PsiFile file, @NotNull Editor editor,
203 @NotNull ShowIntentionsPass.IntentionsInfo intentions) {
204 ApplicationManager.getApplication().assertReadAccessAllowed();
205 myFile = file;
206 myEditor = editor;
208 setLayout(new BorderLayout());
209 setOpaque(false);
211 boolean showFix = false;
212 for (final HighlightInfo.IntentionActionDescriptor pairs : intentions.errorFixesToShow) {
213 IntentionAction fix = pairs.getAction();
214 if (IntentionManagerSettings.getInstance().isShowLightBulb(fix)) {
215 showFix = true;
216 break;
219 mySmartTagIcon = showFix ? ourQuickFixIcon : ourBulbIcon;
221 myHighlightedIcon = new RowIcon(2);
222 myHighlightedIcon.setIcon(mySmartTagIcon, 0);
223 myHighlightedIcon.setIcon(ourArrowIcon, 1);
225 myButton = new JButton(mySmartTagIcon);
226 myButton.setFocusable(false);
227 myButton.setMargin(INACTIVE_MARGIN);
228 myButton.setBorderPainted(false);
229 myButton.setContentAreaFilled(false);
231 add(myButton, BorderLayout.CENTER);
232 setBorder(INACTIVE_BORDER);
234 myButton.addActionListener(new ActionListener() {
235 public void actionPerformed(ActionEvent e) {
236 showPopup();
240 myButton.addMouseListener(new MouseAdapter() {
241 public void mouseEntered(MouseEvent e) {
242 onMouseEnter();
245 public void mouseExited(MouseEvent e) {
246 onMouseExit();
250 myComponentHint = new MyComponentHint(this);
251 IntentionListStep step = new IntentionListStep(this, intentions, myEditor, myFile, project);
252 recreateMyPopup(step);
255 public void hide() {
256 Disposer.dispose(this);
259 private void onMouseExit() {
260 Window ancestor = SwingUtilities.getWindowAncestor(myPopup.getContent());
261 if (ancestor == null) {
262 myButton.setBackground(BACKGROUND_COLOR);
263 myButton.setIcon(mySmartTagIcon);
264 setBorder(INACTIVE_BORDER);
265 myButton.setMargin(INACTIVE_MARGIN);
266 updateComponentHintSize();
270 private void onMouseEnter() {
271 myButton.setBackground(HintUtil.QUESTION_COLOR);
272 myButton.setIcon(myHighlightedIcon);
273 setBorder(BorderFactory.createLineBorder(Color.black));
274 myButton.setMargin(ACTIVE_MARGIN);
275 updateComponentHintSize();
277 String acceleratorsText = KeymapUtil.getFirstKeyboardShortcutText(
278 ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS));
279 if (acceleratorsText.length() > 0) {
280 myButton.setToolTipText(CodeInsightBundle.message("lightbulb.tooltip", acceleratorsText));
284 private void updateComponentHintSize() {
285 Component component = myComponentHint.getComponent();
286 component.setSize(getPreferredSize().width, getHeight());
289 @TestOnly
290 public LightweightHint getComponentHint() {
291 return myComponentHint;
294 private void closePopup() {
295 myPopup.cancel();
296 myPopupShown = false;
299 private void showPopup() {
300 if (myPopup == null || myPopup.isDisposed()) return;
302 if (isShowing()) {
303 myPopup.show(RelativePoint.getSouthWestOf(this));
305 else {
306 myPopup.showInBestPositionFor(myEditor);
309 myPopupShown = true;
312 private void recreateMyPopup(IntentionListStep step) {
313 if (myPopup != null) {
314 Disposer.dispose(myPopup);
316 myPopup = JBPopupFactory.getInstance().createListPopup(step);
317 myPopup.addListener(new JBPopupListener.Adapter() {
318 @Override
319 public void onClosed(LightweightWindowEvent event) {
320 myPopupShown = false;
323 Disposer.register(this, myPopup);
324 Disposer.register(myPopup, new Disposable() {
325 public void dispose() {
326 ApplicationManager.getApplication().assertIsDispatchThread();
331 void canceled(IntentionListStep intentionListStep) {
332 if (myPopup.getListStep() != intentionListStep || myDisposed) {
333 return;
335 // Root canceled. Create new popup. This one cannot be reused.
336 recreateMyPopup(intentionListStep);
339 private class MyComponentHint extends LightweightHint {
340 private boolean myVisible = false;
341 private boolean myShouldDelay;
343 private MyComponentHint(JComponent component) {
344 super(component);
347 public void show(@NotNull final JComponent parentComponent, final int x, final int y, final JComponent focusBackComponent) {
348 myVisible = true;
349 if (myShouldDelay) {
350 myAlarm.cancelAllRequests();
351 myAlarm.addRequest(new Runnable() {
352 public void run() {
353 showImpl(parentComponent, x, y, focusBackComponent);
355 }, DELAY);
357 else {
358 showImpl(parentComponent, x, y, focusBackComponent);
362 private void showImpl(JComponent parentComponent, int x, int y, JComponent focusBackComponent) {
363 if (!parentComponent.isShowing()) return;
364 super.show(parentComponent, x, y, focusBackComponent);
367 public void hide() {
368 super.hide();
369 myVisible = false;
370 myAlarm.cancelAllRequests();
373 public boolean isVisible() {
374 return myVisible || super.isVisible();
377 public void setShouldDelay(boolean shouldDelay) {
378 myShouldDelay = shouldDelay;
382 public static class EnableDisableIntentionAction implements IntentionAction{
383 private final String myActionFamilyName;
384 private final IntentionManagerSettings mySettings = IntentionManagerSettings.getInstance();
385 private final IntentionAction myAction;
387 public EnableDisableIntentionAction(IntentionAction action) {
388 myActionFamilyName = action.getFamilyName();
389 myAction = action;
390 // needed for checking errors in user written actions
391 //noinspection ConstantConditions
392 LOG.assertTrue(myActionFamilyName != null, "action "+action.getClass()+" family returned null");
395 @NotNull
396 public String getText() {
397 return mySettings.isEnabled(myAction) ?
398 CodeInsightBundle.message("disable.intention.action", myActionFamilyName) :
399 CodeInsightBundle.message("enable.intention.action", myActionFamilyName);
402 @NotNull
403 public String getFamilyName() {
404 return getText();
407 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
408 return true;
411 public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
412 mySettings.setEnabled(myAction, !mySettings.isEnabled(myAction));
415 public boolean startInWriteAction() {
416 return false;
419 @Override
420 public String toString() {
421 return getText();