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 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
, ShowIntentionsPass
.IntentionsInfo intentions
) {
203 ApplicationManager
.getApplication().assertReadAccessAllowed();
207 setLayout(new BorderLayout());
210 boolean showFix
= false;
211 for (final HighlightInfo
.IntentionActionDescriptor pairs
: intentions
.errorFixesToShow
) {
212 IntentionAction fix
= pairs
.getAction();
213 if (IntentionManagerSettings
.getInstance().isShowLightBulb(fix
)) {
218 mySmartTagIcon
= showFix ? ourQuickFixIcon
: ourBulbIcon
;
220 myHighlightedIcon
= new RowIcon(2);
221 myHighlightedIcon
.setIcon(mySmartTagIcon
, 0);
222 myHighlightedIcon
.setIcon(ourArrowIcon
, 1);
224 myButton
= new JButton(mySmartTagIcon
);
225 myButton
.setFocusable(false);
226 myButton
.setMargin(INACTIVE_MARGIN
);
227 myButton
.setBorderPainted(false);
228 myButton
.setContentAreaFilled(false);
230 add(myButton
, BorderLayout
.CENTER
);
231 setBorder(INACTIVE_BORDER
);
233 myButton
.addActionListener(new ActionListener() {
234 public void actionPerformed(ActionEvent e
) {
239 myButton
.addMouseListener(new MouseAdapter() {
240 public void mouseEntered(MouseEvent e
) {
244 public void mouseExited(MouseEvent e
) {
249 myComponentHint
= new MyComponentHint(this);
250 IntentionListStep step
= new IntentionListStep(this, intentions
, myEditor
, myFile
, project
);
251 recreateMyPopup(step
);
255 Disposer
.dispose(this);
258 private void onMouseExit() {
259 Window ancestor
= SwingUtilities
.getWindowAncestor(myPopup
.getContent());
260 if (ancestor
== null) {
261 myButton
.setBackground(BACKGROUND_COLOR
);
262 myButton
.setIcon(mySmartTagIcon
);
263 setBorder(INACTIVE_BORDER
);
264 myButton
.setMargin(INACTIVE_MARGIN
);
265 updateComponentHintSize();
269 private void onMouseEnter() {
270 myButton
.setBackground(HintUtil
.QUESTION_COLOR
);
271 myButton
.setIcon(myHighlightedIcon
);
272 setBorder(BorderFactory
.createLineBorder(Color
.black
));
273 myButton
.setMargin(ACTIVE_MARGIN
);
274 updateComponentHintSize();
276 String acceleratorsText
= KeymapUtil
.getFirstKeyboardShortcutText(
277 ActionManager
.getInstance().getAction(IdeActions
.ACTION_SHOW_INTENTION_ACTIONS
));
278 if (acceleratorsText
.length() > 0) {
279 myButton
.setToolTipText(CodeInsightBundle
.message("lightbulb.tooltip", acceleratorsText
));
283 private void updateComponentHintSize() {
284 Component component
= myComponentHint
.getComponent();
285 component
.setSize(getPreferredSize().width
, getHeight());
289 public LightweightHint
getComponentHint() {
290 return myComponentHint
;
293 private void closePopup() {
295 myPopupShown
= false;
298 private void showPopup() {
299 if (myPopup
== null || myPopup
.isDisposed()) return;
302 myPopup
.show(RelativePoint
.getSouthWestOf(this));
305 myPopup
.showInBestPositionFor(myEditor
);
311 private void recreateMyPopup(IntentionListStep step
) {
312 if (myPopup
!= null) {
313 Disposer
.dispose(myPopup
);
315 myPopup
= JBPopupFactory
.getInstance().createListPopup(step
);
316 myPopup
.addListener(new JBPopupListener
.Adapter() {
318 public void onClosed(LightweightWindowEvent event
) {
319 myPopupShown
= false;
322 Disposer
.register(this, myPopup
);
323 Disposer
.register(myPopup
, new Disposable() {
324 public void dispose() {
325 ApplicationManager
.getApplication().assertIsDispatchThread();
330 void canceled(IntentionListStep intentionListStep
) {
331 if (myPopup
.getListStep() != intentionListStep
|| myDisposed
) {
334 // Root canceled. Create new popup. This one cannot be reused.
335 recreateMyPopup(intentionListStep
);
338 private class MyComponentHint
extends LightweightHint
{
339 private boolean myVisible
= false;
340 private boolean myShouldDelay
;
342 private MyComponentHint(JComponent component
) {
346 public void show(@NotNull final JComponent parentComponent
, final int x
, final int y
, final JComponent focusBackComponent
) {
349 myAlarm
.cancelAllRequests();
350 myAlarm
.addRequest(new Runnable() {
352 showImpl(parentComponent
, x
, y
, focusBackComponent
);
357 showImpl(parentComponent
, x
, y
, focusBackComponent
);
361 private void showImpl(JComponent parentComponent
, int x
, int y
, JComponent focusBackComponent
) {
362 if (!parentComponent
.isShowing()) return;
363 super.show(parentComponent
, x
, y
, focusBackComponent
);
369 myAlarm
.cancelAllRequests();
372 public boolean isVisible() {
373 return myVisible
|| super.isVisible();
376 public void setShouldDelay(boolean shouldDelay
) {
377 myShouldDelay
= shouldDelay
;
381 public static class EnableDisableIntentionAction
implements IntentionAction
{
382 private final String myActionFamilyName
;
383 private final IntentionManagerSettings mySettings
= IntentionManagerSettings
.getInstance();
384 private final IntentionAction myAction
;
386 public EnableDisableIntentionAction(IntentionAction action
) {
387 myActionFamilyName
= action
.getFamilyName();
389 // needed for checking errors in user written actions
390 //noinspection ConstantConditions
391 LOG
.assertTrue(myActionFamilyName
!= null, "action "+action
.getClass()+" family returned null");
395 public String
getText() {
396 return mySettings
.isEnabled(myAction
) ?
397 CodeInsightBundle
.message("disable.intention.action", myActionFamilyName
) :
398 CodeInsightBundle
.message("enable.intention.action", myActionFamilyName
);
402 public String
getFamilyName() {
406 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
410 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
411 mySettings
.setEnabled(myAction
, !mySettings
.isEnabled(myAction
));
414 public boolean startInWriteAction() {
419 public String
toString() {