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
;
49 import javax
.swing
.border
.Border
;
51 import java
.awt
.event
.ActionEvent
;
52 import java
.awt
.event
.ActionListener
;
53 import java
.awt
.event
.MouseAdapter
;
54 import java
.awt
.event
.MouseEvent
;
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
);
107 component
.showIntentionHintImpl(false, position
);
108 ApplicationManager
.getApplication().invokeLater(new Runnable() {
110 component
.showPopup();
115 component
.showIntentionHintImpl(true, position
);
117 Disposer
.register(project
, component
);
122 public void dispose() {
124 myComponentHint
.hide();
128 public void editorScrolled() {
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
)) {
143 return Boolean
.FALSE
;
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() {
165 public int getPriority() {
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
);
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();
197 setLayout(new BorderLayout());
200 boolean showFix
= false;
201 for (final HighlightInfo
.IntentionActionDescriptor pairs
: intentions
.errorFixesToShow
) {
202 IntentionAction fix
= pairs
.getAction();
203 if (IntentionManagerSettings
.getInstance().isShowLightBulb(fix
)) {
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
) {
229 myButton
.addMouseListener(new MouseAdapter() {
230 public void mouseEntered(MouseEvent e
) {
234 public void mouseExited(MouseEvent e
) {
239 myComponentHint
= new MyComponentHint(this);
240 IntentionListStep step
= new IntentionListStep(this, intentions
, myEditor
, myFile
, project
);
241 recreateMyPopup(step
);
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() {
280 myPopupShown
= false;
283 private void showPopup() {
284 if (myPopup
== null || myPopup
.isDisposed()) return;
287 myPopup
.show(RelativePoint
.getSouthWestOf(this));
290 myPopup
.showInBestPositionFor(myEditor
);
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() {
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
) {
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
) {
331 public void show(@NotNull final JComponent parentComponent
, final int x
, final int y
, final JComponent focusBackComponent
) {
334 myAlarm
.cancelAllRequests();
335 myAlarm
.addRequest(new Runnable() {
337 showImpl(parentComponent
, x
, y
, focusBackComponent
);
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
);
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();
374 // needed for checking errors in user written actions
375 //noinspection ConstantConditions
376 LOG
.assertTrue(myActionFamilyName
!= null, "action "+action
.getClass()+" family returned null");
380 public String
getText() {
381 return mySettings
.isEnabled(myAction
) ?
382 CodeInsightBundle
.message("disable.intention.action", myActionFamilyName
) :
383 CodeInsightBundle
.message("enable.intention.action", myActionFamilyName
);
387 public String
getFamilyName() {
391 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
395 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
396 mySettings
.setEnabled(myAction
, !mySettings
.isEnabled(myAction
));
399 public boolean startInWriteAction() {
404 public String
toString() {