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
;
53 import javax
.swing
.border
.Border
;
55 import java
.awt
.event
.ActionEvent
;
56 import java
.awt
.event
.ActionListener
;
57 import java
.awt
.event
.MouseAdapter
;
58 import java
.awt
.event
.MouseEvent
;
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
);
111 component
.showIntentionHintImpl(false, position
);
112 ApplicationManager
.getApplication().invokeLater(new Runnable() {
114 component
.showPopup();
119 component
.showIntentionHintImpl(true, position
);
121 Disposer
.register(project
, component
);
127 public boolean isDisposed() {
131 public void dispose() {
133 myComponentHint
.hide();
137 public void editorScrolled() {
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
)) {
152 return Boolean
.FALSE
;
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() {
175 public int getPriority() {
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
);
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();
208 setLayout(new BorderLayout());
211 boolean showFix
= false;
212 for (final HighlightInfo
.IntentionActionDescriptor pairs
: intentions
.errorFixesToShow
) {
213 IntentionAction fix
= pairs
.getAction();
214 if (IntentionManagerSettings
.getInstance().isShowLightBulb(fix
)) {
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
) {
240 myButton
.addMouseListener(new MouseAdapter() {
241 public void mouseEntered(MouseEvent e
) {
245 public void mouseExited(MouseEvent e
) {
250 myComponentHint
= new MyComponentHint(this);
251 IntentionListStep step
= new IntentionListStep(this, intentions
, myEditor
, myFile
, project
);
252 recreateMyPopup(step
);
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());
290 public LightweightHint
getComponentHint() {
291 return myComponentHint
;
294 private void closePopup() {
296 myPopupShown
= false;
299 private void showPopup() {
300 if (myPopup
== null || myPopup
.isDisposed()) return;
303 myPopup
.show(RelativePoint
.getSouthWestOf(this));
306 myPopup
.showInBestPositionFor(myEditor
);
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() {
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
) {
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
) {
347 public void show(@NotNull final JComponent parentComponent
, final int x
, final int y
, final JComponent focusBackComponent
) {
350 myAlarm
.cancelAllRequests();
351 myAlarm
.addRequest(new Runnable() {
353 showImpl(parentComponent
, x
, y
, focusBackComponent
);
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
);
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();
390 // needed for checking errors in user written actions
391 //noinspection ConstantConditions
392 LOG
.assertTrue(myActionFamilyName
!= null, "action "+action
.getClass()+" family returned null");
396 public String
getText() {
397 return mySettings
.isEnabled(myAction
) ?
398 CodeInsightBundle
.message("disable.intention.action", myActionFamilyName
) :
399 CodeInsightBundle
.message("enable.intention.action", myActionFamilyName
);
403 public String
getFamilyName() {
407 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
411 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
412 mySettings
.setEnabled(myAction
, !mySettings
.isEnabled(myAction
));
415 public boolean startInWriteAction() {
420 public String
toString() {