Home | History | Annotate | Download | only in preferences
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.preferences;
     17 
     18 import com.android.annotations.NonNull;
     19 import com.android.ide.eclipse.adt.AdtPlugin;
     20 import com.android.ide.eclipse.adt.AdtUtils;
     21 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
     22 import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner;
     23 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     24 import com.android.tools.lint.client.api.Configuration;
     25 import com.android.tools.lint.client.api.IssueRegistry;
     26 import com.android.tools.lint.detector.api.Category;
     27 import com.android.tools.lint.detector.api.Issue;
     28 import com.android.tools.lint.detector.api.Project;
     29 import com.android.tools.lint.detector.api.Severity;
     30 
     31 import org.eclipse.core.resources.IProject;
     32 import org.eclipse.core.resources.IWorkspace;
     33 import org.eclipse.core.resources.ResourcesPlugin;
     34 import org.eclipse.core.runtime.IAdaptable;
     35 import org.eclipse.jface.dialogs.MessageDialog;
     36 import org.eclipse.jface.viewers.IColorProvider;
     37 import org.eclipse.jface.viewers.ILabelProviderListener;
     38 import org.eclipse.jface.viewers.ITableLabelProvider;
     39 import org.eclipse.jface.viewers.TreeNodeContentProvider;
     40 import org.eclipse.jface.viewers.TreeViewer;
     41 import org.eclipse.jface.viewers.TreeViewerColumn;
     42 import org.eclipse.jface.viewers.Viewer;
     43 import org.eclipse.jface.window.Window;
     44 import org.eclipse.swt.SWT;
     45 import org.eclipse.swt.events.ControlEvent;
     46 import org.eclipse.swt.events.ControlListener;
     47 import org.eclipse.swt.events.ModifyEvent;
     48 import org.eclipse.swt.events.ModifyListener;
     49 import org.eclipse.swt.events.SelectionEvent;
     50 import org.eclipse.swt.events.SelectionListener;
     51 import org.eclipse.swt.events.TraverseEvent;
     52 import org.eclipse.swt.events.TraverseListener;
     53 import org.eclipse.swt.graphics.Color;
     54 import org.eclipse.swt.graphics.Image;
     55 import org.eclipse.swt.graphics.Rectangle;
     56 import org.eclipse.swt.layout.GridData;
     57 import org.eclipse.swt.layout.GridLayout;
     58 import org.eclipse.swt.widgets.Button;
     59 import org.eclipse.swt.widgets.Combo;
     60 import org.eclipse.swt.widgets.Composite;
     61 import org.eclipse.swt.widgets.Control;
     62 import org.eclipse.swt.widgets.Label;
     63 import org.eclipse.swt.widgets.Link;
     64 import org.eclipse.swt.widgets.Shell;
     65 import org.eclipse.swt.widgets.Text;
     66 import org.eclipse.swt.widgets.Tree;
     67 import org.eclipse.swt.widgets.TreeColumn;
     68 import org.eclipse.swt.widgets.TreeItem;
     69 import org.eclipse.ui.ISharedImages;
     70 import org.eclipse.ui.IWorkbench;
     71 import org.eclipse.ui.IWorkbenchPreferencePage;
     72 import org.eclipse.ui.PlatformUI;
     73 import org.eclipse.ui.dialogs.PreferencesUtil;
     74 import org.eclipse.ui.dialogs.PropertyPage;
     75 
     76 import java.io.File;
     77 import java.util.ArrayList;
     78 import java.util.Collections;
     79 import java.util.HashMap;
     80 import java.util.List;
     81 import java.util.Locale;
     82 import java.util.Map;
     83 
     84 /** Preference page for configuring Lint preferences */
     85 public class LintPreferencePage extends PropertyPage implements IWorkbenchPreferencePage,
     86         SelectionListener, ControlListener, ModifyListener {
     87     private static final String ID =
     88             "com.android.ide.eclipse.common.preferences.LintPreferencePage"; //$NON-NLS-1$
     89     private static final int ID_COLUMN_WIDTH = 150;
     90 
     91     private EclipseLintClient mClient;
     92     private IssueRegistry mRegistry;
     93     private Configuration mConfiguration;
     94     private IProject mProject;
     95     private Map<Issue, Severity> mSeverities = new HashMap<Issue, Severity>();
     96     private Map<Issue, Severity> mInitialSeverities = Collections.<Issue, Severity>emptyMap();
     97     private boolean mIgnoreEvent;
     98 
     99     private Tree mTree;
    100     private TreeViewer mTreeViewer;
    101     private Text mDetailsText;
    102     private Button mCheckFileCheckbox;
    103     private Button mCheckExportCheckbox;
    104     private Link mWorkspaceLink;
    105     private TreeColumn mNameColumn;
    106     private TreeColumn mIdColumn;
    107     private Combo mSeverityCombo;
    108     private Button mIncludeAll;
    109     private Button mIgnoreAll;
    110     private Text mSearch;
    111 
    112     /**
    113      * Create the preference page.
    114      */
    115     public LintPreferencePage() {
    116         setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
    117     }
    118 
    119     @Override
    120     public Control createContents(Composite parent) {
    121         IAdaptable resource = getElement();
    122         if (resource != null) {
    123             mProject = (IProject) resource.getAdapter(IProject.class);
    124         }
    125 
    126         Composite container = new Composite(parent, SWT.NULL);
    127         container.setLayout(new GridLayout(2, false));
    128 
    129         if (mProject != null) {
    130             Label projectLabel = new Label(container, SWT.CHECK);
    131             projectLabel.setLayoutData(new GridData(SWT.LEFT, SWT.BOTTOM, false, false, 1,
    132                     1));
    133             projectLabel.setText("Project-specific configuration:");
    134 
    135             mWorkspaceLink = new Link(container, SWT.NONE);
    136             mWorkspaceLink.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
    137             mWorkspaceLink.setText("<a>Configure Workspace Settings...</a>");
    138             mWorkspaceLink.addSelectionListener(this);
    139         } else {
    140             mCheckFileCheckbox = new Button(container, SWT.CHECK);
    141             mCheckFileCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
    142                     2, 1));
    143             mCheckFileCheckbox.setSelection(true);
    144             mCheckFileCheckbox.setText("When saving files, check for errors");
    145 
    146             mCheckExportCheckbox = new Button(container, SWT.CHECK);
    147             mCheckExportCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
    148                     2, 1));
    149             mCheckExportCheckbox.setSelection(true);
    150             mCheckExportCheckbox.setText("Run full error check when exporting app and abort if fatal errors are found");
    151 
    152             Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
    153             separator.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));
    154 
    155             Label checkListLabel = new Label(container, SWT.NONE);
    156             checkListLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
    157             checkListLabel.setText("Issues:");
    158         }
    159 
    160         mRegistry = EclipseLintClient.getRegistry();
    161         mClient = new EclipseLintClient(mRegistry,
    162                 mProject != null ? Collections.singletonList(mProject) : null, null, false);
    163         Project project = null;
    164         if (mProject != null) {
    165             File dir = AdtUtils.getAbsolutePath(mProject).toFile();
    166             project = mClient.getProject(dir, dir);
    167         }
    168         mConfiguration = mClient.getConfiguration(project);
    169 
    170         mSearch = new Text(container, SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH);
    171         mSearch.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
    172         mSearch.addSelectionListener(this);
    173         mSearch.addModifyListener(this);
    174         // Grab the Enter key such that pressing return in the search box filters (instead
    175         // of closing the options dialog)
    176         mSearch.setMessage("type filter text (use ~ to filter by severity, e.g. ~ignore)");
    177         mSearch.addTraverseListener(new TraverseListener() {
    178             @Override
    179             public void keyTraversed(TraverseEvent e) {
    180                 if (e.keyCode == SWT.CR) {
    181                     updateFilter();
    182                     e.doit = false;
    183                 } else if (e.keyCode == SWT.ARROW_DOWN) {
    184                     // Allow moving from the search into the table
    185                     if (mTree.getItemCount() > 0) {
    186                         TreeItem firstCategory = mTree.getItem(0);
    187                         if (firstCategory.getItemCount() > 0) {
    188                             TreeItem first = firstCategory.getItem(0);
    189                             mTree.setFocus();
    190                             mTree.select(first);
    191                         }
    192                     }
    193                 }
    194             }
    195         });
    196 
    197         mTreeViewer = new TreeViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
    198         mTree = mTreeViewer.getTree();
    199         GridData gdTable = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
    200         gdTable.widthHint = 500;
    201         gdTable.heightHint = 160;
    202         mTree.setLayoutData(gdTable);
    203         mTree.setLinesVisible(true);
    204         mTree.setHeaderVisible(true);
    205 
    206         TreeViewerColumn column1 = new TreeViewerColumn(mTreeViewer, SWT.NONE);
    207         mIdColumn = column1.getColumn();
    208         mIdColumn.setWidth(100);
    209         mIdColumn.setText("Id");
    210 
    211         TreeViewerColumn column2 = new TreeViewerColumn(mTreeViewer, SWT.FILL);
    212         mNameColumn = column2.getColumn();
    213         mNameColumn.setWidth(100);
    214         mNameColumn.setText("Name");
    215 
    216         mTreeViewer.setContentProvider(new ContentProvider());
    217         mTreeViewer.setLabelProvider(new LabelProvider());
    218 
    219         mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP |SWT.V_SCROLL
    220                 | SWT.MULTI);
    221         GridData gdText = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 2);
    222         gdText.heightHint = 80;
    223         mDetailsText.setLayoutData(gdText);
    224 
    225         Label severityLabel = new Label(container, SWT.NONE);
    226         severityLabel.setText("Severity:");
    227 
    228         mSeverityCombo = new Combo(container, SWT.READ_ONLY);
    229         mSeverityCombo.setItems(new String[] {
    230                 "(Default)", "Fatal", "Error", "Warning", "Information", "Ignore"
    231         });
    232         GridData gdSeverityCombo = new GridData(SWT.FILL, SWT.TOP, false, false, 1, 1);
    233         gdSeverityCombo.widthHint = 90;
    234         mSeverityCombo.setLayoutData(gdSeverityCombo);
    235         mSeverityCombo.setText("");
    236         mSeverityCombo.addSelectionListener(this);
    237 
    238         List<Issue> issues = mRegistry.getIssues();
    239         for (Issue issue : issues) {
    240             Severity severity = mConfiguration.getSeverity(issue);
    241             mSeverities.put(issue, severity);
    242         }
    243         mInitialSeverities = new HashMap<Issue, Severity>(mSeverities);
    244 
    245         mTreeViewer.setInput(mRegistry);
    246 
    247         mTree.addSelectionListener(this);
    248         // Add a listener to resize the column to the full width of the table
    249         mTree.addControlListener(this);
    250 
    251         loadSettings(false);
    252 
    253         mTreeViewer.expandAll();
    254 
    255         return container;
    256     }
    257 
    258     /**
    259      * Initialize the preference page.
    260      */
    261     @Override
    262     public void init(IWorkbench workbench) {
    263         // Initialize the preference page
    264     }
    265 
    266     @Override
    267     protected void contributeButtons(Composite parent) {
    268         super.contributeButtons(parent);
    269 
    270         // Add "Include All" button for quickly enabling all the detectors, including
    271         // those disabled by default
    272         mIncludeAll = new Button(parent, SWT.PUSH);
    273         mIncludeAll.setText("Include All");
    274         mIncludeAll.addSelectionListener(this);
    275 
    276         // Add "Ignore All" button for quickly disabling all the detectors
    277         mIgnoreAll = new Button(parent, SWT.PUSH);
    278         mIgnoreAll.setText("Ignore All");
    279         mIgnoreAll.addSelectionListener(this);
    280 
    281         // As per the contributeButton javadoc: increase parent's column count for each
    282         // added button
    283         ((GridLayout) parent.getLayout()).numColumns += 2;
    284     }
    285 
    286     @Override
    287     public void dispose() {
    288         super.dispose();
    289         cancelPendingSearch();
    290     }
    291 
    292     @Override
    293     protected void performDefaults() {
    294         super.performDefaults();
    295 
    296         mConfiguration.startBulkEditing();
    297 
    298         List<Issue> issues = mRegistry.getIssues();
    299         for (Issue issue : issues) {
    300             mConfiguration.setSeverity(issue, null);
    301         }
    302 
    303         mConfiguration.finishBulkEditing();
    304 
    305         loadSettings(true);
    306     }
    307 
    308     @Override
    309     public boolean performOk() {
    310         storeSettings();
    311         return true;
    312     }
    313 
    314     private void loadSettings(boolean refresh) {
    315         if (mCheckExportCheckbox != null) {
    316             AdtPrefs prefs = AdtPrefs.getPrefs();
    317             mCheckFileCheckbox.setSelection(prefs.isLintOnSave());
    318             mCheckExportCheckbox.setSelection(prefs.isLintOnExport());
    319         }
    320 
    321         mSeverities.clear();
    322         List<Issue> issues = mRegistry.getIssues();
    323         for (Issue issue : issues) {
    324             Severity severity = mConfiguration.getSeverity(issue);
    325             mSeverities.put(issue, severity);
    326         }
    327 
    328         if (refresh) {
    329             mTreeViewer.refresh();
    330         }
    331     }
    332 
    333     private void storeSettings() {
    334         // Lint on Save, Lint on Export
    335         if (mCheckExportCheckbox != null) {
    336             AdtPrefs prefs = AdtPrefs.getPrefs();
    337             prefs.setLintOnExport(mCheckExportCheckbox.getSelection());
    338             prefs.setLintOnSave(mCheckFileCheckbox.getSelection());
    339         }
    340 
    341         mConfiguration.startBulkEditing();
    342         try {
    343             // Severities
    344             for (Map.Entry<Issue, Severity> entry : mSeverities.entrySet()) {
    345                 Issue issue = entry.getKey();
    346                 Severity severity = entry.getValue();
    347                 if (mConfiguration.getSeverity(issue) != severity) {
    348                     if ((severity == issue.getDefaultSeverity()) && issue.isEnabledByDefault()) {
    349                         severity = null;
    350                     }
    351                     mConfiguration.setSeverity(issue, severity);
    352                 }
    353             }
    354         } finally {
    355             mConfiguration.finishBulkEditing();
    356         }
    357 
    358         if (!mInitialSeverities.equals(mSeverities)) {
    359             // Ask user whether we should re-run the rules.
    360             MessageDialog dialog = new MessageDialog(
    361                     null, "Lint Settings Have Changed", null,
    362                     "The list of enabled checks has changed. Would you like to run lint now " +
    363                             "to update the results?",
    364                     MessageDialog.QUESTION,
    365                     new String[] {
    366                             "Yes", "No"
    367                     },
    368                     0); // yes is the default
    369             int result = dialog.open();
    370             if (result == 0) {
    371                 // Run lint on all the open Android projects
    372                 IWorkspace workspace = ResourcesPlugin.getWorkspace();
    373                 IProject[] projects = workspace.getRoot().getProjects();
    374                 List<IProject> androidProjects = new ArrayList<IProject>(projects.length);
    375                 for (IProject project : projects) {
    376                     if (project.isOpen() && BaseProjectHelper.isAndroidProject(project)) {
    377                         androidProjects.add(project);
    378                     }
    379                 }
    380 
    381                 EclipseLintRunner.startLint(androidProjects, null, false /*fatalOnly*/,
    382                         true /*show*/);
    383             }
    384         }
    385     }
    386 
    387     private void updateFilter() {
    388         cancelPendingSearch();
    389         if (!mSearch.isDisposed()) {
    390             // Clear selection before refiltering since otherwise it might be showing
    391             // items no longer available in the list.
    392             mTree.setSelection(new TreeItem[0]);
    393             mDetailsText.setText("");
    394             try {
    395                 mIgnoreEvent = true;
    396                 mSeverityCombo.setText("");
    397                 mSeverityCombo.setEnabled(false);
    398             } finally {
    399                 mIgnoreEvent = false;
    400             }
    401 
    402             mTreeViewer.getContentProvider().inputChanged(mTreeViewer, null, mRegistry);
    403             mTreeViewer.refresh();
    404             mTreeViewer.expandAll();
    405         }
    406     }
    407 
    408     private void cancelPendingSearch() {
    409         if (mPendingUpdate != null) {
    410             Shell shell = getShell();
    411             if (!shell.isDisposed()) {
    412                 getShell().getDisplay().timerExec(-1, mPendingUpdate);
    413             }
    414             mPendingUpdate = null;
    415         }
    416     }
    417 
    418     private Runnable mPendingUpdate;
    419 
    420     private void scheduleSearch() {
    421         if (mPendingUpdate == null) {
    422             mPendingUpdate = new Runnable() {
    423                 @Override
    424                 public void run() {
    425                     mPendingUpdate = null;
    426                     updateFilter();
    427                 }
    428             };
    429         }
    430         getShell().getDisplay().timerExec(250 /*ms*/, mPendingUpdate);
    431     }
    432 
    433     // ---- Implements SelectionListener ----
    434 
    435     @Override
    436     public void widgetSelected(SelectionEvent e) {
    437         if (mIgnoreEvent) {
    438             return;
    439         }
    440 
    441         Object source = e.getSource();
    442         if (source == mTree) {
    443             TreeItem item = (TreeItem) e.item;
    444             Object data = item != null ? item.getData() : null;
    445             if (data instanceof Issue) {
    446                 Issue issue = (Issue) data;
    447                 String summary = issue.getDescription();
    448                 String explanation = issue.getExplanation();
    449 
    450                 StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
    451                 sb.append(summary);
    452                 sb.append('\n').append('\n');
    453                 sb.append(explanation);
    454                 mDetailsText.setText(sb.toString());
    455                 try {
    456                     mIgnoreEvent = true;
    457                     Severity severity = getSeverity(issue);
    458                     mSeverityCombo.select(severity.ordinal() + 1); // Skip the default option
    459                     mSeverityCombo.setEnabled(true);
    460                 } finally {
    461                     mIgnoreEvent = false;
    462                 }
    463             } else {
    464                 mDetailsText.setText("");
    465                 try {
    466                     mIgnoreEvent = true;
    467                     mSeverityCombo.setText("");
    468                     mSeverityCombo.setEnabled(false);
    469                 } finally {
    470                     mIgnoreEvent = false;
    471                 }
    472             }
    473         } else if (source == mWorkspaceLink) {
    474             int result = PreferencesUtil.createPreferenceDialogOn(getShell(), ID,
    475                     new String[] { ID }, null).open();
    476             if (result == Window.OK) {
    477                 loadSettings(true);
    478             }
    479         } else if (source == mSeverityCombo) {
    480             int index = mSeverityCombo.getSelectionIndex();
    481             Issue issue = (Issue) mTree.getSelection()[0].getData();
    482             Severity severity;
    483             if (index == -1 || index == 0) {
    484                 // "(Default)"
    485                 severity = issue.getDefaultSeverity();
    486             } else {
    487                 // -1: Skip the "(Default)"
    488                 severity = Severity.values()[index - 1];
    489             }
    490             mSeverities.put(issue, severity);
    491             mTreeViewer.refresh();
    492         } else if (source == mIncludeAll) {
    493             List<Issue> issues = mRegistry.getIssues();
    494             for (Issue issue : issues) {
    495                 // The default severity is never ignore; for disabled-by-default
    496                 // issues the {@link Issue#isEnabledByDefault()} method is false instead
    497                 mSeverities.put(issue, issue.getDefaultSeverity());
    498             }
    499             mTreeViewer.refresh();
    500         } else if (source == mIgnoreAll) {
    501             List<Issue> issues = mRegistry.getIssues();
    502             for (Issue issue : issues) {
    503                 mSeverities.put(issue, Severity.IGNORE);
    504             }
    505             mTreeViewer.refresh();
    506         } else if (source == mSearch) {
    507             updateFilter();
    508         }
    509     }
    510 
    511     private Severity getSeverity(Issue issue) {
    512         Severity severity = mSeverities.get(issue);
    513         if (severity != null) {
    514             return severity;
    515         }
    516 
    517         return mConfiguration.getSeverity(issue);
    518     }
    519 
    520     @Override
    521     public void widgetDefaultSelected(SelectionEvent e) {
    522         Object source = e.getSource();
    523         if (source == mTree) {
    524             widgetSelected(e);
    525         } else if (source == mSearch) {
    526             if (e.detail == SWT.CANCEL) {
    527                 // Cancel the search
    528                 mSearch.setText("");
    529             }
    530             updateFilter();
    531         }
    532     }
    533 
    534     // ---- Implements ModifyListener ----
    535     @Override
    536     public void modifyText(ModifyEvent e) {
    537         if (e.getSource() == mSearch) {
    538             scheduleSearch();
    539         }
    540     }
    541 
    542     // ---- Implements ControlListener ----
    543 
    544     @Override
    545     public void controlMoved(ControlEvent e) {
    546     }
    547 
    548     @Override
    549     public void controlResized(ControlEvent e) {
    550         Rectangle r = mTree.getClientArea();
    551         int availableWidth = r.width;
    552 
    553         mIdColumn.setWidth(ID_COLUMN_WIDTH);
    554         availableWidth -= ID_COLUMN_WIDTH;
    555 
    556         // Name absorbs everything else
    557         mNameColumn.setWidth(availableWidth);
    558     }
    559 
    560     private boolean filterMatches(@NonNull String filter, @NonNull Issue issue) {
    561         return (filter.startsWith("~") //$NON-NLS-1$
    562                         && mConfiguration.getSeverity(issue).getDescription()
    563                             .toLowerCase(Locale.US).startsWith(filter.substring(1)))
    564                 || issue.getCategory().getName().toLowerCase(Locale.US).startsWith(filter)
    565                 || issue.getCategory().getFullName().toLowerCase(Locale.US).startsWith(filter)
    566                 || issue.getId().toLowerCase(Locale.US).contains(filter)
    567                 || issue.getDescription().toLowerCase(Locale.US).contains(filter);
    568     }
    569 
    570     private class ContentProvider extends TreeNodeContentProvider {
    571         private Map<Category, List<Issue>> mCategoryToIssues;
    572         private Object[] mElements;
    573 
    574         @Override
    575         public Object[] getElements(Object inputElement) {
    576             return mElements;
    577         }
    578 
    579         @Override
    580         public boolean hasChildren(Object element) {
    581             return element instanceof Category;
    582         }
    583 
    584         @Override
    585         public Object[] getChildren(Object parentElement) {
    586             assert mCategoryToIssues != null;
    587             List<Issue> list = mCategoryToIssues.get(parentElement);
    588             if (list == null) {
    589                 return new Object[0];
    590             }  else {
    591                 return list.toArray();
    592             }
    593         }
    594 
    595         @Override
    596         public Object getParent(Object element) {
    597             return null;
    598         }
    599 
    600         @Override
    601         public void inputChanged(final Viewer viewer, final Object oldInput,
    602                 final Object newInput) {
    603             mCategoryToIssues = null;
    604 
    605             String filter = mSearch.isDisposed() ? "" : mSearch.getText().trim();
    606             if (filter.length() == 0) {
    607                 filter = null;
    608             } else {
    609                 filter = filter.toLowerCase(Locale.US);
    610             }
    611 
    612             mCategoryToIssues = new HashMap<Category, List<Issue>>();
    613             List<Issue> issues = mRegistry.getIssues();
    614             for (Issue issue : issues) {
    615                 if (filter == null || filterMatches(filter, issue)) {
    616                     List<Issue> list = mCategoryToIssues.get(issue.getCategory());
    617                     if (list == null) {
    618                         list = new ArrayList<Issue>();
    619                         mCategoryToIssues.put(issue.getCategory(), list);
    620                     }
    621                     list.add(issue);
    622                 }
    623             }
    624 
    625             if (filter == null) {
    626                 // Not filtering: show all categories
    627                 mElements = mRegistry.getCategories().toArray();
    628             } else {
    629                 // Filtering: only include categories that contain matches
    630                 if (mCategoryToIssues == null) {
    631                     getChildren(null);
    632                 }
    633 
    634                 // Preserve the current category order, so instead of
    635                 // just creating a list of the mCategoryToIssues keyset, add them
    636                 // in the order they appear in in the registry
    637                 List<Category> categories = new ArrayList<Category>(mCategoryToIssues.size());
    638                 for (Category category : mRegistry.getCategories()) {
    639                     if (mCategoryToIssues.containsKey(category)) {
    640                         categories.add(category);
    641                     }
    642                 }
    643                 mElements = categories.toArray();
    644             }
    645         }
    646     }
    647 
    648     private class LabelProvider implements ITableLabelProvider, IColorProvider {
    649 
    650         @Override
    651         public void addListener(ILabelProviderListener listener) {
    652         }
    653 
    654         @Override
    655         public void dispose() {
    656         }
    657 
    658         @Override
    659         public boolean isLabelProperty(Object element, String property) {
    660             return true;
    661         }
    662 
    663         @Override
    664         public void removeListener(ILabelProviderListener listener) {
    665         }
    666 
    667         @Override
    668         public Image getColumnImage(Object element, int columnIndex) {
    669             if (element instanceof Category) {
    670                 return null;
    671             }
    672 
    673             if (columnIndex == 1) {
    674                 Issue issue = (Issue) element;
    675                 Severity severity = mSeverities.get(issue);
    676                 if (severity == null) {
    677                     return null;
    678                 }
    679 
    680                 ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
    681                 switch (severity) {
    682                     case FATAL:
    683                     case ERROR:
    684                         return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
    685                     case WARNING:
    686                         return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
    687                     case INFORMATIONAL:
    688                         return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
    689                     case IGNORE:
    690                         return sharedImages.getImage(ISharedImages.IMG_ELCL_REMOVE_DISABLED);
    691                 }
    692             }
    693             return null;
    694         }
    695 
    696         @Override
    697         public String getColumnText(Object element, int columnIndex) {
    698             if (element instanceof Category) {
    699                 if (columnIndex == 0) {
    700                     return ((Category) element).getFullName();
    701                 } else {
    702                     return ((Category) element).getExplanation();
    703                 }
    704             }
    705 
    706             Issue issue = (Issue) element;
    707             switch (columnIndex) {
    708                 case 0:
    709                     return issue.getId();
    710                 case 1:
    711                     return issue.getDescription();
    712             }
    713 
    714             return null;
    715         }
    716 
    717         // ---- IColorProvider ----
    718 
    719         @Override
    720         public Color getForeground(Object element) {
    721             if (element instanceof Category) {
    722                 return mTree.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND);
    723             }
    724 
    725             if (element instanceof Issue) {
    726                 Issue issue = (Issue) element;
    727                 Severity severity = mSeverities.get(issue);
    728                 if (severity == Severity.IGNORE) {
    729                     return mTree.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
    730                 }
    731             }
    732 
    733             return null;
    734         }
    735 
    736         @Override
    737         public Color getBackground(Object element) {
    738             if (element instanceof Category) {
    739                 return mTree.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
    740             }
    741             return null;
    742         }
    743     }
    744 }