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