| /* |
| * 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); |
| } |
| } |
| } |