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.lint; 17 18 import com.android.ide.eclipse.adt.AdtConstants; 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AdtUtils; 21 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; 22 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; 23 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar; 24 import com.android.tools.lint.client.api.Configuration; 25 import com.android.tools.lint.client.api.IssueRegistry; 26 import com.android.tools.lint.client.api.LintClient; 27 import com.android.tools.lint.detector.api.Issue; 28 import com.android.tools.lint.detector.api.Severity; 29 import com.google.common.collect.ArrayListMultimap; 30 import com.google.common.collect.Multimap; 31 32 import org.eclipse.core.resources.IMarker; 33 import org.eclipse.core.resources.IMarkerDelta; 34 import org.eclipse.core.resources.IProject; 35 import org.eclipse.core.resources.IResource; 36 import org.eclipse.core.resources.IResourceChangeEvent; 37 import org.eclipse.core.resources.IResourceChangeListener; 38 import org.eclipse.core.resources.ResourcesPlugin; 39 import org.eclipse.core.runtime.IProgressMonitor; 40 import org.eclipse.core.runtime.IStatus; 41 import org.eclipse.core.runtime.NullProgressMonitor; 42 import org.eclipse.core.runtime.Status; 43 import org.eclipse.jface.operation.IRunnableWithProgress; 44 import org.eclipse.jface.viewers.ColumnPixelData; 45 import org.eclipse.jface.viewers.ColumnWeightData; 46 import org.eclipse.jface.viewers.StructuredSelection; 47 import org.eclipse.jface.viewers.StyledCellLabelProvider; 48 import org.eclipse.jface.viewers.StyledString; 49 import org.eclipse.jface.viewers.TableLayout; 50 import org.eclipse.jface.viewers.TreeNodeContentProvider; 51 import org.eclipse.jface.viewers.TreePath; 52 import org.eclipse.jface.viewers.TreeViewer; 53 import org.eclipse.jface.viewers.TreeViewerColumn; 54 import org.eclipse.jface.viewers.Viewer; 55 import org.eclipse.jface.viewers.ViewerCell; 56 import org.eclipse.jface.viewers.ViewerComparator; 57 import org.eclipse.jface.window.Window; 58 import org.eclipse.swt.SWT; 59 import org.eclipse.swt.custom.BusyIndicator; 60 import org.eclipse.swt.events.ControlEvent; 61 import org.eclipse.swt.events.ControlListener; 62 import org.eclipse.swt.events.PaintEvent; 63 import org.eclipse.swt.events.PaintListener; 64 import org.eclipse.swt.events.SelectionAdapter; 65 import org.eclipse.swt.events.SelectionEvent; 66 import org.eclipse.swt.events.SelectionListener; 67 import org.eclipse.swt.graphics.Rectangle; 68 import org.eclipse.swt.layout.GridData; 69 import org.eclipse.swt.layout.GridLayout; 70 import org.eclipse.swt.widgets.Composite; 71 import org.eclipse.swt.widgets.Event; 72 import org.eclipse.swt.widgets.Tree; 73 import org.eclipse.swt.widgets.TreeColumn; 74 import org.eclipse.swt.widgets.TreeItem; 75 import org.eclipse.ui.IMemento; 76 import org.eclipse.ui.IWorkbenchPartSite; 77 import org.eclipse.ui.PlatformUI; 78 import org.eclipse.ui.progress.IWorkbenchSiteProgressService; 79 import org.eclipse.ui.progress.WorkbenchJob; 80 81 import java.lang.reflect.InvocationTargetException; 82 import java.util.ArrayList; 83 import java.util.Arrays; 84 import java.util.Collection; 85 import java.util.HashMap; 86 import java.util.List; 87 import java.util.Map; 88 import java.util.Set; 89 90 /** 91 * A tree-table widget which shows a list of lint warnings for an underlying 92 * {@link IResource} such as a file, a project, or a list of projects. 93 */ 94 class LintList extends Composite implements IResourceChangeListener, ControlListener { 95 private static final Object UPDATE_MARKERS_FAMILY = new Object(); 96 97 // For persistence: 98 private static final String KEY_WIDTHS = "lintColWidth"; //$NON-NLS-1$ 99 private static final String KEY_VISIBLE = "lintColVisible"; //$NON-NLS-1$ 100 // Mapping SWT TreeColumns to LintColumns 101 private static final String KEY_COLUMN = "lintColumn"; //$NON-NLS-1$ 102 103 private final IWorkbenchPartSite mSite; 104 private final TreeViewer mTreeViewer; 105 private final Tree mTree; 106 private ContentProvider mContentProvider; 107 private String mSelectedId; 108 private List<? extends IResource> mResources; 109 private Configuration mConfiguration; 110 private final boolean mSingleFile; 111 private int mErrorCount; 112 private int mWarningCount; 113 private final UpdateMarkersJob mUpdateMarkersJob = new UpdateMarkersJob(); 114 private final IssueRegistry mRegistry; 115 private final IMemento mMemento; 116 private final LintColumn mMessageColumn = new LintColumn.MessageColumn(this); 117 private final LintColumn mLineColumn = new LintColumn.LineColumn(this); 118 private final LintColumn[] mColumns = new LintColumn[] { 119 mMessageColumn, 120 new LintColumn.PriorityColumn(this), 121 new LintColumn.CategoryColumn(this), 122 new LintColumn.LocationColumn(this), 123 new LintColumn.FileColumn(this), 124 new LintColumn.PathColumn(this), 125 mLineColumn 126 }; 127 private LintColumn[] mVisibleColumns; 128 LintList(IWorkbenchPartSite site, Composite parent, IMemento memento, boolean singleFile)129 LintList(IWorkbenchPartSite site, Composite parent, IMemento memento, boolean singleFile) { 130 super(parent, SWT.NONE); 131 mSingleFile = singleFile; 132 mMemento = memento; 133 mSite = site; 134 mRegistry = EclipseLintClient.getRegistry(); 135 136 GridLayout gridLayout = new GridLayout(1, false); 137 gridLayout.marginWidth = 0; 138 gridLayout.marginHeight = 0; 139 setLayout(gridLayout); 140 141 mTreeViewer = new TreeViewer(this, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI); 142 mTree = mTreeViewer.getTree(); 143 mTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); 144 145 createColumns(); 146 mTreeViewer.setComparator(new TableComparator()); 147 setSortIndicators(); 148 149 mContentProvider = new ContentProvider(); 150 mTreeViewer.setContentProvider(mContentProvider); 151 152 mTree.setLinesVisible(true); 153 mTree.setHeaderVisible(true); 154 mTree.addControlListener(this); 155 156 ResourcesPlugin.getWorkspace().addResourceChangeListener( 157 this, 158 IResourceChangeEvent.POST_CHANGE 159 | IResourceChangeEvent.PRE_BUILD 160 | IResourceChangeEvent.POST_BUILD); 161 162 // Workaround for https://bugs.eclipse.org/341865 163 mTree.addPaintListener(new PaintListener() { 164 @Override 165 public void paintControl(PaintEvent e) { 166 treePainted = true; 167 mTreeViewer.getTree().removePaintListener(this); 168 } 169 }); 170 171 // Remember the most recently selected id category such that we can 172 // attempt to reselect it after a refresh 173 mTree.addSelectionListener(new SelectionAdapter() { 174 @Override 175 public void widgetSelected(SelectionEvent e) { 176 List<IMarker> markers = getSelectedMarkers(); 177 if (markers.size() > 0) { 178 mSelectedId = EclipseLintClient.getId(markers.get(0)); 179 } 180 } 181 }); 182 } 183 184 private boolean treePainted; 185 updateColumnWidths()186 private void updateColumnWidths() { 187 Rectangle r = mTree.getClientArea(); 188 int availableWidth = r.width; 189 // Add all available size to the first column 190 for (int i = 1; i < mTree.getColumnCount(); i++) { 191 TreeColumn column = mTree.getColumn(i); 192 availableWidth -= column.getWidth(); 193 } 194 if (availableWidth > 100) { 195 mTree.getColumn(0).setWidth(availableWidth); 196 } 197 } 198 setResources(List<? extends IResource> resources)199 public void setResources(List<? extends IResource> resources) { 200 mResources = resources; 201 202 mConfiguration = null; 203 for (IResource resource : mResources) { 204 IProject project = resource.getProject(); 205 if (project != null) { 206 // For logging only 207 LintClient client = new EclipseLintClient(null, null, null, false); 208 mConfiguration = ProjectLintConfiguration.get(client, project, false); 209 break; 210 } 211 } 212 if (mConfiguration == null) { 213 mConfiguration = GlobalLintConfiguration.get(); 214 } 215 216 List<IMarker> markerList = getMarkers(); 217 mTreeViewer.setInput(markerList); 218 if (mSingleFile) { 219 expandAll(); 220 } 221 222 // Selecting the first item isn't a good idea since it may not be the first 223 // item shown in the table (since it does its own sorting), and furthermore we 224 // may not have all the data yet; this is called when scanning begins, not when 225 // it's done: 226 //if (mTree.getItemCount() > 0) { 227 // mTree.select(mTree.getItem(0)); 228 //} 229 230 updateColumnWidths(); // in case mSingleFile changed 231 } 232 getMarkers()233 private List<IMarker> getMarkers() { 234 mErrorCount = mWarningCount = 0; 235 List<IMarker> markerList = new ArrayList<IMarker>(); 236 if (mResources != null) { 237 for (IResource resource : mResources) { 238 IMarker[] markers = EclipseLintClient.getMarkers(resource); 239 for (IMarker marker : markers) { 240 markerList.add(marker); 241 int severity = marker.getAttribute(IMarker.SEVERITY, 0); 242 if (severity == IMarker.SEVERITY_ERROR) { 243 mErrorCount++; 244 } else if (severity == IMarker.SEVERITY_WARNING) { 245 mWarningCount++; 246 } 247 } 248 } 249 250 // No need to sort the marker list here; it will be sorted by the tree table model 251 } 252 return markerList; 253 } 254 getErrorCount()255 public int getErrorCount() { 256 return mErrorCount; 257 } 258 getWarningCount()259 public int getWarningCount() { 260 return mWarningCount; 261 } 262 263 @Override checkSubclass()264 protected void checkSubclass() { 265 // Disable the check that prevents subclassing of SWT components 266 } 267 addSelectionListener(SelectionListener listener)268 public void addSelectionListener(SelectionListener listener) { 269 mTree.addSelectionListener(listener); 270 } 271 refresh()272 public void refresh() { 273 mTreeViewer.refresh(); 274 } 275 getSelectedMarkers()276 public List<IMarker> getSelectedMarkers() { 277 TreeItem[] selection = mTree.getSelection(); 278 List<IMarker> markers = new ArrayList<IMarker>(selection.length); 279 for (TreeItem item : selection) { 280 Object data = item.getData(); 281 if (data instanceof IMarker) { 282 markers.add((IMarker) data); 283 } 284 } 285 286 return markers; 287 } 288 289 @Override dispose()290 public void dispose() { 291 cancelJobs(); 292 ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); 293 super.dispose(); 294 } 295 296 private class ContentProvider extends TreeNodeContentProvider { 297 private Map<Object, Object[]> mChildren; 298 private Map<IMarker, Integer> mTypeCount; 299 private IMarker[] mTopLevels; 300 301 @Override getElements(Object inputElement)302 public Object[] getElements(Object inputElement) { 303 if (inputElement == null) { 304 mTypeCount = null; 305 return new IMarker[0]; 306 } 307 308 @SuppressWarnings("unchecked") 309 List<IMarker> list = (List<IMarker>) inputElement; 310 311 // Partition the children such that at the top level we have one 312 // marker of each type, and below we have all the duplicates of 313 // each one of those errors. And for errors with multiple locations, 314 // there is a third level. 315 Multimap<String, IMarker> types = ArrayListMultimap.<String, IMarker>create(100, 20); 316 for (IMarker marker : list) { 317 String id = EclipseLintClient.getId(marker); 318 types.put(id, marker); 319 } 320 321 Set<String> ids = types.keySet(); 322 323 mChildren = new HashMap<Object, Object[]>(ids.size()); 324 mTypeCount = new HashMap<IMarker, Integer>(ids.size()); 325 326 List<IMarker> topLevel = new ArrayList<IMarker>(ids.size()); 327 for (String id : ids) { 328 Collection<IMarker> markers = types.get(id); 329 int childCount = markers.size(); 330 331 // Must sort the list items in order to have a stable first item 332 // (otherwise preserving expanded paths etc won't work) 333 TableComparator sorter = getTableSorter(); 334 IMarker[] array = markers.toArray(new IMarker[markers.size()]); 335 sorter.sort(mTreeViewer, array); 336 337 IMarker topMarker = array[0]; 338 mTypeCount.put(topMarker, childCount); 339 topLevel.add(topMarker); 340 341 IMarker[] children = Arrays.copyOfRange(array, 1, array.length); 342 mChildren.put(topMarker, children); 343 } 344 345 mTopLevels = topLevel.toArray(new IMarker[topLevel.size()]); 346 return mTopLevels; 347 } 348 349 @Override hasChildren(Object element)350 public boolean hasChildren(Object element) { 351 Object[] children = mChildren != null ? mChildren.get(element) : null; 352 return children != null && children.length > 0; 353 } 354 355 @Override getChildren(Object parentElement)356 public Object[] getChildren(Object parentElement) { 357 Object[] children = mChildren.get(parentElement); 358 if (children != null) { 359 return children; 360 } 361 362 return new Object[0]; 363 } 364 365 @Override getParent(Object element)366 public Object getParent(Object element) { 367 return null; 368 } 369 getCount(IMarker marker)370 public int getCount(IMarker marker) { 371 if (mTypeCount != null) { 372 Integer count = mTypeCount.get(marker); 373 if (count != null) { 374 return count.intValue(); 375 } 376 } 377 378 return -1; 379 } 380 getTopMarkers()381 IMarker[] getTopMarkers() { 382 return mTopLevels; 383 } 384 } 385 386 private class LintColumnLabelProvider extends StyledCellLabelProvider { 387 private LintColumn mColumn; 388 LintColumnLabelProvider(LintColumn column)389 LintColumnLabelProvider(LintColumn column) { 390 mColumn = column; 391 } 392 393 @Override update(ViewerCell cell)394 public void update(ViewerCell cell) { 395 Object element = cell.getElement(); 396 cell.setImage(mColumn.getImage((IMarker) element)); 397 StyledString styledString = mColumn.getStyledValue((IMarker) element); 398 if (styledString == null) { 399 cell.setText(mColumn.getValue((IMarker) element)); 400 cell.setStyleRanges(null); 401 } else { 402 cell.setText(styledString.toString()); 403 cell.setStyleRanges(styledString.getStyleRanges()); 404 } 405 super.update(cell); 406 } 407 } 408 getTreeViewer()409 TreeViewer getTreeViewer() { 410 return mTreeViewer; 411 } 412 getTree()413 Tree getTree() { 414 return mTree; 415 } 416 417 // ---- Implements IResourceChangeListener ---- 418 419 @Override resourceChanged(IResourceChangeEvent event)420 public void resourceChanged(IResourceChangeEvent event) { 421 if (mResources == null) { 422 return; 423 } 424 IMarkerDelta[] deltas = event.findMarkerDeltas(AdtConstants.MARKER_LINT, true); 425 if (deltas.length > 0) { 426 // Update immediately for POST_BUILD events, otherwise do an unconditional 427 // update after 30 seconds. This matches the logic in Eclipse's ProblemView 428 // (see the MarkerView class). 429 if (event.getType() == IResourceChangeEvent.POST_BUILD) { 430 cancelJobs(); 431 getProgressService().schedule(mUpdateMarkersJob, 100); 432 } else { 433 IWorkbenchSiteProgressService progressService = getProgressService(); 434 if (progressService == null) { 435 mUpdateMarkersJob.schedule(30000); 436 } else { 437 getProgressService().schedule(mUpdateMarkersJob, 30000); 438 } 439 } 440 } 441 } 442 443 // ---- Implements ControlListener ---- 444 445 @Override controlMoved(ControlEvent e)446 public void controlMoved(ControlEvent e) { 447 } 448 449 @Override controlResized(ControlEvent e)450 public void controlResized(ControlEvent e) { 451 updateColumnWidths(); 452 } 453 454 // ---- Updating Markers ---- 455 cancelJobs()456 private void cancelJobs() { 457 mUpdateMarkersJob.cancel(); 458 } 459 getProgressService()460 protected IWorkbenchSiteProgressService getProgressService() { 461 if (mSite != null) { 462 Object siteService = mSite.getAdapter(IWorkbenchSiteProgressService.class); 463 if (siteService != null) { 464 return (IWorkbenchSiteProgressService) siteService; 465 } 466 } 467 return null; 468 } 469 470 private class UpdateMarkersJob extends WorkbenchJob { UpdateMarkersJob()471 UpdateMarkersJob() { 472 super("Updating Lint Markers"); 473 setSystem(true); 474 } 475 476 @Override runInUIThread(IProgressMonitor monitor)477 public IStatus runInUIThread(IProgressMonitor monitor) { 478 if (mTree.isDisposed()) { 479 return Status.CANCEL_STATUS; 480 } 481 482 Object[] expandedElements = mTreeViewer.getExpandedElements(); 483 TreePath[] expandedTreePaths = mTreeViewer.getExpandedTreePaths(); 484 485 mTreeViewer.setInput(null); 486 List<IMarker> markerList = getMarkers(); 487 if (markerList.size() == 0) { 488 LayoutEditorDelegate delegate = 489 LayoutEditorDelegate.fromEditor(AdtUtils.getActiveEditor()); 490 if (delegate != null) { 491 GraphicalEditorPart g = delegate.getGraphicalEditor(); 492 assert g != null; 493 LayoutActionBar bar = g == null ? null : g.getLayoutActionBar(); 494 assert bar != null; 495 if (bar != null) { 496 bar.updateErrorIndicator(); 497 } 498 } 499 } 500 // Trigger selection update 501 Event updateEvent = new Event(); 502 updateEvent.widget = mTree; 503 mTree.notifyListeners(SWT.Selection, updateEvent); 504 mTreeViewer.setInput(markerList); 505 mTreeViewer.refresh(); 506 507 mTreeViewer.setExpandedElements(expandedElements); 508 mTreeViewer.setExpandedTreePaths(expandedTreePaths); 509 510 if (mSelectedId != null) { 511 IMarker[] topMarkers = mContentProvider.getTopMarkers(); 512 for (IMarker marker : topMarkers) { 513 if (mSelectedId.equals(EclipseLintClient.getId(marker))) { 514 mTreeViewer.setSelection(new StructuredSelection(marker), true /*reveal*/); 515 break; 516 } 517 } 518 } 519 520 return Status.OK_STATUS; 521 } 522 523 @Override shouldRun()524 public boolean shouldRun() { 525 // Do not run if the change came in before there is a viewer 526 return PlatformUI.isWorkbenchRunning(); 527 } 528 529 @Override belongsTo(Object family)530 public boolean belongsTo(Object family) { 531 return UPDATE_MARKERS_FAMILY == family; 532 } 533 } 534 535 /** 536 * Returns the list of resources being shown in the list 537 * 538 * @return the list of resources being shown in this composite 539 */ getResources()540 public List<? extends IResource> getResources() { 541 return mResources; 542 } 543 544 /** Expands all nodes */ expandAll()545 public void expandAll() { 546 mTreeViewer.expandAll(); 547 } 548 549 /** Collapses all nodes */ collapseAll()550 public void collapseAll() { 551 mTreeViewer.collapseAll(); 552 } 553 554 // ---- Column Persistence ---- 555 saveState(IMemento memento)556 public void saveState(IMemento memento) { 557 if (mSingleFile) { 558 // Don't use persistence for single-file lists: this is a special mode of the 559 // window where we show a hardcoded set of columns for a single file, deliberately 560 // omitting the location column etc 561 return; 562 } 563 564 IMemento columnEntry = memento.createChild(KEY_WIDTHS); 565 LintColumn[] columns = new LintColumn[mTree.getColumnCount()]; 566 int[] positions = mTree.getColumnOrder(); 567 for (int i = 0; i < columns.length; i++) { 568 TreeColumn treeColumn = mTree.getColumn(i); 569 LintColumn column = (LintColumn) treeColumn.getData(KEY_COLUMN); 570 // Workaround for TeeColumn.getWidth() returning 0 in some cases, 571 // see https://bugs.eclipse.org/341865 for details. 572 int width = getColumnWidth(column, treePainted); 573 columnEntry.putInteger(getKey(treeColumn), width); 574 columns[positions[i]] = column; 575 } 576 577 if (getVisibleColumns() != null) { 578 IMemento visibleEntry = memento.createChild(KEY_VISIBLE); 579 for (LintColumn column : getVisibleColumns()) { 580 visibleEntry.putBoolean(getKey(column), true); 581 } 582 } 583 } 584 createColumns()585 private void createColumns() { 586 LintColumn[] columns = getVisibleColumns(); 587 TableLayout layout = new TableLayout(); 588 589 for (int i = 0; i < columns.length; i++) { 590 LintColumn column = columns[i]; 591 TreeViewerColumn viewerColumn = null; 592 TreeColumn treeColumn; 593 viewerColumn = new TreeViewerColumn(mTreeViewer, SWT.NONE); 594 treeColumn = viewerColumn.getColumn(); 595 treeColumn.setData(KEY_COLUMN, column); 596 treeColumn.setResizable(true); 597 treeColumn.addSelectionListener(getHeaderListener()); 598 if (!column.isLeftAligned()) { 599 treeColumn.setAlignment(SWT.RIGHT); 600 } 601 viewerColumn.setLabelProvider(new LintColumnLabelProvider(column)); 602 treeColumn.setText(column.getColumnHeaderText()); 603 treeColumn.setImage(column.getColumnHeaderImage()); 604 IMemento columnWidths = null; 605 if (mMemento != null && !mSingleFile) { 606 columnWidths = mMemento.getChild(KEY_WIDTHS); 607 } 608 int columnWidth = getColumnWidth(column, false); 609 if (columnWidths != null) { 610 columnWidths.putInteger(getKey(column), columnWidth); 611 } 612 if (i == 0) { 613 // The first column should use layout -weights- to get all the 614 // remaining room 615 layout.addColumnData(new ColumnWeightData(1, true)); 616 } else if (columnWidth < 0) { 617 int defaultColumnWidth = column.getPreferredWidth(); 618 layout.addColumnData(new ColumnPixelData(defaultColumnWidth, true, true)); 619 } else { 620 layout.addColumnData(new ColumnPixelData(columnWidth, true)); 621 } 622 } 623 mTreeViewer.getTree().setLayout(layout); 624 mTree.layout(true); 625 } 626 getColumnWidth(LintColumn column, boolean getFromUi)627 private int getColumnWidth(LintColumn column, boolean getFromUi) { 628 Tree tree = mTreeViewer.getTree(); 629 if (getFromUi) { 630 TreeColumn[] columns = tree.getColumns(); 631 for (int i = 0; i < columns.length; i++) { 632 if (column.equals(columns[i].getData(KEY_COLUMN))) { 633 return columns[i].getWidth(); 634 } 635 } 636 } 637 int preferredWidth = -1; 638 if (mMemento != null && !mSingleFile) { 639 IMemento columnWidths = mMemento.getChild(KEY_WIDTHS); 640 if (columnWidths != null) { 641 Integer value = columnWidths.getInteger(getKey(column)); 642 // Make sure we get a useful value 643 if (value != null && value.intValue() >= 0) 644 preferredWidth = value.intValue(); 645 } 646 } 647 if (preferredWidth <= 0) { 648 preferredWidth = Math.max(column.getPreferredWidth(), 30); 649 } 650 return preferredWidth; 651 } 652 getKey(TreeColumn treeColumn)653 private static String getKey(TreeColumn treeColumn) { 654 return getKey((LintColumn) treeColumn.getData(KEY_COLUMN)); 655 } 656 getKey(LintColumn column)657 private static String getKey(LintColumn column) { 658 return column.getClass().getSimpleName(); 659 } 660 getVisibleColumns()661 private LintColumn[] getVisibleColumns() { 662 if (mVisibleColumns == null) { 663 if (mSingleFile) { 664 // Special mode where we show just lint warnings for a single file: 665 // use a hardcoded list of columns, not including path/location etc but 666 // including line numbers (which are normally not shown by default). 667 mVisibleColumns = new LintColumn[] { 668 mMessageColumn, mLineColumn 669 }; 670 } else { 671 // Generate visible columns based on (a) previously saved window state, 672 // and (b) default window visible states provided by the columns themselves 673 List<LintColumn> list = new ArrayList<LintColumn>(); 674 IMemento visibleColumns = null; 675 if (mMemento != null) { 676 visibleColumns = mMemento.getChild(KEY_VISIBLE); 677 } 678 for (LintColumn column : mColumns) { 679 if (visibleColumns != null) { 680 Boolean b = visibleColumns.getBoolean(getKey(column)); 681 if (b != null && b.booleanValue()) { 682 list.add(column); 683 } 684 } else if (column.visibleByDefault()) { 685 list.add(column); 686 } 687 } 688 if (!list.contains(mMessageColumn)) { 689 list.add(0, mMessageColumn); 690 } 691 mVisibleColumns = list.toArray(new LintColumn[list.size()]); 692 } 693 } 694 695 return mVisibleColumns; 696 } 697 getCount(IMarker marker)698 int getCount(IMarker marker) { 699 return mContentProvider.getCount(marker); 700 } 701 getIssue(String id)702 Issue getIssue(String id) { 703 return mRegistry.getIssue(id); 704 } 705 getIssue(IMarker marker)706 Issue getIssue(IMarker marker) { 707 String id = EclipseLintClient.getId(marker); 708 return mRegistry.getIssue(id); 709 } 710 getSeverity(Issue issue)711 Severity getSeverity(Issue issue) { 712 return mConfiguration.getSeverity(issue); 713 } 714 715 // ---- Choosing visible columns ---- 716 configureColumns()717 public void configureColumns() { 718 ColumnDialog dialog = new ColumnDialog(getShell(), mColumns, getVisibleColumns()); 719 if (dialog.open() == Window.OK) { 720 mVisibleColumns = dialog.getSelectedColumns(); 721 // Clear out columns: Must recreate to set the right label provider etc 722 for (TreeColumn column : mTree.getColumns()) { 723 column.dispose(); 724 } 725 createColumns(); 726 mTreeViewer.setComparator(new TableComparator()); 727 setSortIndicators(); 728 mTreeViewer.refresh(); 729 } 730 } 731 732 // ---- Table Sorting ---- 733 getHeaderListener()734 private SelectionListener getHeaderListener() { 735 return new SelectionAdapter() { 736 @Override 737 public void widgetSelected(SelectionEvent e) { 738 final TreeColumn treeColumn = (TreeColumn) e.widget; 739 final LintColumn column = (LintColumn) treeColumn.getData(KEY_COLUMN); 740 741 try { 742 IWorkbenchSiteProgressService progressService = getProgressService(); 743 if (progressService == null) { 744 BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() { 745 @Override 746 public void run() { 747 resortTable(treeColumn, column, 748 new NullProgressMonitor()); 749 } 750 }); 751 } else { 752 getProgressService().busyCursorWhile(new IRunnableWithProgress() { 753 @Override 754 public void run(IProgressMonitor monitor) { 755 resortTable(treeColumn, column, monitor); 756 } 757 }); 758 } 759 } catch (InvocationTargetException e1) { 760 AdtPlugin.log(e1, null); 761 } catch (InterruptedException e1) { 762 return; 763 } 764 } 765 766 private void resortTable(final TreeColumn treeColumn, LintColumn column, 767 IProgressMonitor monitor) { 768 TableComparator sorter = getTableSorter(); 769 monitor.beginTask("Sorting", 100); 770 monitor.worked(10); 771 if (column.equals(sorter.getTopColumn())) { 772 sorter.reverseTopPriority(); 773 } else { 774 sorter.setTopPriority(column); 775 } 776 monitor.worked(15); 777 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { 778 @Override 779 public void run() { 780 mTreeViewer.refresh(); 781 updateDirectionIndicator(treeColumn); 782 } 783 }); 784 monitor.done(); 785 } 786 }; 787 } 788 789 private void setSortIndicators() { 790 LintColumn top = getTableSorter().getTopColumn(); 791 TreeColumn[] columns = mTreeViewer.getTree().getColumns(); 792 for (int i = 0; i < columns.length; i++) { 793 TreeColumn column = columns[i]; 794 if (column.getData(KEY_COLUMN).equals(top)) { 795 updateDirectionIndicator(column); 796 return; 797 } 798 } 799 } 800 801 private void updateDirectionIndicator(TreeColumn column) { 802 Tree tree = mTreeViewer.getTree(); 803 tree.setSortColumn(column); 804 if (getTableSorter().isAscending()) { 805 tree.setSortDirection(SWT.UP); 806 } else { 807 tree.setSortDirection(SWT.DOWN); 808 } 809 } 810 811 private TableComparator getTableSorter() { 812 return (TableComparator) mTreeViewer.getComparator(); 813 } 814 815 /** Comparator used to sort the {@link LintList} tree. 816 * <p> 817 * This code is simplified from similar code in 818 * org.eclipse.ui.views.markers.internal.TableComparator 819 */ 820 private class TableComparator extends ViewerComparator { 821 private int[] mPriorities; 822 private boolean[] mDirections; 823 private int[] mDefaultPriorities; 824 private boolean[] mDefaultDirections; 825 826 private TableComparator() { 827 int[] defaultPriorities = new int[mColumns.length]; 828 for (int i = 0; i < defaultPriorities.length; i++) { 829 defaultPriorities[i] = i; 830 } 831 mPriorities = defaultPriorities; 832 833 boolean[] directions = new boolean[mColumns.length]; 834 for (int i = 0; i < directions.length; i++) { 835 directions[i] = mColumns[i].isAscending(); 836 } 837 mDirections = directions; 838 839 mDefaultPriorities = new int[defaultPriorities.length]; 840 System.arraycopy(defaultPriorities, 0, this.mDefaultPriorities, 0, 841 defaultPriorities.length); 842 mDefaultDirections = new boolean[directions.length]; 843 System.arraycopy(directions, 0, this.mDefaultDirections, 0, directions.length); 844 } 845 846 private void resetState() { 847 System.arraycopy(mDefaultPriorities, 0, mPriorities, 0, mPriorities.length); 848 System.arraycopy(mDefaultDirections, 0, mDirections, 0, mDirections.length); 849 } 850 851 private void reverseTopPriority() { 852 mDirections[mPriorities[0]] = !mDirections[mPriorities[0]]; 853 } 854 855 private void setTopPriority(LintColumn property) { 856 for (int i = 0; i < mColumns.length; i++) { 857 if (mColumns[i].equals(property)) { 858 setTopPriority(i); 859 return; 860 } 861 } 862 } 863 864 private void setTopPriority(int priority) { 865 if (priority < 0 || priority >= mPriorities.length) { 866 return; 867 } 868 int index = -1; 869 for (int i = 0; i < mPriorities.length; i++) { 870 if (mPriorities[i] == priority) { 871 index = i; 872 } 873 } 874 if (index == -1) { 875 resetState(); 876 return; 877 } 878 // shift the array 879 for (int i = index; i > 0; i--) { 880 mPriorities[i] = mPriorities[i - 1]; 881 } 882 mPriorities[0] = priority; 883 mDirections[priority] = mDefaultDirections[priority]; 884 } 885 886 private boolean isAscending() { 887 return mDirections[mPriorities[0]]; 888 } 889 890 private int getTopPriority() { 891 return mPriorities[0]; 892 } 893 894 private LintColumn getTopColumn() { 895 return mColumns[getTopPriority()]; 896 } 897 898 @Override 899 public int compare(Viewer viewer, Object e1, Object e2) { 900 return compare((IMarker) e1, (IMarker) e2, 0, true); 901 } 902 903 private int compare(IMarker marker1, IMarker marker2, int depth, 904 boolean continueSearching) { 905 if (depth >= mPriorities.length) { 906 return 0; 907 } 908 int column = mPriorities[depth]; 909 LintColumn property = mColumns[column]; 910 int result = property.compare(marker1, marker2); 911 if (result == 0 && continueSearching) { 912 return compare(marker1, marker2, depth + 1, continueSearching); 913 } 914 return result * (mDirections[column] ? 1 : -1); 915 } 916 } 917 } 918