aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java979
1 files changed, 979 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
new file mode 100644
index 000000000..ccb04bb6b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
@@ -0,0 +1,979 @@
+/*
+ * 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);
+ }
+ }
+}