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