inspection tool window: align bottom indent when has no fixes
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ui / QuickFixPreviewPanelFactory.java
1 /*
2  * Copyright 2000-2016 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.codeInspection.ui;
17
18 import com.intellij.codeInspection.CommonProblemDescriptor;
19 import com.intellij.codeInspection.ex.InspectionToolWrapper;
20 import com.intellij.codeInspection.ex.QuickFixAction;
21 import com.intellij.codeInspection.ui.actions.suppress.SuppressActionWrapper;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.ide.DataManager;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.editor.ex.EditorEx;
29 import com.intellij.openapi.util.Disposer;
30 import com.intellij.ui.IdeBorderFactory;
31 import com.intellij.ui.SimpleColoredComponent;
32 import com.intellij.util.ui.AsyncProcessIcon;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.*;
37 import java.awt.*;
38 import java.util.Arrays;
39 import java.util.stream.Stream;
40
41 /**
42  * @author Dmitry Batkovich
43  */
44 public class QuickFixPreviewPanelFactory {
45   private static final Logger LOG = Logger.getInstance(QuickFixPreviewPanelFactory.class);
46   private static final int MAX_FIX_COUNT = 3;
47
48   @Nullable
49   public static JComponent create(@Nullable EditorEx editor,
50                                   @NotNull InspectionResultsView view) {
51     if (view.isUpdating() && !view.getTree().areDescriptorNodesSelected()) {
52       return new LoadingInProgressPreview(view);
53     }
54     else {
55       final QuickFixReadyPanel panel = new QuickFixReadyPanel(view, editor);
56       return panel.isEmpty() ? null : panel;
57     }
58   }
59
60   private static class QuickFixReadyPanel extends JPanel {
61     @NotNull private final InspectionResultsView myView;
62     private final InspectionToolWrapper myWrapper;
63     private final boolean myEmpty;
64
65     public QuickFixReadyPanel(@NotNull InspectionResultsView view, EditorEx editor) {
66       myView = view;
67       myWrapper = view.getTree().getSelectedToolWrapper();
68       LOG.assertTrue(myWrapper != null);
69       CommonProblemDescriptor[] descriptors = myView.getTree().getSelectedDescriptors();
70       if (editor != null) {
71         new ProblemPreviewEditorPresentation(editor, view.getProject(), descriptors);
72       }
73       QuickFixAction[] fixes = view.getProvider().getQuickFixes(myWrapper, view.getTree());
74       myEmpty = fillPanel(fixes, descriptors);
75     }
76
77     public boolean isEmpty() {
78       return myEmpty;
79     }
80
81     private boolean fillPanel(@Nullable QuickFixAction[] fixes,
82                               CommonProblemDescriptor[] descriptors) {
83       boolean hasFixes = fixes != null && fixes.length != 0;
84       int problemCount = descriptors.length;
85       boolean multipleDescriptors = problemCount > 1;
86       setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
87       boolean hasComponents = false;
88
89       if (multipleDescriptors) {
90         add(getLabel(problemCount));
91         hasComponents = true;
92       }
93
94       final DefaultActionGroup actions = new DefaultActionGroup();
95       if (hasFixes) {
96         actions.addAll(createFixActions(fixes, multipleDescriptors));
97       }
98       final AnAction suppressionCombo = createSuppressionCombo(myView);
99       if (suppressionCombo != null) {
100         actions.add(suppressionCombo);
101       }
102       if (actions.getChildrenCount() != 0) {
103         final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actions, true);
104         final JComponent component = toolbar.getComponent();
105         toolbar.setTargetComponent(this);
106         add(component);
107         hasComponents = true;
108       }
109
110       if (hasComponents) {
111         setBorder(IdeBorderFactory.createEmptyBorder(hasFixes ? 2 : 9, (hasFixes || problemCount > 1) ? 8 : 5, hasFixes ? 0 : 8, 0));
112       }
113       return !hasComponents;
114     }
115
116     @Nullable
117     private static AnAction createSuppressionCombo(InspectionResultsView view) {
118       final AnActionEvent
119         event = AnActionEvent.createFromDataContext(ActionPlaces.CODE_INSPECTION, null, DataManager.getInstance().getDataContext(view));
120       final AnAction[] suppressors = new SuppressActionWrapper().getChildren(event);
121       final Stream<AnAction> suppressActionStream = Arrays.stream(suppressors).filter(s -> {
122         s.update(event);
123         return event.getPresentation().isEnabled();
124       });
125       if (!suppressActionStream.findFirst().isPresent()) {
126         return null;
127       }
128       final ComboBoxAction action = new ComboBoxAction() {
129         {
130           getTemplatePresentation().setText("Suppress");
131           getTemplatePresentation().setEnabledAndVisible(suppressors.length != 0);
132         }
133
134         @NotNull
135         @Override
136         protected DefaultActionGroup createPopupActionGroup(JComponent button) {
137           DefaultActionGroup group = new DefaultCompactActionGroup();
138           group.addAll(suppressors);
139           return group;
140         }
141       };
142       action.setSmallVariant(false);
143       return action;
144     }
145
146     @NotNull
147     private static AnAction[] createFixActions(QuickFixAction[] fixes, boolean multipleDescriptors) {
148       if (fixes.length > MAX_FIX_COUNT) {
149         final ComboBoxAction fixComboBox = new ComboBoxAction() {
150           {
151             getTemplatePresentation().setText("Apply quick fixes" + (multipleDescriptors ? " to all the problems" : ""));
152             getTemplatePresentation().setIcon(AllIcons.Actions.CreateFromUsage);
153             setSmallVariant(false);
154           }
155
156           @NotNull
157           @Override
158           protected DefaultActionGroup createPopupActionGroup(JComponent button) {
159             final DefaultActionGroup actionGroup = new DefaultActionGroup();
160             for (QuickFixAction fix : fixes) {
161               actionGroup.add(fix);
162             }
163             return actionGroup;
164           }
165         };
166         return new AnAction[] {fixComboBox};
167       }
168       return fixes;
169     }
170
171   }
172
173
174   private static class LoadingInProgressPreview extends JPanel implements InspectionTreeLoadingProgressAware {
175     private final InspectionResultsView myView;
176     private SimpleColoredComponent myWaitingLabel;
177
178     private LoadingInProgressPreview(InspectionResultsView view) {
179       myView = view;
180       setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
181       setBorder(IdeBorderFactory.createEmptyBorder(16, 9, 13, 0));
182       AsyncProcessIcon waitingIcon = new AsyncProcessIcon("Inspection preview panel updating...");
183       Disposer.register(this, waitingIcon);
184       myWaitingLabel = getLabel(myView.getTree().getSelectedProblemCount());
185       add(myWaitingLabel);
186       add(waitingIcon);
187     }
188
189     @Override
190     public void updateLoadingProgress() {
191       if (myWaitingLabel != null) {
192         myWaitingLabel.clear();
193         final InspectionTree tree = myView.getTree();
194         appendTextToLabel(myWaitingLabel, tree.getSelectedProblemCount());
195       }
196     }
197
198     @Override
199     public void treeLoaded() {
200       ApplicationManager.getApplication().invokeLater(() -> {
201         if (!myView.isDisposed()) {
202           myView.syncRightPanel();
203         }
204       });
205     }
206
207     @Override
208     public void dispose() {
209     }
210   }
211
212   @NotNull
213   private static SimpleColoredComponent getLabel(int problemsCount) {
214     SimpleColoredComponent label = new SimpleColoredComponent();
215     appendTextToLabel(label, problemsCount);
216     label.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, 2));
217     return label;
218   }
219
220   private static void appendTextToLabel(SimpleColoredComponent label,
221                                         int problemsCount) {
222     label.append(problemsCount + " problems:");
223   }
224 }