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