0ddf1995966ae3841f6d2e8a386ecc87de7f7a4a
[idea/community.git] / platform / lang-impl / src / com / intellij / profile / codeInspection / ui / SingleInspectionProfilePanel.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
17 package com.intellij.profile.codeInspection.ui;
18
19 import com.intellij.codeHighlighting.HighlightDisplayLevel;
20 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
21 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
22 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
23 import com.intellij.codeInsight.hint.HintUtil;
24 import com.intellij.codeInspection.InspectionsBundle;
25 import com.intellij.codeInspection.ex.*;
26 import com.intellij.icons.AllIcons;
27 import com.intellij.ide.CommonActionsManager;
28 import com.intellij.ide.DefaultTreeExpander;
29 import com.intellij.ide.TreeExpander;
30 import com.intellij.ide.ui.search.SearchUtil;
31 import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
32 import com.intellij.lang.annotation.HighlightSeverity;
33 import com.intellij.openapi.Disposable;
34 import com.intellij.openapi.actionSystem.*;
35 import com.intellij.openapi.application.ApplicationManager;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.editor.colors.TextAttributesKey;
38 import com.intellij.openapi.editor.markup.TextAttributes;
39 import com.intellij.openapi.project.DumbAwareAction;
40 import com.intellij.openapi.project.Project;
41 import com.intellij.openapi.util.Comparing;
42 import com.intellij.openapi.util.Disposer;
43 import com.intellij.openapi.util.JDOMUtil;
44 import com.intellij.openapi.util.text.StringUtil;
45 import com.intellij.profile.codeInspection.BaseInspectionProfileManager;
46 import com.intellij.profile.codeInspection.InspectionProfileManager;
47 import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
48 import com.intellij.profile.codeInspection.ui.filter.InspectionFilterAction;
49 import com.intellij.profile.codeInspection.ui.filter.InspectionsFilter;
50 import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionConfigTreeNode;
51 import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator;
52 import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeRenderer;
53 import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeTable;
54 import com.intellij.profile.codeInspection.ui.table.ScopesAndSeveritiesTable;
55 import com.intellij.psi.search.scope.packageSet.NamedScope;
56 import com.intellij.ui.*;
57 import com.intellij.ui.components.JBLabel;
58 import com.intellij.util.Alarm;
59 import com.intellij.util.containers.ContainerUtil;
60 import com.intellij.util.containers.Convertor;
61 import com.intellij.util.containers.Queue;
62 import com.intellij.util.ui.JBInsets;
63 import com.intellij.util.ui.JBUI;
64 import com.intellij.util.ui.UIUtil;
65 import com.intellij.util.ui.tree.TreeUtil;
66 import gnu.trove.THashMap;
67 import gnu.trove.THashSet;
68 import org.jdom.Element;
69 import org.jetbrains.annotations.NonNls;
70 import org.jetbrains.annotations.NotNull;
71 import org.jetbrains.annotations.Nullable;
72
73 import javax.swing.*;
74 import javax.swing.event.TreeExpansionEvent;
75 import javax.swing.event.TreeExpansionListener;
76 import javax.swing.event.TreeSelectionEvent;
77 import javax.swing.event.TreeSelectionListener;
78 import javax.swing.tree.DefaultTreeModel;
79 import javax.swing.tree.TreeNode;
80 import javax.swing.tree.TreePath;
81 import java.awt.*;
82 import java.io.IOException;
83 import java.io.StringReader;
84 import java.util.*;
85 import java.util.List;
86
87 import com.intellij.util.containers.Queue;
88
89 public class SingleInspectionProfilePanel extends JPanel {
90   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.InspectionToolsPanel");
91   @NonNls private static final String INSPECTION_FILTER_HISTORY = "INSPECTION_FILTER_HISTORY";
92   private static final String UNDER_CONSTRUCTION = InspectionsBundle.message("inspection.tool.description.under.construction.text");
93   @NonNls private static final String EMPTY_HTML = "<html><body></body></html>";
94
95   private static final float DIVIDER_PROPORTION_DEFAULT = 0.5f;
96
97   private final Map<HighlightDisplayKey, ToolDescriptors> myInitialToolDescriptors = new THashMap<>();
98   private final InspectionConfigTreeNode myRoot =
99     new InspectionConfigTreeNode.Group(InspectionsBundle.message("inspection.root.node.title"));
100   private final Alarm myAlarm = new Alarm();
101   private final ProjectInspectionProfileManager myProjectProfileManager;
102   private InspectionProfileModifiableModel myProfile;
103   private JEditorPane myBrowser;
104   private JPanel myOptionsPanel;
105   private JPanel myInspectionProfilePanel = null;
106   private FilterComponent myProfileFilter;
107   private final InspectionsFilter myInspectionsFilter = new InspectionsFilter() {
108     @Override
109     protected void filterChanged() {
110       filterTree(myProfileFilter.getFilter());
111     }
112   };
113   private boolean myModified = false;
114   private InspectionsConfigTreeTable myTreeTable;
115   private TreeExpander myTreeExpander;
116   private boolean myIsInRestore = false;
117
118   private String[] myInitialScopesOrder;
119   private Disposable myDisposable = new Disposable() {
120     @Override
121     public void dispose() {}
122   };
123
124   public SingleInspectionProfilePanel(@NotNull ProjectInspectionProfileManager projectProfileManager,
125                                       @NotNull InspectionProfileModifiableModel profile) {
126     super(new BorderLayout());
127     myProjectProfileManager = projectProfileManager;
128     myProfile = profile;
129   }
130
131   public Map<HighlightDisplayKey, ToolDescriptors> getInitialToolDescriptors() {
132     return myInitialToolDescriptors;
133   }
134
135   private static VisibleTreeState getExpandedNodes(InspectionProfileImpl profile) {
136     if (profile.isProjectLevel()) {
137       return ProjectInspectionProfilesVisibleTreeState.getInstance(((ProjectInspectionProfileManager)profile.getProfileManager()).getProject()).getVisibleTreeState(profile);
138     }
139     else {
140       return AppInspectionProfilesVisibleTreeState.getInstance().getVisibleTreeState(profile);
141     }
142   }
143
144   private static InspectionConfigTreeNode findGroupNodeByPath(@NotNull String[] path, int idx, @NotNull InspectionConfigTreeNode node) {
145     if (path.length == idx) {
146       return node;
147     }
148
149     final String currentKey = path[idx];
150     for (int i = 0; i < node.getChildCount(); i++) {
151       final InspectionConfigTreeNode currentNode = (InspectionConfigTreeNode)node.getChildAt(i);
152       if (Comparing.equal(currentNode.getGroupName(), currentKey)) {
153         return findGroupNodeByPath(path, ++idx, currentNode);
154       }
155     }
156
157     return null;
158   }
159
160   @Nullable
161   private static InspectionConfigTreeNode findNodeByKey(String name, InspectionConfigTreeNode root) {
162     for (int i = 0; i < root.getChildCount(); i++) {
163       final InspectionConfigTreeNode child = (InspectionConfigTreeNode)root.getChildAt(i);
164       final Descriptor descriptor = child.getDefaultDescriptor();
165       if (descriptor != null) {
166         if (descriptor.getKey().toString().equals(name)) {
167           return child;
168         }
169       }
170       else {
171         final InspectionConfigTreeNode node = findNodeByKey(name, child);
172         if (node != null) return node;
173       }
174     }
175     return null;
176   }
177
178   public static String renderSeverity(HighlightSeverity severity) {
179     if (HighlightSeverity.INFORMATION.equals(severity)) return "No highlighting, only fix"; //todo severity presentation
180     return StringUtil.capitalizeWords(severity.getName().toLowerCase(Locale.US), true);
181   }
182
183   private static void updateUpHierarchy(final InspectionConfigTreeNode parent) {
184     if (parent != null) {
185       parent.dropCache();
186       updateUpHierarchy((InspectionConfigTreeNode)parent.getParent());
187     }
188   }
189
190   private static boolean isDescriptorAccepted(Descriptor descriptor,
191                                               @NonNls String filter,
192                                               final boolean forceInclude,
193                                               final List<Set<String>> keySetList, final Set<String> quoted) {
194     filter = filter.toLowerCase();
195     if (StringUtil.containsIgnoreCase(descriptor.getText(), filter)) {
196       return true;
197     }
198     final String[] groupPath = descriptor.getGroup();
199     for (String group : groupPath) {
200       if (StringUtil.containsIgnoreCase(group, filter)) {
201         return true;
202       }
203     }
204     for (String stripped : quoted) {
205       if (StringUtil.containsIgnoreCase(descriptor.getText(),stripped)) {
206         return true;
207       }
208       for (String group : groupPath) {
209         if (StringUtil.containsIgnoreCase(group,stripped)) {
210           return true;
211         }
212       }
213       final String description = descriptor.getToolWrapper().loadDescription();
214       if (description != null && StringUtil.containsIgnoreCase(description.toLowerCase(Locale.US), stripped)) {
215         if (!forceInclude) return true;
216       } else if (forceInclude) return false;
217     }
218     for (Set<String> keySet : keySetList) {
219       if (keySet.contains(descriptor.getKey().toString())) {
220         if (!forceInclude) {
221           return true;
222         }
223       }
224       else {
225         if (forceInclude) {
226           return false;
227         }
228       }
229     }
230     return forceInclude;
231   }
232
233   private static void setConfigPanel(final JPanel configPanelAnchor, final ScopeToolState state) {
234     configPanelAnchor.removeAll();
235     final JComponent additionalConfigPanel = state.getAdditionalConfigPanel();
236     if (additionalConfigPanel != null) {
237       // assume that the panel does not need scrolling if it already contains a scrollable content
238       if (UIUtil.hasScrollPane(additionalConfigPanel)) {
239         configPanelAnchor.add(additionalConfigPanel);
240       }
241       else {
242         configPanelAnchor.add(ScrollPaneFactory.createScrollPane(additionalConfigPanel, SideBorder.NONE));
243       }
244     }
245     UIUtil.setEnabled(configPanelAnchor, state.isEnabled(), true);
246   }
247
248   private static InspectionConfigTreeNode getGroupNode(InspectionConfigTreeNode root, String[] groupPath) {
249     InspectionConfigTreeNode currentRoot = root;
250     for (final String group : groupPath) {
251       currentRoot = getGroupNode(currentRoot, group);
252     }
253     return currentRoot;
254   }
255
256   private static InspectionConfigTreeNode getGroupNode(InspectionConfigTreeNode root, String group) {
257     final int childCount = root.getChildCount();
258     for (int i = 0; i < childCount; i++) {
259       InspectionConfigTreeNode child = (InspectionConfigTreeNode)root.getChildAt(i);
260       if (group.equals(child.getUserObject())) {
261         return child;
262       }
263     }
264     InspectionConfigTreeNode child = new InspectionConfigTreeNode.Group(group);
265     root.add(child);
266     return child;
267   }
268
269   private static void copyUsedSeveritiesIfUndefined(InspectionProfileImpl selectedProfile, BaseInspectionProfileManager profileManager) {
270     final SeverityRegistrar registrar = profileManager.getSeverityRegistrar();
271     final Set<HighlightSeverity> severities = selectedProfile.getUsedSeverities();
272     for (Iterator<HighlightSeverity> iterator = severities.iterator(); iterator.hasNext();) {
273       HighlightSeverity severity = iterator.next();
274       if (registrar.isSeverityValid(severity.getName())) {
275         iterator.remove();
276       }
277     }
278
279     if (!severities.isEmpty()) {
280       final SeverityRegistrar oppositeRegister = selectedProfile.getProfileManager().getSeverityRegistrar();
281       for (HighlightSeverity severity : severities) {
282         final TextAttributesKey attributesKey = TextAttributesKey.find(severity.getName());
283         final TextAttributes textAttributes = oppositeRegister.getTextAttributesBySeverity(severity);
284         if (textAttributes == null) {
285           continue;
286         }
287         HighlightInfoType.HighlightInfoTypeImpl info = new HighlightInfoType.HighlightInfoTypeImpl(severity, attributesKey);
288         registrar.registerSeverity(new SeverityRegistrar.SeverityBasedTextAttributes(textAttributes.clone(), info),
289                                    textAttributes.getErrorStripeColor());
290       }
291     }
292   }
293
294   private void initUI() {
295     myInspectionProfilePanel = createInspectionProfileSettingsPanel();
296     add(myInspectionProfilePanel, BorderLayout.CENTER);
297     UserActivityWatcher userActivityWatcher = new UserActivityWatcher();
298     userActivityWatcher.addUserActivityListener(new UserActivityListener() {
299       @Override
300       public void stateChanged() {
301         //invoke after all other listeners
302         ApplicationManager.getApplication().invokeLater(() -> {
303           if (myProfile == null) return; //panel was disposed
304           updateProperSettingsForSelection();
305           wereToolSettingsModified();
306         });
307       }
308     });
309     userActivityWatcher.register(myOptionsPanel);
310     updateSelectedProfileState();
311     reset();
312   }
313
314   private void updateSelectedProfileState() {
315     if (myProfile == null) return;
316     restoreTreeState();
317     repaintTableData();
318     updateSelection();
319   }
320
321   public void updateSelection() {
322     if (myTreeTable != null) {
323       final TreePath selectionPath = myTreeTable.getTree().getSelectionPath();
324       if (selectionPath != null) {
325         TreeUtil.selectNode(myTreeTable.getTree(), (TreeNode) selectionPath.getLastPathComponent());
326         final int rowForPath = myTreeTable.getTree().getRowForPath(selectionPath);
327         TableUtil.selectRows(myTreeTable, new int[]{rowForPath});
328         scrollToCenter();
329       }
330     }
331   }
332
333   private void loadDescriptorsConfigs(boolean onlyModified) {
334     for (ToolDescriptors toolDescriptors : myInitialToolDescriptors.values()) {
335       loadDescriptorConfig(toolDescriptors.getDefaultDescriptor(), onlyModified);
336       for (Descriptor descriptor : toolDescriptors.getNonDefaultDescriptors()) {
337         loadDescriptorConfig(descriptor, onlyModified);
338       }
339     }
340   }
341
342   private void loadDescriptorConfig(Descriptor descriptor, boolean ifModifier) {
343     if (!ifModifier || myProfile.isProperSetting(descriptor.getKey().toString())) {
344       descriptor.loadConfig();
345     }
346   }
347
348   private void wereToolSettingsModified() {
349     for (final ToolDescriptors toolDescriptor : myInitialToolDescriptors.values()) {
350       Descriptor desc = toolDescriptor.getDefaultDescriptor();
351       if (wereToolSettingsModified(desc, true)) return;
352       List<Descriptor> descriptors = toolDescriptor.getNonDefaultDescriptors();
353       for (Descriptor descriptor : descriptors) {
354         if (wereToolSettingsModified(descriptor, false)) return;
355       }
356     }
357     myModified = false;
358   }
359
360   private boolean wereToolSettingsModified(Descriptor descriptor, boolean isDefault) {
361     if (!myProfile.isToolEnabled(descriptor.getKey(), descriptor.getScope(), myProjectProfileManager.getProject())) {
362       return false;
363     }
364     Element oldConfig = descriptor.getConfig();
365     if (oldConfig == null) return false;
366
367     ScopeToolState state = null;
368     if (isDefault) {
369       state = myProfile.getToolDefaultState(descriptor.getKey().toString(), myProjectProfileManager.getProject());
370     } else {
371       for (ScopeToolState candidate : myProfile.getNonDefaultTools(descriptor.getKey().toString(), myProjectProfileManager.getProject())) {
372         final String scope = descriptor.getScopeName();
373         if (Comparing.equal(candidate.getScopeName(), scope)) {
374           state = candidate;
375           break;
376         }
377       }
378     }
379
380     if (state == null) {
381       return true;
382     }
383
384     Element newConfig = Descriptor.createConfigElement(state.getTool());
385     if (!JDOMUtil.areElementsEqual(oldConfig, newConfig)) {
386       myAlarm.cancelAllRequests();
387       myAlarm.addRequest(() -> myTreeTable.repaint(), 300);
388       myModified = true;
389       return true;
390     }
391     return false;
392   }
393
394   private void updateProperSettingsForSelection() {
395     final TreePath selectionPath = myTreeTable.getTree().getSelectionPath();
396     if (selectionPath != null) {
397       InspectionConfigTreeNode node = (InspectionConfigTreeNode)selectionPath.getLastPathComponent();
398       final Descriptor descriptor = node.getDefaultDescriptor();
399       if (descriptor != null) {
400         final boolean properSetting = myProfile.isProperSetting(descriptor.getKey().toString());
401         if (node.isProperSetting() != properSetting) {
402           myAlarm.cancelAllRequests();
403           myAlarm.addRequest(() -> myTreeTable.repaint(), 300);
404           node.dropCache();
405           updateUpHierarchy((InspectionConfigTreeNode)node.getParent());
406         }
407       }
408     }
409   }
410
411   private void initToolStates() {
412     InspectionProfileModifiableModel profile = myProfile;
413     if (profile == null) {
414       return;
415     }
416
417     myInitialToolDescriptors.clear();
418     final Project project = myProjectProfileManager.getProject();
419     for (final ScopeToolState state : profile.getDefaultStates(myProjectProfileManager.getProject())) {
420       if (!accept(state.getTool())) {
421         continue;
422       }
423       ToolDescriptors descriptors = ToolDescriptors.fromScopeToolState(state, profile, project);
424       myInitialToolDescriptors.put(descriptors.getDefaultDescriptor().getKey(), descriptors);
425     }
426     myInitialScopesOrder = myProfile.getScopesOrder();
427   }
428
429   protected boolean accept(InspectionToolWrapper entry) {
430     return entry.getDefaultLevel() != HighlightDisplayLevel.NON_SWITCHABLE_ERROR;
431   }
432
433   private void postProcessModification() {
434     wereToolSettingsModified();
435     //resetup configs
436     for (ScopeToolState state : myProfile.getAllTools(myProjectProfileManager.getProject())) {
437       state.resetConfigPanel();
438     }
439     fillTreeData(myProfileFilter.getFilter(), true);
440     repaintTableData();
441     updateOptionsAndDescriptionPanel(myTreeTable.getTree().getSelectionPaths());
442   }
443
444   public void setFilter(String filter) {
445     myProfileFilter.setFilter(filter);
446   }
447
448   private void filterTree(@Nullable String filter) {
449     if (myTreeTable != null) {
450       getExpandedNodes(myProfile).saveVisibleState(myTreeTable.getTree());
451       fillTreeData(filter, true);
452       reloadModel();
453       restoreTreeState();
454       if (myTreeTable.getTree().getSelectionPath() == null) {
455         TreeUtil.selectFirstNode(myTreeTable.getTree());
456       }
457     }
458   }
459
460   private void filterTree() {
461     filterTree(myProfileFilter != null ? myProfileFilter.getFilter() : null);
462   }
463
464   private void reloadModel() {
465     try {
466       myIsInRestore = true;
467       ((DefaultTreeModel)myTreeTable.getTree().getModel()).reload();
468     }
469     finally {
470       myIsInRestore = false;
471     }
472
473   }
474
475   private void restoreTreeState() {
476
477     try {
478       myIsInRestore = true;
479       getExpandedNodes(myProfile).restoreVisibleState(myTreeTable.getTree());
480     }
481     finally {
482       myIsInRestore = false;
483     }
484   }
485
486   private ActionToolbar createTreeToolbarPanel() {
487     final CommonActionsManager actionManager = CommonActionsManager.getInstance();
488
489     DefaultActionGroup actions = new DefaultActionGroup();
490
491     actions.add(new InspectionFilterAction(myProfile, myInspectionsFilter, myProjectProfileManager.getProject(), myProfileFilter));
492     actions.addSeparator();
493
494     actions.add(actionManager.createExpandAllAction(myTreeExpander, myTreeTable));
495     actions.add(actionManager.createCollapseAllAction(myTreeExpander, myTreeTable));
496     actions.add(new DumbAwareAction("Reset to Empty", "Reset to empty", AllIcons.Actions.Reset_to_empty){
497
498       @Override
499       public void update(@NotNull AnActionEvent e) {
500         e.getPresentation().setEnabled(myProfile != null && myProfile.isExecutable(myProjectProfileManager.getProject()));
501       }
502
503       @Override
504       public void actionPerformed(@NotNull AnActionEvent e) {
505         myProfile.resetToEmpty(e.getProject());
506         loadDescriptorsConfigs(false);
507         postProcessModification();
508       }
509     });
510
511     actions.add(new AdvancedSettingsAction(myProjectProfileManager.getProject(), myRoot) {
512       @Override
513       protected InspectionProfileModifiableModel getInspectionProfile() {
514         return myProfile;
515       }
516
517       @Override
518       protected void postProcessModification() {
519         loadDescriptorsConfigs(true);
520         SingleInspectionProfilePanel.this.postProcessModification();
521       }
522     });
523
524
525     final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actions, true);
526     actionToolbar.setTargetComponent(this);
527     return actionToolbar;
528   }
529
530   private void repaintTableData() {
531     if (myTreeTable != null) {
532       getExpandedNodes(myProfile).saveVisibleState(myTreeTable.getTree());
533       reloadModel();
534       restoreTreeState();
535     }
536   }
537
538   public void selectInspectionTool(String name) {
539     selectNode(findNodeByKey(name, myRoot));
540   }
541
542   public void selectInspectionGroup(String[] path) {
543     final InspectionConfigTreeNode node = findGroupNodeByPath(path, 0, myRoot);
544     selectNode(node);
545     if (node != null) {
546       myTreeTable.getTree().expandPath(new TreePath(node.getPath()));
547     }
548   }
549
550   private void selectNode(InspectionConfigTreeNode node) {
551     if (node != null) {
552       TreeUtil.selectNode(myTreeTable.getTree(), node);
553       final int rowForPath = myTreeTable.getTree().getRowForPath(new TreePath(node.getPath()));
554       TableUtil.selectRows(myTreeTable, new int[]{rowForPath});
555       scrollToCenter();
556     }
557   }
558
559   private void scrollToCenter() {
560     ListSelectionModel selectionModel = myTreeTable.getSelectionModel();
561     int maxSelectionIndex = selectionModel.getMaxSelectionIndex();
562     final int maxColumnSelectionIndex = Math.max(0, myTreeTable.getColumnModel().getSelectionModel().getMinSelectionIndex());
563     Rectangle maxCellRect = myTreeTable.getCellRect(maxSelectionIndex, maxColumnSelectionIndex, false);
564
565     final Point selectPoint = maxCellRect.getLocation();
566     final int allHeight = myTreeTable.getVisibleRect().height;
567     myTreeTable.scrollRectToVisible(new Rectangle(new Point(0, Math.max(0, selectPoint.y - allHeight / 2)), new Dimension(0, allHeight)));
568   }
569
570   private JScrollPane initTreeScrollPane() {
571     fillTreeData(null, true);
572
573     final InspectionsConfigTreeRenderer renderer = new InspectionsConfigTreeRenderer(){
574       @Override
575       protected String getFilter() {
576         return myProfileFilter != null ? myProfileFilter.getFilter() : null;
577       }
578     };
579     myTreeTable = InspectionsConfigTreeTable.create(new InspectionsConfigTreeTable.InspectionsConfigTreeTableSettings(myRoot, myProjectProfileManager.getProject()) {
580       @Override
581       protected void onChanged(final InspectionConfigTreeNode node) {
582         updateUpHierarchy((InspectionConfigTreeNode)node.getParent());
583       }
584
585       @Override
586       public void updateRightPanel() {
587         updateOptionsAndDescriptionPanel();
588       }
589
590       @Override
591       public InspectionProfileImpl getInspectionProfile() {
592         return myProfile;
593       }
594     }, myDisposable);
595     myTreeTable.setTreeCellRenderer(renderer);
596     myTreeTable.setRootVisible(false);
597     UIUtil.setLineStyleAngled(myTreeTable.getTree());
598     TreeUtil.installActions(myTreeTable.getTree());
599
600
601     myTreeTable.getTree().addTreeSelectionListener(new TreeSelectionListener() {
602       @Override
603       public void valueChanged(TreeSelectionEvent e) {
604         if (myTreeTable.getTree().getSelectionPaths() != null) {
605           updateOptionsAndDescriptionPanel(myTreeTable.getTree().getSelectionPaths());
606         }
607         else {
608           initOptionsAndDescriptionPanel();
609         }
610
611         if (!myIsInRestore) {
612           InspectionProfileModifiableModel selected = myProfile;
613           if (selected != null) {
614             InspectionProfileImpl baseProfile = selected.getSource();
615             getExpandedNodes(baseProfile).setSelectionPaths(myTreeTable.getTree().getSelectionPaths());
616             getExpandedNodes(selected).setSelectionPaths(myTreeTable.getTree().getSelectionPaths());
617           }
618         }
619       }
620     });
621
622
623     myTreeTable.addMouseListener(new PopupHandler() {
624       @Override
625       public void invokePopup(Component comp, int x, int y) {
626         final int[] selectionRows = myTreeTable.getTree().getSelectionRows();
627         if (selectionRows != null &&
628             myTreeTable.getTree().getPathForLocation(x, y) != null &&
629             Arrays.binarySearch(selectionRows, myTreeTable.getTree().getRowForLocation(x, y)) > -1) {
630           compoundPopup().show(comp, x, y);
631         }
632       }
633     });
634
635
636     new TreeSpeedSearch(myTreeTable.getTree(), new Convertor<TreePath, String>() {
637       @Override
638       public String convert(TreePath o) {
639         final InspectionConfigTreeNode node = (InspectionConfigTreeNode)o.getLastPathComponent();
640         final Descriptor descriptor = node.getDefaultDescriptor();
641         return InspectionsConfigTreeComparator.getDisplayTextToSort(descriptor != null ? descriptor.getText() : node.getGroupName());
642       }
643     });
644
645
646     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTreeTable);
647     myTreeTable.getTree().setShowsRootHandles(true);
648     scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
649     scrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM + SideBorder.LEFT + SideBorder.TOP));
650     TreeUtil.collapseAll(myTreeTable.getTree(), 1);
651
652     myTreeTable.getTree().addTreeExpansionListener(new TreeExpansionListener() {
653
654
655       @Override
656       public void treeCollapsed(TreeExpansionEvent event) {
657         InspectionProfileModifiableModel selected = myProfile;
658         getExpandedNodes(selected.getSource()).saveVisibleState(myTreeTable.getTree());
659         getExpandedNodes(selected).saveVisibleState(myTreeTable.getTree());
660       }
661
662       @Override
663       public void treeExpanded(TreeExpansionEvent event) {
664         InspectionProfileModifiableModel selected = myProfile;
665         if (selected != null) {
666           final InspectionConfigTreeNode node = (InspectionConfigTreeNode)event.getPath().getLastPathComponent();
667           getExpandedNodes(selected.getSource()).expandNode(node);
668           getExpandedNodes(selected).expandNode(node);
669         }
670       }
671     });
672
673     myTreeExpander = new DefaultTreeExpander(myTreeTable.getTree()) {
674       @Override
675       public boolean canExpand() {
676         return myTreeTable.isShowing();
677       }
678
679       @Override
680       public boolean canCollapse() {
681         return myTreeTable.isShowing();
682       }
683     };
684     myProfileFilter = new MyFilterComponent();
685
686     return scrollPane;
687   }
688
689   private JPopupMenu compoundPopup() {
690     final DefaultActionGroup group = new DefaultActionGroup();
691     final SeverityRegistrar severityRegistrar = myProfile.getProfileManager().getOwnSeverityRegistrar();
692     for (HighlightSeverity severity : LevelChooserAction.getSeverities(severityRegistrar, includeDoNotShow())) {
693       final HighlightDisplayLevel level = HighlightDisplayLevel.find(severity);
694       group.add(new AnAction(renderSeverity(severity), renderSeverity(severity), level.getIcon()) {
695         @Override
696         public void actionPerformed(@NotNull AnActionEvent e) {
697           setNewHighlightingLevel(level);
698         }
699
700         @Override
701         public boolean isDumbAware() {
702           return true;
703         }
704       });
705     }
706     group.add(Separator.getInstance());
707     ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group);
708     return menu.getComponent();
709   }
710
711   private boolean includeDoNotShow() {
712     final TreePath[] paths = myTreeTable.getTree().getSelectionPaths();
713     if (paths == null) return true;
714     return includeDoNotShow(InspectionsAggregationUtil.getInspectionsNodes(paths));
715   }
716
717   private boolean includeDoNotShow(List<InspectionConfigTreeNode> nodes) {
718     final Project project = myProjectProfileManager.getProject();
719     return !nodes.stream()
720       .filter(node -> myProfile.getToolDefaultState(node.getKey().toString(), project).getTool() instanceof GlobalInspectionToolWrapper)
721       .findFirst()
722       .isPresent();
723   }
724
725   private void fillTreeData(@Nullable String filter, boolean forceInclude) {
726     if (myProfile == null) return;
727     myRoot.removeAllChildren();
728     myRoot.dropCache();
729     List<Set<String>> keySetList = new ArrayList<>();
730     final Set<String> quoted = new HashSet<>();
731     if (filter != null && !filter.isEmpty()) {
732       keySetList.addAll(SearchUtil.findKeys(filter, quoted));
733     }
734     Project project = myProjectProfileManager.getProject();
735     final boolean emptyFilter = myInspectionsFilter.isEmptyFilter();
736     for (ToolDescriptors toolDescriptors : myInitialToolDescriptors.values()) {
737       final Descriptor descriptor = toolDescriptors.getDefaultDescriptor();
738       if (filter != null && !filter.isEmpty() && !isDescriptorAccepted(descriptor, filter, forceInclude, keySetList, quoted)) {
739         continue;
740       }
741       final InspectionConfigTreeNode node = new InspectionConfigTreeNode.Tool(toolDescriptors.getDefaultDescriptor().getKey(), this);
742       if (!emptyFilter && !myInspectionsFilter.matches(
743         myProfile.getTools(toolDescriptors.getDefaultDescriptor().getKey().toString(), project), node)) {
744         continue;
745       }
746       getGroupNode(myRoot, toolDescriptors.getDefaultDescriptor().getGroup()).add(node);
747       myRoot.dropCache();
748     }
749     if (filter != null && forceInclude && myRoot.getChildCount() == 0) {
750       final Set<String> filters = SearchableOptionsRegistrar.getInstance().getProcessedWords(filter);
751       if (filters.size() > 1 || !quoted.isEmpty()) {
752         fillTreeData(filter, false);
753       }
754     }
755     TreeUtil.sort(myRoot, new InspectionsConfigTreeComparator());
756   }
757
758   // TODO 134099: see IntentionDescriptionPanel#readHTML
759   public static boolean readHTML(JEditorPane browser, String text) {
760     try {
761       browser.read(new StringReader(text), null);
762       return true;
763     }
764     catch (IOException ignored) {
765       return false;
766     }
767   }
768
769   // TODO 134099: see IntentionDescriptionPanel#setHTML
770   public static String toHTML(JEditorPane browser, String text, boolean miniFontSize) {
771     final HintHint hintHint = new HintHint(browser, new Point(0, 0));
772     hintHint.setFont(miniFontSize ? UIUtil.getLabelFont(UIUtil.FontSize.SMALL) : UIUtil.getLabelFont());
773     return HintUtil.prepareHintText(text, hintHint);
774   }
775
776   private void updateOptionsAndDescriptionPanel(final TreePath... paths) {
777     if (myProfile == null || paths == null || paths.length == 0) {
778       return;
779     }
780     final TreePath path = paths[0];
781     if (path == null) return;
782     final List<InspectionConfigTreeNode> nodes = InspectionsAggregationUtil.getInspectionsNodes(paths);
783     if (!nodes.isEmpty()) {
784       final InspectionConfigTreeNode singleNode = paths.length == 1 && ((InspectionConfigTreeNode)paths[0].getLastPathComponent()).getDefaultDescriptor() != null
785                                                   ? ContainerUtil.getFirstItem(nodes) : null;
786       if (singleNode != null) {
787         final Descriptor descriptor = singleNode.getDefaultDescriptor();
788         LOG.assertTrue(descriptor != null);
789         if (descriptor.loadDescription() != null) {
790           // need this in order to correctly load plugin-supplied descriptions
791           final Descriptor defaultDescriptor = singleNode.getDefaultDescriptor();
792           final String description = defaultDescriptor.loadDescription();
793           try {
794             if (!readHTML(myBrowser, SearchUtil.markup(toHTML(myBrowser, description, false), myProfileFilter.getFilter()))) {
795               readHTML(myBrowser, toHTML(myBrowser, "<b>" + UNDER_CONSTRUCTION + "</b>", false));
796             }
797           }
798           catch (Throwable t) {
799             LOG.error("Failed to load description for: " +
800                       defaultDescriptor.getToolWrapper().getTool().getClass() +
801                       "; description: " +
802                       description, t);
803           }
804
805         }
806         else {
807           readHTML(myBrowser, toHTML(myBrowser, "Can't find inspection description.", false));
808         }
809       }
810       else {
811         readHTML(myBrowser, toHTML(myBrowser, "Multiple inspections are selected. You can edit them as a single inspection.", false));
812       }
813
814       myOptionsPanel.removeAll();
815       final Project project = myProjectProfileManager.getProject();
816       final JPanel severityPanel = new JPanel(new GridBagLayout());
817       final JPanel configPanelAnchor = new JPanel(new GridLayout());
818
819       final Set<String> scopesNames = new THashSet<>();
820       for (final InspectionConfigTreeNode node : nodes) {
821         final List<ScopeToolState> nonDefaultTools = myProfile.getNonDefaultTools(node.getDefaultDescriptor().getKey().toString(), project);
822         for (final ScopeToolState tool : nonDefaultTools) {
823           scopesNames.add(tool.getScopeName());
824         }
825       }
826
827       final double severityPanelWeightY;
828       if (scopesNames.isEmpty()) {
829
830         final LevelChooserAction severityLevelChooser =
831           new LevelChooserAction(myProfile.getProfileManager().getOwnSeverityRegistrar(),
832                                  includeDoNotShow(nodes)) {
833             @Override
834             protected void onChosen(final HighlightSeverity severity) {
835               final HighlightDisplayLevel level = HighlightDisplayLevel.find(severity);
836               for (final InspectionConfigTreeNode node : nodes) {
837                 final HighlightDisplayKey key = node.getDefaultDescriptor().getKey();
838                 final NamedScope scope = node.getDefaultDescriptor().getScope();
839                 final boolean toUpdate = myProfile.getErrorLevel(key, scope, project) != level;
840                 myProfile.setErrorLevel(key, level, null, project);
841                 if (toUpdate) node.dropCache();
842               }
843               myTreeTable.updateUI();
844             }
845           };
846         final HighlightSeverity severity =
847           ScopesAndSeveritiesTable.getSeverity(ContainerUtil.map(nodes, node -> node.getDefaultDescriptor().getState()));
848         severityLevelChooser.setChosen(severity);
849
850         final ScopesChooser scopesChooser = new ScopesChooser(ContainerUtil.map(nodes, node -> node.getDefaultDescriptor()), myProfile, project, null) {
851           @Override
852           protected void onScopesOrderChanged() {
853             myTreeTable.updateUI();
854             updateOptionsAndDescriptionPanel();
855           }
856
857           @Override
858           protected void onScopeAdded() {
859             myTreeTable.updateUI();
860             updateOptionsAndDescriptionPanel();
861           }
862         };
863
864         severityPanel.add(new JLabel(InspectionsBundle.message("inspection.severity")),
865                           new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL,
866                                                  JBUI.insets(10, 0), 0, 0));
867         final JComponent severityLevelChooserComponent = severityLevelChooser.createCustomComponent(severityLevelChooser.getTemplatePresentation());
868         severityPanel.add(severityLevelChooserComponent,
869                           new GridBagConstraints(1, 0, 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH,
870                                                  JBUI.insets(10, 0), 0, 0));
871         final JComponent scopesChooserComponent = scopesChooser.createCustomComponent(scopesChooser.getTemplatePresentation());
872         severityPanel.add(scopesChooserComponent,
873                           new GridBagConstraints(2, 0, 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH,
874                                                  JBUI.insets(10, 0), 0, 0));
875         final JLabel label = new JLabel("", SwingConstants.RIGHT);
876         severityPanel.add(label,
877                           new GridBagConstraints(3, 0, 1, 1, 1, 0,
878                                                  GridBagConstraints.EAST,
879                                                  GridBagConstraints.BOTH,
880                                                  JBUI.insets(2, 0), 0, 0));
881         severityPanelWeightY = 0.0;
882         if (singleNode != null) {
883           setConfigPanel(configPanelAnchor, myProfile.getToolDefaultState(singleNode.getDefaultDescriptor().getKey().toString(),
884                                                                                   project));
885         }
886       }
887       else {
888         if (singleNode != null) {
889           for (final Descriptor descriptor : singleNode.getDescriptors().getNonDefaultDescriptors()) {
890             descriptor.loadConfig();
891           }
892         }
893         final JTable scopesAndScopesAndSeveritiesTable =
894           new ScopesAndSeveritiesTable(new ScopesAndSeveritiesTable.TableSettings(nodes, myProfile, project) {
895             @Override
896             protected void onScopeChosen(@NotNull final ScopeToolState state) {
897               setConfigPanel(configPanelAnchor, state);
898               configPanelAnchor.revalidate();
899               configPanelAnchor.repaint();
900             }
901
902             @Override
903             protected void onSettingsChanged() {
904               update(false);
905             }
906
907             @Override
908             protected void onScopeAdded() {
909               update(true);
910             }
911
912             @Override
913             protected void onScopesOrderChanged() {
914               update(true);
915             }
916
917             @Override
918             protected void onScopeRemoved(final int scopesCount) {
919               update(scopesCount == 1);
920             }
921
922             private void update(final boolean updateOptionsAndDescriptionPanel) {
923               Queue<InspectionConfigTreeNode> q = new Queue<>(nodes.size());
924               for (InspectionConfigTreeNode node : nodes) {
925                 q.addLast(node);
926               }
927               while (!q.isEmpty()) {
928                 final InspectionConfigTreeNode inspectionConfigTreeNode = q.pullFirst();
929                 inspectionConfigTreeNode.dropCache();
930                 final TreeNode parent = inspectionConfigTreeNode.getParent();
931                 if (parent != null && parent.getParent() != null) {
932                   q.addLast((InspectionConfigTreeNode)parent);
933                 }
934               }
935
936               myTreeTable.updateUI();
937               if (updateOptionsAndDescriptionPanel) {
938                 updateOptionsAndDescriptionPanel();
939               }
940             }
941           });
942
943         final ToolbarDecorator wrappedTable = ToolbarDecorator.createDecorator(scopesAndScopesAndSeveritiesTable).disableUpDownActions().setRemoveActionUpdater(
944           new AnActionButtonUpdater() {
945             @Override
946             public boolean isEnabled(AnActionEvent e) {
947               final int selectedRow = scopesAndScopesAndSeveritiesTable.getSelectedRow();
948               final int rowCount = scopesAndScopesAndSeveritiesTable.getRowCount();
949               return rowCount - 1 != selectedRow;
950             }
951           });
952         final JPanel panel = wrappedTable.createPanel();
953         panel.setMinimumSize(new Dimension(getMinimumSize().width, 3 * scopesAndScopesAndSeveritiesTable.getRowHeight()));
954         severityPanel.add(new JBLabel("Severity by Scope"),
955                           new GridBagConstraints(0, 0, 1, 1, 1.0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
956                                                  JBUI.insets(5, 0, 2, 10), 0, 0));
957         severityPanel.add(panel, new GridBagConstraints(0, 1, 1, 1, 0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
958                                                         JBUI.insets(0, 0, 0, 0), 0, 0));
959         severityPanelWeightY = 0.3;
960       }
961       myOptionsPanel.add(severityPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, severityPanelWeightY, GridBagConstraints.WEST, GridBagConstraints.BOTH,
962                                                                JBUI.insets(0, 2, 0, 0), 0, 0));
963       if (configPanelAnchor.getComponentCount() != 0) {
964         configPanelAnchor.setBorder(IdeBorderFactory.createTitledBorder("Options", false, new JBInsets(7, 0, 0, 0)));
965       }
966       GuiUtils.enableChildren(myOptionsPanel, isThoughOneNodeEnabled(nodes));
967       if (configPanelAnchor.getComponentCount() != 0 || scopesNames.isEmpty()) {
968         myOptionsPanel.add(configPanelAnchor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH,
969                                                                      JBUI.insets(0, 2, 0, 0), 0, 0));
970       }
971       myOptionsPanel.revalidate();
972     }
973     else {
974       initOptionsAndDescriptionPanel();
975     }
976     myOptionsPanel.repaint();
977   }
978
979   private boolean isThoughOneNodeEnabled(final List<InspectionConfigTreeNode> nodes) {
980     final Project project = myProjectProfileManager.getProject();
981     for (final InspectionConfigTreeNode node : nodes) {
982       final String toolId = node.getDefaultDescriptor().getKey().toString();
983       if (myProfile.getTools(toolId, project).isEnabled()) {
984         return true;
985       }
986     }
987     return false;
988   }
989
990   private void updateOptionsAndDescriptionPanel() {
991     final TreePath[] paths = myTreeTable.getTree().getSelectionPaths();
992     if (paths != null) {
993       updateOptionsAndDescriptionPanel(paths);
994     } else {
995       initOptionsAndDescriptionPanel();
996     }
997   }
998
999   private void initOptionsAndDescriptionPanel() {
1000     myOptionsPanel.removeAll();
1001     readHTML(myBrowser, EMPTY_HTML);
1002     myOptionsPanel.validate();
1003     myOptionsPanel.repaint();
1004   }
1005
1006   public InspectionProfileModifiableModel getProfile() {
1007     return myProfile;
1008   }
1009
1010   private void setProfile(InspectionProfileModifiableModel modifiableModel) {
1011     if (myProfile == modifiableModel) {
1012       return;
1013     }
1014     myProfile = modifiableModel;
1015     initToolStates();
1016   }
1017
1018   @Override
1019   public Dimension getPreferredSize() {
1020     return new Dimension(700, 500);
1021   }
1022
1023   public void disposeUI() {
1024     if (myInspectionProfilePanel == null) {
1025       return;
1026     }
1027     myAlarm.cancelAllRequests();
1028     myProfileFilter.dispose();
1029     if (myProfile != null) {
1030       for (ScopeToolState state : myProfile.getAllTools(myProjectProfileManager.getProject())) {
1031         state.resetConfigPanel();
1032       }
1033     }
1034     myProfile = null;
1035     Disposer.dispose(myDisposable);
1036     myDisposable = null;
1037   }
1038
1039   private JPanel createInspectionProfileSettingsPanel() {
1040
1041     myBrowser = new JEditorPane(UIUtil.HTML_MIME, EMPTY_HTML);
1042     myBrowser.setEditable(false);
1043     myBrowser.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 5));
1044     myBrowser.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE);
1045
1046     initToolStates();
1047     fillTreeData(myProfileFilter != null ? myProfileFilter.getFilter() : null, true);
1048
1049     JPanel descriptionPanel = new JPanel(new BorderLayout());
1050     descriptionPanel.setBorder(IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.description.title"), false,
1051                                                                    new JBInsets(2, 2, 0, 0)));
1052     descriptionPanel.add(ScrollPaneFactory.createScrollPane(myBrowser), BorderLayout.CENTER);
1053
1054     JBSplitter rightSplitter =
1055       new JBSplitter(true, "SingleInspectionProfilePanel.HORIZONTAL_DIVIDER_PROPORTION", DIVIDER_PROPORTION_DEFAULT);
1056     rightSplitter.setFirstComponent(descriptionPanel);
1057
1058     myOptionsPanel = new JPanel(new GridBagLayout());
1059     initOptionsAndDescriptionPanel();
1060     rightSplitter.setSecondComponent(myOptionsPanel);
1061     rightSplitter.setHonorComponentsMinimumSize(true);
1062
1063     final JScrollPane tree = initTreeScrollPane();
1064
1065     final JPanel northPanel = new JPanel(new GridBagLayout());
1066     northPanel.setBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0));
1067     myProfileFilter.setPreferredSize(new Dimension(20, myProfileFilter.getPreferredSize().height));
1068     northPanel.add(myProfileFilter, new GridBagConstraints(0, 0, 1, 1, 0.5, 1, GridBagConstraints.BASELINE_TRAILING, GridBagConstraints.HORIZONTAL,
1069                                                            JBUI.emptyInsets(), 0, 0));
1070     northPanel.add(createTreeToolbarPanel().getComponent(), new GridBagConstraints(1, 0, 1, 1, 1, 1, GridBagConstraints.BASELINE_LEADING, GridBagConstraints.HORIZONTAL,
1071                                                                                    JBUI.emptyInsets(), 0, 0));
1072
1073     JBSplitter mainSplitter = new OnePixelSplitter(false, DIVIDER_PROPORTION_DEFAULT, 0.01f, 0.99f);
1074     mainSplitter.setSplitterProportionKey("SingleInspectionProfilePanel.VERTICAL_DIVIDER_PROPORTION");
1075     mainSplitter.setFirstComponent(tree);
1076     mainSplitter.setSecondComponent(rightSplitter);
1077     mainSplitter.setHonorComponentsMinimumSize(false);
1078
1079     final JPanel panel = new JPanel(new BorderLayout());
1080     panel.add(northPanel, BorderLayout.NORTH);
1081     panel.add(mainSplitter, BorderLayout.CENTER);
1082     return panel;
1083   }
1084
1085   public boolean isModified() {
1086     if (myTreeTable == null) return false;
1087     if (myModified) return true;
1088     if (myProfile.isChanged()) return true;
1089     if (myProfile.getSource().isProjectLevel() != myProfile.isProjectLevel()) return true;
1090     if (!Comparing.strEqual(myProfile.getSource().getName(), myProfile.getName())) return true;
1091     if (!Comparing.equal(myInitialScopesOrder, myProfile.getScopesOrder())) return true;
1092     return descriptorsAreChanged();
1093   }
1094
1095   public void reset() {
1096     myModified = false;
1097     setProfile(myProfile);
1098     filterTree();
1099     final String filter = myProfileFilter.getFilter();
1100     myProfileFilter.reset();
1101     myProfileFilter.setSelectedItem(filter);
1102     myProfile.setName(myProfile.getSource().getName());
1103     myProfile.setProjectLevel(myProfile.getSource().isProjectLevel());
1104   }
1105
1106   public void apply() {
1107     final boolean modified = isModified();
1108     if (!modified) {
1109       return;
1110     }
1111     InspectionProfileModifiableModel selectedProfile = myProfile;
1112
1113     BaseInspectionProfileManager profileManager = selectedProfile.isProjectLevel() ? myProjectProfileManager : (BaseInspectionProfileManager)InspectionProfileManager.getInstance();
1114     InspectionProfileImpl source = selectedProfile.getSource();
1115
1116     // delete by instance, only if from another profile manager or has another name (otherwise will be replaced and we don't need to explicitly delete it)
1117     if (source.getProfileManager() != profileManager || !source.getName().equals(selectedProfile.getName())) {
1118       source.getProfileManager().deleteProfile(source);
1119     }
1120
1121     if (selectedProfile.getProfileManager() != profileManager) {
1122       copyUsedSeveritiesIfUndefined(selectedProfile, profileManager);
1123       selectedProfile.setProfileManager(profileManager);
1124     }
1125
1126     selectedProfile.commit();
1127     profileManager.addProfile(source);
1128     profileManager.fireProfileChanged(source);
1129
1130     myModified = false;
1131     myRoot.dropCache();
1132     initToolStates();
1133     updateOptionsAndDescriptionPanel();
1134   }
1135
1136   private boolean descriptorsAreChanged() {
1137     for (ToolDescriptors toolDescriptors : myInitialToolDescriptors.values()) {
1138       Descriptor desc = toolDescriptors.getDefaultDescriptor();
1139       Project project = myProjectProfileManager.getProject();
1140       if (myProfile.isToolEnabled(desc.getKey(), null, project) != desc.isEnabled()){
1141         return true;
1142       }
1143       if (myProfile.getErrorLevel(desc.getKey(), desc.getScope(), project) != desc.getLevel()) {
1144         return true;
1145       }
1146       final List<Descriptor> descriptors = toolDescriptors.getNonDefaultDescriptors();
1147       for (Descriptor descriptor : descriptors) {
1148         if (myProfile.isToolEnabled(descriptor.getKey(), descriptor.getScope(), project) != descriptor.isEnabled()) {
1149           return true;
1150         }
1151         if (myProfile.getErrorLevel(descriptor.getKey(), descriptor.getScope(), project) != descriptor.getLevel()) {
1152           return true;
1153         }
1154       }
1155
1156       final List<ScopeToolState> tools = myProfile.getNonDefaultTools(desc.getKey().toString(), project);
1157       if (tools.size() != descriptors.size()) {
1158         return true;
1159       }
1160       for (int i = 0; i < tools.size(); i++) {
1161         final ScopeToolState pair = tools.get(i);
1162         if (!Comparing.equal(pair.getScope(project), descriptors.get(i).getScope())) {
1163           return true;
1164         }
1165       }
1166     }
1167
1168
1169     return false;
1170   }
1171
1172   @Override
1173   public void setVisible(boolean aFlag) {
1174     if (aFlag && myInspectionProfilePanel == null) {
1175       initUI();
1176     }
1177     super.setVisible(aFlag);
1178   }
1179
1180   private void setNewHighlightingLevel(@NotNull HighlightDisplayLevel level) {
1181     final int[] rows = myTreeTable.getTree().getSelectionRows();
1182     final boolean showOptionsAndDescriptorPanels = rows != null && rows.length == 1;
1183     for (int i = 0; rows != null && i < rows.length; i++) {
1184       final InspectionConfigTreeNode node = (InspectionConfigTreeNode)myTreeTable.getTree().getPathForRow(rows[i]).getLastPathComponent();
1185       final InspectionConfigTreeNode parent = (InspectionConfigTreeNode)node.getParent();
1186       final Object userObject = node.getUserObject();
1187       if (userObject instanceof ToolDescriptors && (node.getScopeName() != null || node.isLeaf())) {
1188         updateErrorLevel(node, showOptionsAndDescriptorPanels, level);
1189         updateUpHierarchy(parent);
1190       }
1191       else {
1192         updateErrorLevelUpInHierarchy(level, showOptionsAndDescriptorPanels, node);
1193         updateUpHierarchy(parent);
1194       }
1195     }
1196     if (rows != null) {
1197       updateOptionsAndDescriptionPanel(myTreeTable.getTree().getSelectionPaths());
1198     }
1199     else {
1200       initOptionsAndDescriptionPanel();
1201     }
1202     repaintTableData();
1203   }
1204
1205   private void updateErrorLevelUpInHierarchy(@NotNull HighlightDisplayLevel level,
1206                                              boolean showOptionsAndDescriptorPanels,
1207                                              InspectionConfigTreeNode node) {
1208     node.dropCache();
1209     for (int j = 0; j < node.getChildCount(); j++) {
1210       final InspectionConfigTreeNode child = (InspectionConfigTreeNode)node.getChildAt(j);
1211       final Object userObject = child.getUserObject();
1212       if (userObject instanceof ToolDescriptors && (child.getScopeName() != null || child.isLeaf())) {
1213         updateErrorLevel(child, showOptionsAndDescriptorPanels, level);
1214       }
1215       else {
1216         updateErrorLevelUpInHierarchy(level, showOptionsAndDescriptorPanels, child);
1217       }
1218     }
1219   }
1220
1221   private void updateErrorLevel(final InspectionConfigTreeNode child,
1222                                 final boolean showOptionsAndDescriptorPanels,
1223                                 @NotNull HighlightDisplayLevel level) {
1224     final HighlightDisplayKey key = child.getDefaultDescriptor().getKey();
1225     myProfile.setErrorLevel(key, level, null, myProjectProfileManager.getProject());
1226     child.dropCache();
1227     if (showOptionsAndDescriptorPanels) {
1228       updateOptionsAndDescriptionPanel(new TreePath(child.getPath()));
1229     }
1230   }
1231
1232   public JComponent getPreferredFocusedComponent() {
1233     return myTreeTable;
1234   }
1235
1236   private class MyFilterComponent extends FilterComponent {
1237     private MyFilterComponent() {
1238       super(INSPECTION_FILTER_HISTORY, 10);
1239     }
1240
1241     @Override
1242     public void filter() {
1243       filterTree(getFilter());
1244     }
1245
1246     @Override
1247     protected void onlineFilter() {
1248       if (myProfile == null) return;
1249       final String filter = getFilter();
1250       getExpandedNodes(myProfile).saveVisibleState(myTreeTable.getTree());
1251       fillTreeData(filter, true);
1252       reloadModel();
1253       if (filter == null || filter.isEmpty()) {
1254         restoreTreeState();
1255       } else {
1256         TreeUtil.expandAll(myTreeTable.getTree());
1257       }
1258     }
1259   }
1260 }