replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / quickFixes / QuickFixManager.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.uiDesigner.quickFixes;
17
18 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.openapi.command.CommandProcessor;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.ui.popup.JBPopupFactory;
23 import com.intellij.openapi.ui.popup.ListPopup;
24 import com.intellij.openapi.ui.popup.PopupStep;
25 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
26 import com.intellij.openapi.util.Pair;
27 import com.intellij.ui.HintHint;
28 import com.intellij.ui.LightweightHint;
29 import com.intellij.uiDesigner.ErrorInfo;
30 import com.intellij.uiDesigner.UIDesignerBundle;
31 import com.intellij.uiDesigner.designSurface.GuiEditor;
32 import com.intellij.uiDesigner.radComponents.RadComponent;
33 import com.intellij.util.Alarm;
34 import com.intellij.util.IJSwingUtilities;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import javax.swing.*;
39 import javax.swing.event.ChangeEvent;
40 import javax.swing.event.ChangeListener;
41 import java.awt.*;
42 import java.util.ArrayList;
43
44 /**
45  * @author Anton Katilin
46  * @author Vladimir Kondratyev
47  */
48 public abstract class QuickFixManager <T extends JComponent>{
49   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.quickFixes.QuickFixManager");
50
51   private GuiEditor myEditor;
52   /** Component on which hint will be shown */
53   protected final T myComponent;
54   /**
55    * This alarm contains request for showing of hint
56    */
57   private final Alarm myAlarm;
58   /**
59    * This request updates visibility of the hint
60    */
61   private final MyShowHintRequest myShowHintRequest;
62   /**
63    * My currently visible hint. May be null if there is no visible hint
64    */
65   private LightweightHint myHint;
66   private Rectangle myLastHintBounds;
67
68   public QuickFixManager(@Nullable final GuiEditor editor, @NotNull final T component, @NotNull final JViewport viewPort) {
69     myEditor = editor;
70     myComponent = component;
71     myAlarm = new Alarm();
72     myShowHintRequest = new MyShowHintRequest(this);
73
74     (new VisibilityWatcherImpl(this, component)).install(myComponent);
75     myComponent.addFocusListener(new FocusListenerImpl(this));
76
77     // Alt+Enter
78     new ShowHintAction(this, component);
79
80     viewPort.addChangeListener(new ChangeListener() {
81       public void stateChanged(ChangeEvent e) {
82         updateIntentionHintPosition(viewPort);
83       }
84     });
85   }
86
87   public final GuiEditor getEditor(){
88     return myEditor;
89   }
90
91   public void setEditor(final GuiEditor editor) {
92     myEditor = editor;
93   }
94
95   /**
96    * @return error info for the current {@link #myComponent} state.
97    */
98   @NotNull
99   protected abstract ErrorInfo[] getErrorInfos();
100
101   /**
102    * @return rectangle (in {@link #myComponent} coordinates) that represents
103    * area that contains errors. This methods is invoked only if {@link #getErrorInfos()}
104    * returned non empty list of error infos. {@code null} means that
105    * error bounds are not defined.
106    */
107   @Nullable
108   protected abstract Rectangle getErrorBounds();
109
110   public void refreshIntentionHint() {
111     if(!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)){
112       hideIntentionHint();
113       return;
114     }
115     if (myHint == null || !myHint.isVisible()) {
116       updateIntentionHintVisibility();
117     }
118     else {
119       final ErrorInfo[] errorInfos = getErrorInfos();
120       final Rectangle bounds = getErrorBounds();
121       if (!haveFixes(errorInfos) || bounds == null || !bounds.equals(myLastHintBounds)) {
122         hideIntentionHint();
123         updateIntentionHintVisibility();
124       }
125     }
126   }
127
128   /**
129    * Adds in timer queue requst for updating visibility of the popup hint
130    */
131   public final void updateIntentionHintVisibility(){
132     myAlarm.cancelAllRequests();
133     myAlarm.addRequest(myShowHintRequest, 500);
134   }
135
136   /**
137    * Shows intention hint (light bulb) if it's not visible yet.
138    */
139   final void showIntentionHint(){
140     if(!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)){
141       hideIntentionHint();
142       return;
143     }
144
145     // 1. Hide previous hint (if any)
146     hideIntentionHint();
147
148     // 2. Found error (if any)
149     final ErrorInfo[] errorInfos = getErrorInfos();
150     if(!haveFixes(errorInfos)) {
151       hideIntentionHint();
152       return;
153     }
154
155     // 3. Determine position where this hint should be shown
156     final Rectangle bounds = getErrorBounds();
157     if(bounds == null){
158       return;
159     }
160
161     // 4. Show light bulb to fix this error
162     final LightBulbComponentImpl lightBulbComponent = new LightBulbComponentImpl(this, AllIcons.Actions.IntentionBulb);
163     myHint = new LightweightHint(lightBulbComponent);
164     myLastHintBounds = bounds;
165     myHint.show(myComponent, bounds.x - AllIcons.Actions.IntentionBulb.getIconWidth() - 4, bounds.y, myComponent, new HintHint(myComponent, bounds.getLocation()));
166   }
167
168   private void updateIntentionHintPosition(final JViewport viewPort) {
169     if (myHint != null && myHint.isVisible()) {
170       Rectangle rc = getErrorBounds();
171       if (rc != null) {
172         myLastHintBounds = rc;
173         Rectangle hintRect = new Rectangle(rc.x - AllIcons.Actions.IntentionBulb.getIconWidth() - 4, rc.y, AllIcons.Actions.IntentionBulb
174                                                                                                              .getIconWidth() + 4, AllIcons.Actions.IntentionBulb
175                                                                                                                                     .getIconHeight() + 4);
176         LOG.debug("hintRect=" + hintRect);
177         if (getHintClipRect(viewPort).contains(hintRect)) {
178           myHint.pack();
179         }
180         else {
181           myHint.hide();
182         }
183       }
184     }
185   }
186
187   protected Rectangle getHintClipRect(final JViewport viewPort) {
188     return viewPort.getViewRect();
189   }
190
191   private static boolean haveFixes(final ErrorInfo[] errorInfos) {
192     boolean haveFixes = false;
193     for(ErrorInfo errorInfo: errorInfos) {
194       if (errorInfo.myFixes.length > 0 || errorInfo.getInspectionId() != null) {
195         haveFixes = true;
196         break;
197       }
198     }
199     return haveFixes;
200   }
201
202   /**
203    * Hides currently visible hint (light bulb) .If any.
204    */
205   public final void hideIntentionHint(){
206     myAlarm.cancelAllRequests();
207     if(myHint != null && myHint.isVisible()){
208       myHint.hide();
209       myComponent.paintImmediately(myComponent.getVisibleRect());
210     }
211   }
212
213   final void showIntentionPopup(){
214     LOG.debug("showIntentionPopup()");
215     if(myHint == null || !myHint.isVisible()){
216       return;
217     }
218     final ErrorInfo[] errorInfos = getErrorInfos();
219     if(!haveFixes(errorInfos)){
220       return;
221     }
222
223     final ArrayList<ErrorWithFix> fixList = new ArrayList<>();
224     for(ErrorInfo errorInfo: errorInfos) {
225       final QuickFix[] quickFixes = errorInfo.myFixes;
226       if (quickFixes.length > 0) {
227         for (QuickFix fix: quickFixes) {
228           fixList.add(new ErrorWithFix(errorInfo, fix));
229         }
230       }
231       else if (errorInfo.getInspectionId() != null) {
232         buildSuppressFixes(errorInfo, fixList, true);
233       }
234     }
235
236     final ListPopup popup = JBPopupFactory.getInstance().createListPopup(new QuickFixPopupStep(fixList, true));
237     popup.showUnderneathOf(myHint.getComponent());
238   }
239
240   private void buildSuppressFixes(final ErrorInfo errorInfo, final ArrayList<ErrorWithFix> suppressList, boolean named) {
241     final String suppressName = named
242                                 ? UIDesignerBundle.message("action.suppress.named.for.component", errorInfo.myDescription)
243                                 : UIDesignerBundle.message("action.suppress.for.component");
244     final String suppressAllName = named
245                                 ? UIDesignerBundle.message("action.suppress.named.for.all.components", errorInfo.myDescription)
246                                 : UIDesignerBundle.message("action.suppress.for.all.components");
247
248     final SuppressFix suppressFix = new SuppressFix(myEditor, suppressName,
249                                                     errorInfo.getInspectionId(), errorInfo.getComponent());
250     final SuppressFix suppressAllFix = new SuppressFix(myEditor, suppressAllName,
251                                                        errorInfo.getInspectionId(), null);
252     suppressList.add(new ErrorWithFix(errorInfo, suppressFix));
253     suppressList.add(new ErrorWithFix(errorInfo, suppressAllFix));
254   }
255
256   private static class ErrorWithFix extends Pair<ErrorInfo, QuickFix> {
257     public ErrorWithFix(final ErrorInfo first, final QuickFix second) {
258       super(first, second);
259     }
260   }
261
262   private class QuickFixPopupStep extends BaseListPopupStep<ErrorWithFix> {
263     private final boolean myShowSuppresses;
264
265     public QuickFixPopupStep(final ArrayList<ErrorWithFix> fixList, boolean showSuppresses) {
266       super(null, fixList);
267       myShowSuppresses = showSuppresses;
268     }
269
270     @NotNull
271     public String getTextFor(final ErrorWithFix value) {
272       return value.second.getName();
273     }
274
275     public PopupStep onChosen(final ErrorWithFix selectedValue, final boolean finalChoice) {
276       if (selectedValue.second instanceof PopupQuickFix) {
277         return ((PopupQuickFix) selectedValue.second).getPopupStep();
278       }
279       if (finalChoice || !myShowSuppresses) {
280         return doFinalStep(
281           () -> CommandProcessor.getInstance().executeCommand(myEditor.getProject(), () -> selectedValue.second.run(), selectedValue.second.getName(), null));
282       }
283       if (selectedValue.first.getInspectionId() != null && selectedValue.second.getComponent() != null &&
284           !(selectedValue.second instanceof SuppressFix)) {
285         ArrayList<ErrorWithFix> suppressList = new ArrayList<>();
286         buildSuppressFixes(selectedValue.first, suppressList, false);
287         return new QuickFixPopupStep(suppressList, false);
288       }
289       return FINAL_CHOICE;
290     }
291
292     public boolean hasSubstep(final ErrorWithFix selectedValue) {
293       return (myShowSuppresses && selectedValue.first.getInspectionId() != null && selectedValue.second.getComponent() != null &&
294         !(selectedValue.second instanceof SuppressFix)) || selectedValue.second instanceof PopupQuickFix;
295     }
296
297     @Override public boolean isAutoSelectionEnabled() {
298       return false;
299     }
300   }
301
302   private static class SuppressFix extends QuickFix {
303     private final String myInspectionId;
304
305     public SuppressFix(final GuiEditor editor, final String name, final String inspectionId, final RadComponent component) {
306       super(editor, name, component);
307       myInspectionId = inspectionId;
308     }
309
310     public void run() {
311       if (!myEditor.ensureEditable()) return;
312       myEditor.getRootContainer().suppressInspection(myInspectionId, myComponent);
313       myEditor.refreshAndSave(true);
314       DaemonCodeAnalyzer.getInstance(myEditor.getProject()).restart();
315     }
316   }
317
318   private final class MyShowHintRequest implements Runnable{
319     private final QuickFixManager myManager;
320
321     public MyShowHintRequest(@NotNull final QuickFixManager manager) {
322       myManager = manager;
323     }
324
325     public void run() {
326       myManager.showIntentionHint();
327     }
328   }
329 }