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.
16 package com.intellij.uiDesigner.quickFixes;
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;
39 import javax.swing.event.ChangeEvent;
40 import javax.swing.event.ChangeListener;
42 import java.util.ArrayList;
45 * @author Anton Katilin
46 * @author Vladimir Kondratyev
48 public abstract class QuickFixManager <T extends JComponent>{
49 private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.quickFixes.QuickFixManager");
51 private GuiEditor myEditor;
52 /** Component on which hint will be shown */
53 protected final T myComponent;
55 * This alarm contains request for showing of hint
57 private final Alarm myAlarm;
59 * This request updates visibility of the hint
61 private final MyShowHintRequest myShowHintRequest;
63 * My currently visible hint. May be null if there is no visible hint
65 private LightweightHint myHint;
66 private Rectangle myLastHintBounds;
68 public QuickFixManager(@Nullable final GuiEditor editor, @NotNull final T component, @NotNull final JViewport viewPort) {
70 myComponent = component;
71 myAlarm = new Alarm();
72 myShowHintRequest = new MyShowHintRequest(this);
74 (new VisibilityWatcherImpl(this, component)).install(myComponent);
75 myComponent.addFocusListener(new FocusListenerImpl(this));
78 new ShowHintAction(this, component);
80 viewPort.addChangeListener(new ChangeListener() {
81 public void stateChanged(ChangeEvent e) {
82 updateIntentionHintPosition(viewPort);
87 public final GuiEditor getEditor(){
91 public void setEditor(final GuiEditor editor) {
96 * @return error info for the current {@link #myComponent} state.
99 protected abstract ErrorInfo[] getErrorInfos();
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.
108 protected abstract Rectangle getErrorBounds();
110 public void refreshIntentionHint() {
111 if(!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)){
115 if (myHint == null || !myHint.isVisible()) {
116 updateIntentionHintVisibility();
119 final ErrorInfo[] errorInfos = getErrorInfos();
120 final Rectangle bounds = getErrorBounds();
121 if (!haveFixes(errorInfos) || bounds == null || !bounds.equals(myLastHintBounds)) {
123 updateIntentionHintVisibility();
129 * Adds in timer queue requst for updating visibility of the popup hint
131 public final void updateIntentionHintVisibility(){
132 myAlarm.cancelAllRequests();
133 myAlarm.addRequest(myShowHintRequest, 500);
137 * Shows intention hint (light bulb) if it's not visible yet.
139 final void showIntentionHint(){
140 if(!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)){
145 // 1. Hide previous hint (if any)
148 // 2. Found error (if any)
149 final ErrorInfo[] errorInfos = getErrorInfos();
150 if(!haveFixes(errorInfos)) {
155 // 3. Determine position where this hint should be shown
156 final Rectangle bounds = getErrorBounds();
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()));
168 private void updateIntentionHintPosition(final JViewport viewPort) {
169 if (myHint != null && myHint.isVisible()) {
170 Rectangle rc = getErrorBounds();
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)) {
187 protected Rectangle getHintClipRect(final JViewport viewPort) {
188 return viewPort.getViewRect();
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) {
203 * Hides currently visible hint (light bulb) .If any.
205 public final void hideIntentionHint(){
206 myAlarm.cancelAllRequests();
207 if(myHint != null && myHint.isVisible()){
209 myComponent.paintImmediately(myComponent.getVisibleRect());
213 final void showIntentionPopup(){
214 LOG.debug("showIntentionPopup()");
215 if(myHint == null || !myHint.isVisible()){
218 final ErrorInfo[] errorInfos = getErrorInfos();
219 if(!haveFixes(errorInfos)){
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));
231 else if (errorInfo.getInspectionId() != null) {
232 buildSuppressFixes(errorInfo, fixList, true);
236 final ListPopup popup = JBPopupFactory.getInstance().createListPopup(new QuickFixPopupStep(fixList, true));
237 popup.showUnderneathOf(myHint.getComponent());
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");
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));
256 private static class ErrorWithFix extends Pair<ErrorInfo, QuickFix> {
257 public ErrorWithFix(final ErrorInfo first, final QuickFix second) {
258 super(first, second);
262 private class QuickFixPopupStep extends BaseListPopupStep<ErrorWithFix> {
263 private final boolean myShowSuppresses;
265 public QuickFixPopupStep(final ArrayList<ErrorWithFix> fixList, boolean showSuppresses) {
266 super(null, fixList);
267 myShowSuppresses = showSuppresses;
271 public String getTextFor(final ErrorWithFix value) {
272 return value.second.getName();
275 public PopupStep onChosen(final ErrorWithFix selectedValue, final boolean finalChoice) {
276 if (selectedValue.second instanceof PopupQuickFix) {
277 return ((PopupQuickFix) selectedValue.second).getPopupStep();
279 if (finalChoice || !myShowSuppresses) {
281 () -> CommandProcessor.getInstance().executeCommand(myEditor.getProject(), () -> selectedValue.second.run(), selectedValue.second.getName(), null));
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);
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;
297 @Override public boolean isAutoSelectionEnabled() {
302 private static class SuppressFix extends QuickFix {
303 private final String myInspectionId;
305 public SuppressFix(final GuiEditor editor, final String name, final String inspectionId, final RadComponent component) {
306 super(editor, name, component);
307 myInspectionId = inspectionId;
311 if (!myEditor.ensureEditable()) return;
312 myEditor.getRootContainer().suppressInspection(myInspectionId, myComponent);
313 myEditor.refreshAndSave(true);
314 DaemonCodeAnalyzer.getInstance(myEditor.getProject()).restart();
318 private final class MyShowHintRequest implements Runnable{
319 private final QuickFixManager myManager;
321 public MyShowHintRequest(@NotNull final QuickFixManager manager) {
326 myManager.showIntentionHint();