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