blob: ccb04bb6b78f1ff4ac43643786d3ecae50c57bb5 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.lint;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Severity;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TreeNodeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.progress.WorkbenchJob;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A tree-table widget which shows a list of lint warnings for an underlying
* {@link IResource} such as a file, a project, or a list of projects.
*/
class LintList extends Composite implements IResourceChangeListener, ControlListener {
private static final Object UPDATE_MARKERS_FAMILY = new Object();
// For persistence:
private static final String KEY_WIDTHS = "lintColWidth"; //$NON-NLS-1$
private static final String KEY_VISIBLE = "lintColVisible"; //$NON-NLS-1$
// Mapping SWT TreeColumns to LintColumns
private static final String KEY_COLUMN = "lintColumn"; //$NON-NLS-1$
private final IWorkbenchPartSite mSite;
private final TreeViewer mTreeViewer;
private final Tree mTree;
private Set<String> mExpandedIds;
private ContentProvider mContentProvider;
private String mSelectedId;
private List<? extends IResource> mResources;
private Configuration mConfiguration;
private final boolean mSingleFile;
private int mErrorCount;
private int mWarningCount;
private final UpdateMarkersJob mUpdateMarkersJob = new UpdateMarkersJob();
private final IssueRegistry mRegistry;
private final IMemento mMemento;
private final LintColumn mMessageColumn = new LintColumn.MessageColumn(this);
private final LintColumn mLineColumn = new LintColumn.LineColumn(this);
private final LintColumn[] mColumns = new LintColumn[] {
mMessageColumn,
new LintColumn.PriorityColumn(this),
new LintColumn.CategoryColumn(this),
new LintColumn.LocationColumn(this),
new LintColumn.FileColumn(this),
new LintColumn.PathColumn(this),
mLineColumn
};
private LintColumn[] mVisibleColumns;
LintList(IWorkbenchPartSite site, Composite parent, IMemento memento, boolean singleFile) {
super(parent, SWT.NONE);
mSingleFile = singleFile;
mMemento = memento;
mSite = site;
mRegistry = EclipseLintClient.getRegistry();
GridLayout gridLayout = new GridLayout(1, false);
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
setLayout(gridLayout);
mTreeViewer = new TreeViewer(this, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
mTree = mTreeViewer.getTree();
mTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
createColumns();
mTreeViewer.setComparator(new TableComparator());
setSortIndicators();
mContentProvider = new ContentProvider();
mTreeViewer.setContentProvider(mContentProvider);
mTree.setLinesVisible(true);
mTree.setHeaderVisible(true);
mTree.addControlListener(this);
ResourcesPlugin.getWorkspace().addResourceChangeListener(
this,
IResourceChangeEvent.POST_CHANGE
| IResourceChangeEvent.PRE_BUILD
| IResourceChangeEvent.POST_BUILD);
// Workaround for https://bugs.eclipse.org/341865
mTree.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
mTreePainted = true;
mTreeViewer.getTree().removePaintListener(this);
}
});
// Remember the most recently selected id category such that we can
// attempt to reselect it after a refresh
mTree.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
List<IMarker> markers = getSelectedMarkers();
if (markers.size() > 0) {
mSelectedId = EclipseLintClient.getId(markers.get(0));
}
}
});
mTree.addTreeListener(new TreeListener() {
@Override
public void treeExpanded(TreeEvent e) {
Object data = e.item.getData();
if (data instanceof IMarker) {
String id = EclipseLintClient.getId((IMarker) data);
if (id != null) {
if (mExpandedIds == null) {
mExpandedIds = new HashSet<String>();
}
mExpandedIds.add(id);
}
}
}
@Override
public void treeCollapsed(TreeEvent e) {
if (mExpandedIds != null) {
Object data = e.item.getData();
if (data instanceof IMarker) {
String id = EclipseLintClient.getId((IMarker) data);
if (id != null) {
mExpandedIds.remove(id);
}
}
}
}
});
}
private boolean mTreePainted;
private void updateColumnWidths() {
Rectangle r = mTree.getClientArea();
int availableWidth = r.width;
// Add all available size to the first column
for (int i = 1; i < mTree.getColumnCount(); i++) {
TreeColumn column = mTree.getColumn(i);
availableWidth -= column.getWidth();
}
if (availableWidth > 100) {
mTree.getColumn(0).setWidth(availableWidth);
}
}
public void setResources(List<? extends IResource> resources) {
mResources = resources;
mConfiguration = null;
for (IResource resource : mResources) {
IProject project = resource.getProject();
if (project != null) {
// For logging only
LintClient client = new EclipseLintClient(null, null, null, false);
mConfiguration = ProjectLintConfiguration.get(client, project, false);
break;
}
}
if (mConfiguration == null) {
mConfiguration = GlobalLintConfiguration.get();
}
List<IMarker> markerList = getMarkers();
mTreeViewer.setInput(markerList);
if (mSingleFile) {
expandAll();
}
// Selecting the first item isn't a good idea since it may not be the first
// item shown in the table (since it does its own sorting), and furthermore we
// may not have all the data yet; this is called when scanning begins, not when
// it's done:
//if (mTree.getItemCount() > 0) {
// mTree.select(mTree.getItem(0));
//}
updateColumnWidths(); // in case mSingleFile changed
}
/** Select the first item */
public void selectFirst() {
if (mTree.getItemCount() > 0) {
mTree.select(mTree.getItem(0));
}
}
private List<IMarker> getMarkers() {
mErrorCount = mWarningCount = 0;
List<IMarker> markerList = new ArrayList<IMarker>();
if (mResources != null) {
for (IResource resource : mResources) {
IMarker[] markers = EclipseLintClient.getMarkers(resource);
for (IMarker marker : markers) {
markerList.add(marker);
int severity = marker.getAttribute(IMarker.SEVERITY, 0);
if (severity == IMarker.SEVERITY_ERROR) {
mErrorCount++;
} else if (severity == IMarker.SEVERITY_WARNING) {
mWarningCount++;
}
}
}
// No need to sort the marker list here; it will be sorted by the tree table model
}
return markerList;
}
public int getErrorCount() {
return mErrorCount;
}
public int getWarningCount() {
return mWarningCount;
}
@Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
public void addSelectionListener(SelectionListener listener) {
mTree.addSelectionListener(listener);
}
public void refresh() {
mTreeViewer.refresh();
}
public List<IMarker> getSelectedMarkers() {
TreeItem[] selection = mTree.getSelection();
List<IMarker> markers = new ArrayList<IMarker>(selection.length);
for (TreeItem item : selection) {
Object data = item.getData();
if (data instanceof IMarker) {
markers.add((IMarker) data);
}
}
return markers;
}
@Override
public void dispose() {
cancelJobs();
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
super.dispose();
}
private class ContentProvider extends TreeNodeContentProvider {
private Map<Object, Object[]> mChildren;
private Map<IMarker, Integer> mTypeCount;
private IMarker[] mTopLevels;
@Override
public Object[] getElements(Object inputElement) {
if (inputElement == null) {
mTypeCount = null;
return new IMarker[0];
}
@SuppressWarnings("unchecked")
List<IMarker> list = (List<IMarker>) inputElement;
// Partition the children such that at the top level we have one
// marker of each type, and below we have all the duplicates of
// each one of those errors. And for errors with multiple locations,
// there is a third level.
Multimap<String, IMarker> types = ArrayListMultimap.<String, IMarker>create(100, 20);
for (IMarker marker : list) {
String id = EclipseLintClient.getId(marker);
types.put(id, marker);
}
Set<String> ids = types.keySet();
mChildren = new HashMap<Object, Object[]>(ids.size());
mTypeCount = new HashMap<IMarker, Integer>(ids.size());
List<IMarker> topLevel = new ArrayList<IMarker>(ids.size());
for (String id : ids) {
Collection<IMarker> markers = types.get(id);
int childCount = markers.size();
// Must sort the list items in order to have a stable first item
// (otherwise preserving expanded paths etc won't work)
TableComparator sorter = getTableSorter();
IMarker[] array = markers.toArray(new IMarker[markers.size()]);
sorter.sort(mTreeViewer, array);
IMarker topMarker = array[0];
mTypeCount.put(topMarker, childCount);
topLevel.add(topMarker);
IMarker[] children = Arrays.copyOfRange(array, 1, array.length);
mChildren.put(topMarker, children);
}
mTopLevels = topLevel.toArray(new IMarker[topLevel.size()]);
return mTopLevels;
}
@Override
public boolean hasChildren(Object element) {
Object[] children = mChildren != null ? mChildren.get(element) : null;
return children != null && children.length > 0;
}
@Override
public Object[] getChildren(Object parentElement) {
Object[] children = mChildren.get(parentElement);
if (children != null) {
return children;
}
return new Object[0];
}
@Override
public Object getParent(Object element) {
return null;
}
public int getCount(IMarker marker) {
if (mTypeCount != null) {
Integer count = mTypeCount.get(marker);
if (count != null) {
return count.intValue();
}
}
return -1;
}
IMarker[] getTopMarkers() {
return mTopLevels;
}
}
private class LintColumnLabelProvider extends StyledCellLabelProvider {
private LintColumn mColumn;
LintColumnLabelProvider(LintColumn column) {
mColumn = column;
}
@Override
public void update(ViewerCell cell) {
Object element = cell.getElement();
cell.setImage(mColumn.getImage((IMarker) element));
StyledString styledString = mColumn.getStyledValue((IMarker) element);
if (styledString == null) {
cell.setText(mColumn.getValue((IMarker) element));
cell.setStyleRanges(null);
} else {
cell.setText(styledString.toString());
cell.setStyleRanges(styledString.getStyleRanges());
}
super.update(cell);
}
}
TreeViewer getTreeViewer() {
return mTreeViewer;
}
Tree getTree() {
return mTree;
}
// ---- Implements IResourceChangeListener ----
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (mResources == null) {
return;
}
IMarkerDelta[] deltas = event.findMarkerDeltas(AdtConstants.MARKER_LINT, true);
if (deltas.length > 0) {
// Update immediately for POST_BUILD events, otherwise do an unconditional
// update after 30 seconds. This matches the logic in Eclipse's ProblemView
// (see the MarkerView class).
if (event.getType() == IResourceChangeEvent.POST_BUILD) {
cancelJobs();
getProgressService().schedule(mUpdateMarkersJob, 100);
} else {
IWorkbenchSiteProgressService progressService = getProgressService();
if (progressService == null) {
mUpdateMarkersJob.schedule(30000);
} else {
getProgressService().schedule(mUpdateMarkersJob, 30000);
}
}
}
}
// ---- Implements ControlListener ----
@Override
public void controlMoved(ControlEvent e) {
}
@Override
public void controlResized(ControlEvent e) {
updateColumnWidths();
}
// ---- Updating Markers ----
private void cancelJobs() {
mUpdateMarkersJob.cancel();
}
protected IWorkbenchSiteProgressService getProgressService() {
if (mSite != null) {
Object siteService = mSite.getAdapter(IWorkbenchSiteProgressService.class);
if (siteService != null) {
return (IWorkbenchSiteProgressService) siteService;
}
}
return null;
}
private class UpdateMarkersJob extends WorkbenchJob {
UpdateMarkersJob() {
super("Updating Lint Markers");
setSystem(true);
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (mTree.isDisposed()) {
return Status.CANCEL_STATUS;
}
mTreeViewer.setInput(null);
List<IMarker> markerList = getMarkers();
if (markerList.size() == 0) {
LayoutEditorDelegate delegate =
LayoutEditorDelegate.fromEditor(AdtUtils.getActiveEditor());
if (delegate != null) {
GraphicalEditorPart g = delegate.getGraphicalEditor();
assert g != null;
LayoutActionBar bar = g == null ? null : g.getLayoutActionBar();
assert bar != null;
if (bar != null) {
bar.updateErrorIndicator();
}
}
}
// Trigger selection update
Event updateEvent = new Event();
updateEvent.widget = mTree;
mTree.notifyListeners(SWT.Selection, updateEvent);
mTreeViewer.setInput(markerList);
mTreeViewer.refresh();
if (mExpandedIds != null) {
List<IMarker> expanded = new ArrayList<IMarker>(mExpandedIds.size());
IMarker[] topMarkers = mContentProvider.getTopMarkers();
if (topMarkers != null) {
for (IMarker marker : topMarkers) {
String id = EclipseLintClient.getId(marker);
if (id != null && mExpandedIds.contains(id)) {
expanded.add(marker);
}
}
}
if (!expanded.isEmpty()) {
mTreeViewer.setExpandedElements(expanded.toArray());
}
}
if (mSelectedId != null) {
IMarker[] topMarkers = mContentProvider.getTopMarkers();
for (IMarker marker : topMarkers) {
if (mSelectedId.equals(EclipseLintClient.getId(marker))) {
mTreeViewer.setSelection(new StructuredSelection(marker), true /*reveal*/);
break;
}
}
}
return Status.OK_STATUS;
}
@Override
public boolean shouldRun() {
// Do not run if the change came in before there is a viewer
return PlatformUI.isWorkbenchRunning();
}
@Override
public boolean belongsTo(Object family) {
return UPDATE_MARKERS_FAMILY == family;
}
}
/**
* Returns the list of resources being shown in the list
*
* @return the list of resources being shown in this composite
*/
public List<? extends IResource> getResources() {
return mResources;
}
/** Expands all nodes */
public void expandAll() {
mTreeViewer.expandAll();
if (mExpandedIds == null) {
mExpandedIds = new HashSet<String>();
}
IMarker[] topMarkers = mContentProvider.getTopMarkers();
if (topMarkers != null) {
for (IMarker marker : topMarkers) {
String id = EclipseLintClient.getId(marker);
if (id != null) {
mExpandedIds.add(id);
}
}
}
}
/** Collapses all nodes */
public void collapseAll() {
mTreeViewer.collapseAll();
mExpandedIds = null;
}
// ---- Column Persistence ----
public void saveState(IMemento memento) {
if (mSingleFile) {
// Don't use persistence for single-file lists: this is a special mode of the
// window where we show a hardcoded set of columns for a single file, deliberately
// omitting the location column etc
return;
}
IMemento columnEntry = memento.createChild(KEY_WIDTHS);
LintColumn[] columns = new LintColumn[mTree.getColumnCount()];
int[] positions = mTree.getColumnOrder();
for (int i = 0; i < columns.length; i++) {
TreeColumn treeColumn = mTree.getColumn(i);
LintColumn column = (LintColumn) treeColumn.getData(KEY_COLUMN);
// Workaround for TeeColumn.getWidth() returning 0 in some cases,
// see https://bugs.eclipse.org/341865 for details.
int width = getColumnWidth(column, mTreePainted);
columnEntry.putInteger(getKey(treeColumn), width);
columns[positions[i]] = column;
}
if (getVisibleColumns() != null) {
IMemento visibleEntry = memento.createChild(KEY_VISIBLE);
for (LintColumn column : getVisibleColumns()) {
visibleEntry.putBoolean(getKey(column), true);
}
}
}
private void createColumns() {
LintColumn[] columns = getVisibleColumns();
TableLayout layout = new TableLayout();
for (int i = 0; i < columns.length; i++) {
LintColumn column = columns[i];
TreeViewerColumn viewerColumn = null;
TreeColumn treeColumn;
viewerColumn = new TreeViewerColumn(mTreeViewer, SWT.NONE);
treeColumn = viewerColumn.getColumn();
treeColumn.setData(KEY_COLUMN, column);
treeColumn.setResizable(true);
treeColumn.addSelectionListener(getHeaderListener());
if (!column.isLeftAligned()) {
treeColumn.setAlignment(SWT.RIGHT);
}
viewerColumn.setLabelProvider(new LintColumnLabelProvider(column));
treeColumn.setText(column.getColumnHeaderText());
treeColumn.setImage(column.getColumnHeaderImage());
IMemento columnWidths = null;
if (mMemento != null && !mSingleFile) {
columnWidths = mMemento.getChild(KEY_WIDTHS);
}
int columnWidth = getColumnWidth(column, false);
if (columnWidths != null) {
columnWidths.putInteger(getKey(column), columnWidth);
}
if (i == 0) {
// The first column should use layout -weights- to get all the
// remaining room
layout.addColumnData(new ColumnWeightData(1, true));
} else if (columnWidth < 0) {
int defaultColumnWidth = column.getPreferredWidth();
layout.addColumnData(new ColumnPixelData(defaultColumnWidth, true, true));
} else {
layout.addColumnData(new ColumnPixelData(columnWidth, true));
}
}
mTreeViewer.getTree().setLayout(layout);
mTree.layout(true);
}
private int getColumnWidth(LintColumn column, boolean getFromUi) {
Tree tree = mTreeViewer.getTree();
if (getFromUi) {
TreeColumn[] columns = tree.getColumns();
for (int i = 0; i < columns.length; i++) {
if (column.equals(columns[i].getData(KEY_COLUMN))) {
return columns[i].getWidth();
}
}
}
int preferredWidth = -1;
if (mMemento != null && !mSingleFile) {
IMemento columnWidths = mMemento.getChild(KEY_WIDTHS);
if (columnWidths != null) {
Integer value = columnWidths.getInteger(getKey(column));
// Make sure we get a useful value
if (value != null && value.intValue() >= 0)
preferredWidth = value.intValue();
}
}
if (preferredWidth <= 0) {
preferredWidth = Math.max(column.getPreferredWidth(), 30);
}
return preferredWidth;
}
private static String getKey(TreeColumn treeColumn) {
return getKey((LintColumn) treeColumn.getData(KEY_COLUMN));
}
private static String getKey(LintColumn column) {
return column.getClass().getSimpleName();
}
private LintColumn[] getVisibleColumns() {
if (mVisibleColumns == null) {
if (mSingleFile) {
// Special mode where we show just lint warnings for a single file:
// use a hardcoded list of columns, not including path/location etc but
// including line numbers (which are normally not shown by default).
mVisibleColumns = new LintColumn[] {
mMessageColumn, mLineColumn
};
} else {
// Generate visible columns based on (a) previously saved window state,
// and (b) default window visible states provided by the columns themselves
List<LintColumn> list = new ArrayList<LintColumn>();
IMemento visibleColumns = null;
if (mMemento != null) {
visibleColumns = mMemento.getChild(KEY_VISIBLE);
}
for (LintColumn column : mColumns) {
if (visibleColumns != null) {
Boolean b = visibleColumns.getBoolean(getKey(column));
if (b != null && b.booleanValue()) {
list.add(column);
}
} else if (column.visibleByDefault()) {
list.add(column);
}
}
if (!list.contains(mMessageColumn)) {
list.add(0, mMessageColumn);
}
mVisibleColumns = list.toArray(new LintColumn[list.size()]);
}
}
return mVisibleColumns;
}
int getCount(IMarker marker) {
return mContentProvider.getCount(marker);
}
Issue getIssue(String id) {
return mRegistry.getIssue(id);
}
Issue getIssue(IMarker marker) {
String id = EclipseLintClient.getId(marker);
return mRegistry.getIssue(id);
}
Severity getSeverity(Issue issue) {
return mConfiguration.getSeverity(issue);
}
// ---- Choosing visible columns ----
public void configureColumns() {
ColumnDialog dialog = new ColumnDialog(getShell(), mColumns, getVisibleColumns());
if (dialog.open() == Window.OK) {
mVisibleColumns = dialog.getSelectedColumns();
// Clear out columns: Must recreate to set the right label provider etc
for (TreeColumn column : mTree.getColumns()) {
column.dispose();
}
createColumns();
mTreeViewer.setComparator(new TableComparator());
setSortIndicators();
mTreeViewer.refresh();
}
}
// ---- Table Sorting ----
private SelectionListener getHeaderListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
final TreeColumn treeColumn = (TreeColumn) e.widget;
final LintColumn column = (LintColumn) treeColumn.getData(KEY_COLUMN);
try {
IWorkbenchSiteProgressService progressService = getProgressService();
if (progressService == null) {
BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
@Override
public void run() {
resortTable(treeColumn, column,
new NullProgressMonitor());
}
});
} else {
getProgressService().busyCursorWhile(new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) {
resortTable(treeColumn, column, monitor);
}
});
}
} catch (InvocationTargetException e1) {
AdtPlugin.log(e1, null);
} catch (InterruptedException e1) {
return;
}
}
private void resortTable(final TreeColumn treeColumn, LintColumn column,
IProgressMonitor monitor) {
TableComparator sorter = getTableSorter();
monitor.beginTask("Sorting", 100);
monitor.worked(10);
if (column.equals(sorter.getTopColumn())) {
sorter.reverseTopPriority();
} else {
sorter.setTopPriority(column);
}
monitor.worked(15);
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
mTreeViewer.refresh();
updateDirectionIndicator(treeColumn);
}
});
monitor.done();
}
};
}
private void setSortIndicators() {
LintColumn top = getTableSorter().getTopColumn();
TreeColumn[] columns = mTreeViewer.getTree().getColumns();
for (int i = 0; i < columns.length; i++) {
TreeColumn column = columns[i];
if (column.getData(KEY_COLUMN).equals(top)) {
updateDirectionIndicator(column);
return;
}
}
}
private void updateDirectionIndicator(TreeColumn column) {
Tree tree = mTreeViewer.getTree();
tree.setSortColumn(column);
if (getTableSorter().isAscending()) {
tree.setSortDirection(SWT.UP);
} else {
tree.setSortDirection(SWT.DOWN);
}
}
private TableComparator getTableSorter() {
return (TableComparator) mTreeViewer.getComparator();
}
/** Comparator used to sort the {@link LintList} tree.
* <p>
* This code is simplified from similar code in
* org.eclipse.ui.views.markers.internal.TableComparator
*/
private class TableComparator extends ViewerComparator {
private int[] mPriorities;
private boolean[] mDirections;
private int[] mDefaultPriorities;
private boolean[] mDefaultDirections;
private TableComparator() {
int[] defaultPriorities = new int[mColumns.length];
for (int i = 0; i < defaultPriorities.length; i++) {
defaultPriorities[i] = i;
}
mPriorities = defaultPriorities;
boolean[] directions = new boolean[mColumns.length];
for (int i = 0; i < directions.length; i++) {
directions[i] = mColumns[i].isAscending();
}
mDirections = directions;
mDefaultPriorities = new int[defaultPriorities.length];
System.arraycopy(defaultPriorities, 0, this.mDefaultPriorities, 0,
defaultPriorities.length);
mDefaultDirections = new boolean[directions.length];
System.arraycopy(directions, 0, this.mDefaultDirections, 0, directions.length);
}
private void resetState() {
System.arraycopy(mDefaultPriorities, 0, mPriorities, 0, mPriorities.length);
System.arraycopy(mDefaultDirections, 0, mDirections, 0, mDirections.length);
}
private void reverseTopPriority() {
mDirections[mPriorities[0]] = !mDirections[mPriorities[0]];
}
private void setTopPriority(LintColumn property) {
for (int i = 0; i < mColumns.length; i++) {
if (mColumns[i].equals(property)) {
setTopPriority(i);
return;
}
}
}
private void setTopPriority(int priority) {
if (priority < 0 || priority >= mPriorities.length) {
return;
}
int index = -1;
for (int i = 0; i < mPriorities.length; i++) {
if (mPriorities[i] == priority) {
index = i;
}
}
if (index == -1) {
resetState();
return;
}
// shift the array
for (int i = index; i > 0; i--) {
mPriorities[i] = mPriorities[i - 1];
}
mPriorities[0] = priority;
mDirections[priority] = mDefaultDirections[priority];
}
private boolean isAscending() {
return mDirections[mPriorities[0]];
}
private int getTopPriority() {
return mPriorities[0];
}
private LintColumn getTopColumn() {
return mColumns[getTopPriority()];
}
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
return compare((IMarker) e1, (IMarker) e2, 0, true);
}
private int compare(IMarker marker1, IMarker marker2, int depth,
boolean continueSearching) {
if (depth >= mPriorities.length) {
return 0;
}
int column = mPriorities[depth];
LintColumn property = mColumns[column];
int result = property.compare(marker1, marker2);
if (result == 0 && continueSearching) {
return compare(marker1, marker2, depth + 1, continueSearching);
}
return result * (mDirections[column] ? 1 : -1);
}
}
}