aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.pngbin0 -> 332 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.pngbin0 -> 297 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.pngbin0 -> 422 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.pngbin0 -> 424 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.pngbin0 -> 372 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.pngbin0 -> 404 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.pngbin0 -> 442 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.pngbin0 -> 387 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.pngbin0 -> 430 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.pngbin0 -> 421 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.pngbin0 -> 486 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.pngbin0 -> 345 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java81
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java111
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java60
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java17
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java125
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java94
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java174
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java167
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java48
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java75
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java29
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java35
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java104
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java182
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java189
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java70
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java2
36 files changed, 1336 insertions, 290 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.png
new file mode 100644
index 0000000..2e63c35
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.png
new file mode 100644
index 0000000..aa6f067
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.png
new file mode 100644
index 0000000..8434b78
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.png
new file mode 100644
index 0000000..43ab63e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.png
new file mode 100644
index 0000000..67fee2d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.png
new file mode 100644
index 0000000..3d0c188
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.png
new file mode 100644
index 0000000..42a9767
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.png
new file mode 100644
index 0000000..743aabc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.png
new file mode 100644
index 0000000..927067a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.png
new file mode 100644
index 0000000..44e66a0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.png
new file mode 100644
index 0000000..853ace9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.png
new file mode 100644
index 0000000..d71d6e3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
index 75326fe..507bed5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
@@ -16,7 +16,6 @@
package com.android.ide.common.layout;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
@@ -34,7 +33,10 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT;
import static com.android.ide.common.layout.LayoutConstants.VALUE_TRUE;
import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_FRAGMENT;
+import static com.android.util.XmlUtils.ANDROID_URI;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.AbstractViewRule;
import com.android.ide.common.api.IAttributeInfo;
import com.android.ide.common.api.IAttributeInfo.Format;
@@ -971,7 +973,8 @@ public class BaseViewRule extends AbstractViewRule {
* @param id attribute to be stripped
* @return the id name without the {@code @+id} or {@code @id} prefix
*/
- public static String stripIdPrefix(String id) {
+ @NonNull
+ public static String stripIdPrefix(@Nullable String id) {
if (id == null) {
return ""; //$NON-NLS-1$
} else if (id.startsWith(NEW_ID_PREFIX)) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
index 0a12ba4..4017cf1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
@@ -20,7 +20,9 @@ package com.android.ide.eclipse.adt.internal.editors;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
import com.android.sdklib.SdkConstants;
+import com.google.common.collect.Maps;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
@@ -36,7 +38,8 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import java.net.URL;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
/**
* Factory to generate icons for Android Editors.
@@ -55,9 +58,11 @@ public class IconFactory {
private static IconFactory sInstance;
- private HashMap<String, Image> mIconMap = new HashMap<String, Image>();
- private HashMap<URL, Image> mUrlMap = new HashMap<URL, Image>();
- private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>();
+ private Map<String, Image> mIconMap = Maps.newHashMap();
+ private Map<URL, Image> mUrlMap = Maps.newHashMap();
+ private Map<String, ImageDescriptor> mImageDescMap = Maps.newHashMap();
+ private Map<Image, Image> mErrorIcons;
+ private Map<Image, Image> mWarningIcons;
private IconFactory() {
}
@@ -85,6 +90,24 @@ public class IconFactory {
}
}
mUrlMap.clear();
+ if (mErrorIcons != null) {
+ for (Image icon : mErrorIcons.values()) {
+ // The map can contain null values
+ if (icon != null) {
+ icon.dispose();
+ }
+ }
+ mErrorIcons = null;
+ }
+ if (mWarningIcons != null) {
+ for (Image icon : mWarningIcons.values()) {
+ // The map can contain null values
+ if (icon != null) {
+ icon.dispose();
+ }
+ }
+ mWarningIcons = null;
+ }
}
/**
@@ -253,6 +276,56 @@ public class IconFactory {
}
/**
+ * Returns an image with an error icon overlaid on it. The icons are cached,
+ * so the base image should be cached as well, or this method will keep
+ * storing new overlays into its cache.
+ *
+ * @param image the base image
+ * @return the combined image
+ */
+ @NonNull
+ public Image addErrorIcon(@NonNull Image image) {
+ if (mErrorIcons != null) {
+ Image combined = mErrorIcons.get(image);
+ if (combined != null) {
+ return combined;
+ }
+ } else {
+ mErrorIcons = new IdentityHashMap<Image, Image>();
+ }
+
+ Image combined = new ErrorImageComposite(image, false).createImage();
+ mErrorIcons.put(image, combined);
+
+ return combined;
+ }
+
+ /**
+ * Returns an image with a warning icon overlaid on it. The icons are
+ * cached, so the base image should be cached as well, or this method will
+ * keep storing new overlays into its cache.
+ *
+ * @param image the base image
+ * @return the combined image
+ */
+ @NonNull
+ public Image addWarningIcon(@NonNull Image image) {
+ if (mWarningIcons != null) {
+ Image combined = mWarningIcons.get(image);
+ if (combined != null) {
+ return combined;
+ }
+ } else {
+ mWarningIcons = new IdentityHashMap<Image, Image>();
+ }
+
+ Image combined = new ErrorImageComposite(image, true).createImage();
+ mWarningIcons.put(image, combined);
+
+ return combined;
+ }
+
+ /**
* A simple image description that generates a 16x16 image which consists
* of a colored letter inside a black & white circle.
*/
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index 4bc7641..8f50001 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -39,13 +39,18 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gle2.OutlinePage;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
+import com.android.tools.lint.client.api.IssueRegistry;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
@@ -78,8 +83,11 @@ import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -138,6 +146,7 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
private final HashMap<String, ElementDescriptor> mUnknownDescriptorMap =
new HashMap<String, ElementDescriptor>();
+ private EclipseLintClient mClient;
/**
* Flag indicating if the replacement file is due to a config change.
@@ -405,27 +414,103 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
return true;
}
+ /**
+ * Returns one of the issues for the given node (there could be more than one)
+ *
+ * @param node the node to look up lint issues for
+ * @return the marker for one of the issues found for the given node
+ */
+ @Nullable
+ public IMarker getIssueForNode(@Nullable UiViewElementNode node) {
+ if (node == null) {
+ return null;
+ }
+
+ if (mClient != null) {
+ return mClient.getIssueForNode(node);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a collection of nodes that have one or more lint warnings
+ * associated with them (retrievable via
+ * {@link #getIssueForNode(UiViewElementNode)})
+ *
+ * @return a collection of nodes, which should <b>not</b> be modified by the
+ * caller
+ */
+ @Nullable
+ public Collection<Node> getLintNodes() {
+ if (mClient != null) {
+ return mClient.getIssueNodes();
+ }
+
+ return null;
+ }
+
@Override
public Job delegateRunLint() {
- Job job = super.delegateRunLint();
+ // We want to customize the {@link EclipseLintClient} created to run this
+ // single file lint, in particular such that we can set the mode which collects
+ // nodes on that lint job, such that we can quickly look up error nodes
+ //Job job = super.delegateRunLint();
+
+ Job job = null;
+ IFile file = getEditor().getInputFile();
+ if (file != null) {
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ List<IFile> resources = Collections.singletonList(file);
+ mClient = new EclipseLintClient(registry,
+ resources, getEditor().getStructuredDocument(), false /*fatal*/);
+
+ mClient.setCollectNodes(true);
+
+ job = EclipseLintRunner.startLint(mClient, resources, file,
+ false /*show*/);
+ }
if (job != null) {
- job.addJobChangeListener(new JobChangeAdapter() {
- @Override
- public void done(IJobChangeEvent event) {
- GraphicalEditorPart graphicalEditor = getGraphicalEditor();
- if (graphicalEditor != null) {
- LayoutActionBar bar = graphicalEditor.getLayoutActionBar();
- if (!bar.isDisposed()) {
- bar.updateErrorIndicator();
- }
- }
- }
- });
+ job.addJobChangeListener(new LintJobListener(getGraphicalEditor()));
}
return job;
}
+ private class LintJobListener extends JobChangeAdapter implements Runnable {
+ private final GraphicalEditorPart mEditor;
+ private final LayoutCanvas mCanvas;
+
+ LintJobListener(GraphicalEditorPart editor) {
+ mEditor = editor;
+ mCanvas = editor.getCanvasControl();;
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ LayoutActionBar bar = mEditor.getLayoutActionBar();
+ if (!bar.isDisposed()) {
+ bar.updateErrorIndicator();
+ }
+
+ // Redraw
+ if (!mCanvas.isDisposed()) {
+ mCanvas.getDisplay().asyncExec(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (!mCanvas.isDisposed()) {
+ mCanvas.redraw();
+
+ OutlinePage outlinePage = mCanvas.getOutlinePage();
+ if (outlinePage != null) {
+ outlinePage.refreshIcons();
+ }
+ }
+ }
+ }
/**
* Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
index 0d7fc28..7cbb8f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
@@ -179,4 +179,8 @@ public class CanvasTransform {
public int inverseTranslate(int screenX) {
return (int) ((screenX - mMargin + mTranslate) / mScale);
}
+
+ public int inverseScale(int canwasW) {
+ return (int) (canwasW / mScale);
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index d4f25f8..493f996 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -17,7 +17,6 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_STRING_PREFIX;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH;
@@ -30,6 +29,7 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
import static com.android.sdklib.SdkConstants.FD_GEN_SOURCES;
+import static com.android.util.XmlUtils.ANDROID_URI;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED;
@@ -74,7 +74,6 @@ import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFa
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
@@ -273,6 +272,7 @@ public class GraphicalEditorPart extends EditorPart
private FlyoutControlComposite mStructureFlyout;
private FlyoutControlComposite mPaletteComposite;
private PropertyFactory mPropertyFactory;
+ private boolean mRenderedOnce;
/**
* Flags which tracks whether this editor is currently active which is set whenever
@@ -400,7 +400,7 @@ public class GraphicalEditorPart extends EditorPart
GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
mActionBar.setLayoutData(detailsData);
if (file != null) {
- mActionBar.updateErrorIndicator(EclipseLintClient.hasMarkers(file));
+ mActionBar.updateErrorIndicator(file);
}
mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER);
@@ -1611,6 +1611,21 @@ public class GraphicalEditorPart extends EditorPart
} else {
// Nope, no missing or broken classes. Clear success, congrats!
hideError();
+
+ // First time this layout is opened, run lint on the file (after a delay)
+ if (!mRenderedOnce) {
+ mRenderedOnce = true;
+ Job job = new Job("Run Lint") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ getEditorDelegate().delegateRunLint();
+ return Status.OK_STATUS;
+ }
+
+ };
+ job.setSystem(true);
+ job.schedule(3000); // 3 seconds
+ }
}
model.refreshUi();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 1c83430..63a36f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -15,8 +15,8 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+import static com.android.util.XmlUtils.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.ide.common.api.INode;
@@ -26,6 +26,7 @@ import com.android.ide.common.api.RuleAction.Separator;
import com.android.ide.common.api.RuleAction.Toggle;
import com.android.ide.common.layout.BaseViewRule;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
@@ -35,6 +36,7 @@ import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
import com.google.common.base.Strings;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
@@ -553,9 +555,10 @@ public class LayoutActionBar extends Composite {
mLintButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- IFile file = mEditor.getEditorDelegate().getEditor().getInputFile();
+ CommonXmlEditor editor = mEditor.getEditorDelegate().getEditor();
+ IFile file = editor.getInputFile();
if (file != null) {
- EclipseLintClient.showErrors(getShell(), file);
+ EclipseLintClient.showErrors(getShell(), file, editor);
}
}
});
@@ -567,7 +570,17 @@ public class LayoutActionBar extends Composite {
* Updates the lint indicator state in the given layout editor
*/
public void updateErrorIndicator() {
- updateErrorIndicator(EclipseLintClient.hasMarkers(mEditor.getEditedFile()));
+ updateErrorIndicator(mEditor.getEditedFile());
+ }
+
+ /**
+ * Updates the lint indicator state for the given file
+ *
+ * @param file the file to show the indicator status for
+ */
+ public void updateErrorIndicator(IFile file) {
+ IMarker[] markers = EclipseLintClient.getMarkers(file);
+ updateErrorIndicator(markers.length);
}
/**
@@ -575,14 +588,14 @@ public class LayoutActionBar extends Composite {
*
* @param hasLintWarnings whether there are lint errors to be shown
*/
- void updateErrorIndicator(final boolean hasLintWarnings) {
+ private void updateErrorIndicator(final int markerCount) {
Display display = getDisplay();
if (display.getThread() != Thread.currentThread()) {
display.asyncExec(new Runnable() {
@Override
public void run() {
if (!isDisposed()) {
- updateErrorIndicator(hasLintWarnings);
+ updateErrorIndicator(markerCount);
}
}
});
@@ -590,10 +603,37 @@ public class LayoutActionBar extends Composite {
}
GridData layoutData = (GridData) mLintToolBar.getLayoutData();
- if (layoutData.exclude == hasLintWarnings) {
- layoutData.exclude = !hasLintWarnings;
- mLintToolBar.setVisible(hasLintWarnings);
- layout();
+ Integer existing = (Integer) mLintToolBar.getData();
+ Integer current = Integer.valueOf(markerCount);
+ if (!current.equals(existing)) {
+ mLintToolBar.setData(current);
+ boolean layout = false;
+ boolean hasLintWarnings = markerCount > 0;
+ if (layoutData.exclude == hasLintWarnings) {
+ layoutData.exclude = !hasLintWarnings;
+ mLintToolBar.setVisible(hasLintWarnings);
+ layout = true;
+ }
+ if (markerCount > 0) {
+ String iconName = "";
+ switch (markerCount) {
+ case 1: iconName = "lint1"; break; //$NON-NLS-1$
+ case 2: iconName = "lint2"; break; //$NON-NLS-1$
+ case 3: iconName = "lint3"; break; //$NON-NLS-1$
+ case 4: iconName = "lint4"; break; //$NON-NLS-1$
+ case 5: iconName = "lint5"; break; //$NON-NLS-1$
+ case 6: iconName = "lint6"; break; //$NON-NLS-1$
+ case 7: iconName = "lint7"; break; //$NON-NLS-1$
+ case 8: iconName = "lint8"; break; //$NON-NLS-1$
+ case 9: iconName = "lint9"; break; //$NON-NLS-1$
+ default: iconName = "lint9p"; break;//$NON-NLS-1$
+ }
+ mLintButton.setImage(IconFactory.getInstance().getIcon(iconName));
+ }
+ if (layout) {
+ layout();
+ }
+ redraw();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 481c215..954305e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -197,6 +197,9 @@ public class LayoutCanvas extends Canvas {
/** The overlay which paints the mouse hover. */
private HoverOverlay mHoverOverlay;
+ /** The overlay which paints the lint warnings */
+ private LintOverlay mLintOverlay;
+
/** The overlay which paints the selection. */
private SelectionOverlay mSelectionOverlay;
@@ -267,6 +270,8 @@ public class LayoutCanvas extends Canvas {
mImageOverlay = new ImageOverlay(this, mHScale, mVScale);
mIncludeOverlay = new IncludeOverlay(this);
mImageOverlay.create(display);
+ mLintOverlay = new LintOverlay(this);
+ mLintOverlay.create(display);
// --- Set up listeners
addPaintListener(new PaintListener() {
@@ -310,6 +315,8 @@ public class LayoutCanvas extends Canvas {
if (editorDelegate != null) {
mOutlinePage = editorDelegate.getGraphicalOutline();
}
+
+ new LintTooltipManager(this).register();
}
private Runnable mZoomCheck = new Runnable() {
@@ -423,6 +430,11 @@ public class LayoutCanvas extends Canvas {
mIncludeOverlay = null;
}
+ if (mLintOverlay != null) {
+ mLintOverlay.dispose();
+ mLintOverlay = null;
+ }
+
mViewHierarchy.dispose();
}
@@ -774,6 +786,11 @@ public class LayoutCanvas extends Canvas {
if (!mHoverOverlay.isHiding()) {
mHoverOverlay.paint(gc);
}
+
+ if (!mLintOverlay.isHiding()) {
+ mLintOverlay.paint(gc);
+ }
+
if (!mIncludeOverlay.isHiding()) {
mIncludeOverlay.paint(gc);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
new file mode 100644
index 0000000..d5d5ce7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 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.editors.layout.gle2;
+
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Rectangle;
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+
+import lombok.ast.libs.org.parboiled.google.collect.Lists;
+
+/**
+ * The {@link LintOverlay} paints an icon over each view that contains at least one
+ * lint error (unless the view is smaller than the icon)
+ */
+public class LintOverlay extends Overlay {
+ /** Approximate size of lint overlay icons */
+ static final int ICON_SIZE = 8;
+ /** Alpha to draw lint overlay icons with */
+ private static final int ALPHA = 192;
+
+ private final LayoutCanvas mCanvas;
+ private Image mWarningImage;
+ private Image mErrorImage;
+
+ /**
+ * Constructs a new {@link LintOverlay}
+ *
+ * @param canvas the associated canvas
+ */
+ public LintOverlay(LayoutCanvas canvas) {
+ mCanvas = canvas;
+ }
+
+ @Override
+ public void paint(GC gc) {
+ LayoutEditorDelegate editor = mCanvas.getEditorDelegate();
+ Collection<Node> nodes = editor.getLintNodes();
+ if (nodes != null && !nodes.isEmpty()) {
+ // Copy list before iterating through it to avoid a concurrent list modification
+ // in case lint runs in the background while painting and updates this list
+ nodes = Lists.newArrayList(nodes);
+ ViewHierarchy hierarchy = mCanvas.getViewHierarchy();
+ Image icon = getWarningIcon();
+ ImageData imageData = icon.getImageData();
+ int iconWidth = imageData.width;
+ int iconHeight = imageData.height;
+ CanvasTransform mHScale = mCanvas.getHorizontalTransform();
+ CanvasTransform mVScale = mCanvas.getVerticalTransform();
+
+ int oldAlpha = gc.getAlpha();
+ try {
+ gc.setAlpha(ALPHA);
+ for (Node node : nodes) {
+ CanvasViewInfo vi = hierarchy.findViewInfoFor(node);
+ if (vi != null) {
+ Rectangle bounds = vi.getAbsRect();
+ int x = mHScale.translate(bounds.x);
+ int y = mVScale.translate(bounds.y);
+ int w = mHScale.scale(bounds.width);
+ int h = mVScale.scale(bounds.height);
+ if (w < iconWidth || h < iconHeight) {
+ // Don't draw badges on tiny widgets (including those
+ // that aren't tiny but are zoomed out too far)
+ continue;
+ }
+
+ x += w - iconWidth;
+ y += h - iconHeight;
+
+ boolean isError = false;
+ IMarker marker = editor.getIssueForNode(vi.getUiViewNode());
+ if (marker != null) {
+ int severity = marker.getAttribute(IMarker.SEVERITY, 0);
+ isError = severity == IMarker.SEVERITY_ERROR;
+ }
+
+ icon = isError ? getErrorIcon() : getWarningIcon();
+
+ gc.drawImage(icon, x, y);
+ }
+ }
+ } finally {
+ gc.setAlpha(oldAlpha);
+ }
+ }
+ }
+
+ private Image getWarningIcon() {
+ if (mWarningImage == null) {
+ mWarningImage = IconFactory.getInstance().getIcon("warning-badge"); //$NON-NLS-1$
+ }
+
+ return mWarningImage;
+ }
+
+ private Image getErrorIcon() {
+ if (mErrorImage == null) {
+ mErrorImage = IconFactory.getInstance().getIcon("error-badge"); //$NON-NLS-1$
+ }
+
+ return mErrorImage;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java
new file mode 100644
index 0000000..bc2f390
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 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.editors.layout.gle2;
+
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+
+import com.android.ide.common.layout.BaseLayoutRule;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+import java.util.List;
+
+/** Actual tooltip showing multiple lines for various widgets that have lint errors */
+class LintTooltip extends Shell {
+ private final LayoutCanvas mCanvas;
+ private final List<UiViewElementNode> mNodes;
+
+ LintTooltip(LayoutCanvas canvas, List<UiViewElementNode> nodes) {
+ super(canvas.getDisplay(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
+ mCanvas = canvas;
+ mNodes = nodes;
+
+ createContents();
+ }
+
+ protected void createContents() {
+ Display display = getDisplay();
+ Color fg = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
+ Color bg = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
+ setBackground(bg);
+ GridLayout gridLayout = new GridLayout(2, false);
+ setLayout(gridLayout);
+
+ LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
+
+ boolean first = true;
+ for (UiViewElementNode node : mNodes) {
+ IMarker marker = delegate.getIssueForNode(node);
+ if (marker != null) {
+ String message = marker.getAttribute(IMarker.MESSAGE, null);
+ if (message != null) {
+ Label icon = new Label(this, SWT.NONE);
+ icon.setForeground(fg);
+ icon.setBackground(bg);
+ icon.setImage(node.getIcon());
+
+ Label label = new Label(this, SWT.WRAP);
+ if (first) {
+ label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
+ first = false;
+ }
+
+ String id = BaseLayoutRule.stripIdPrefix(node.getAttributeValue(ATTR_ID));
+ if (id.isEmpty()) {
+ if (node.getXmlNode() != null) {
+ id = node.getXmlNode().getNodeName();
+ } else {
+ id = node.getDescriptor().getUiName();
+ }
+ }
+
+ label.setText(String.format("%1$s: %2$s", id, message));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java
new file mode 100644
index 0000000..a2bbdf4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 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.editors.layout.gle2;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LintOverlay.ICON_SIZE;
+
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/** Tooltip in the layout editor showing lint errors under the cursor */
+class LintTooltipManager implements Listener {
+ private final LayoutCanvas mCanvas;
+ private Shell mTip = null;
+ private List<UiViewElementNode> mShowingNodes;
+
+ /**
+ * Sets up a custom tooltip when hovering over tree items. It currently displays the error
+ * message for the lint warning associated with each node, if any (and only if the hover
+ * is over the icon portion).
+ */
+ LintTooltipManager(LayoutCanvas canvas) {
+ mCanvas = canvas;
+ }
+
+ void register() {
+ mCanvas.addListener(SWT.Dispose, this);
+ mCanvas.addListener(SWT.KeyDown, this);
+ mCanvas.addListener(SWT.MouseMove, this);
+ mCanvas.addListener(SWT.MouseHover, this);
+ }
+
+ void unregister() {
+ mCanvas.removeListener(SWT.Dispose, this);
+ mCanvas.removeListener(SWT.KeyDown, this);
+ mCanvas.removeListener(SWT.MouseMove, this);
+ mCanvas.removeListener(SWT.MouseHover, this);
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ switch(event.type) {
+ case SWT.MouseMove:
+ // See if we're still overlapping this or *other* errors; if so, keep the
+ // tip up (or update it).
+ if (mShowingNodes != null) {
+ List<UiViewElementNode> nodes = computeNodes(event);
+ if (nodes != null && !nodes.isEmpty()) {
+ if (nodes.equals(mShowingNodes)) {
+ return;
+ } else {
+ show(nodes);
+ }
+ break;
+ }
+ }
+
+ // If not, fall through and hide the tooltip
+
+ //$FALL-THROUGH$
+ case SWT.Dispose:
+ case SWT.FocusOut:
+ case SWT.KeyDown:
+ case SWT.MouseExit:
+ case SWT.MouseDown:
+ hide();
+ break;
+ case SWT.MouseHover:
+ hide();
+ show(event);
+ break;
+ }
+ }
+
+ private void hide() {
+ if (mTip != null) {
+ mTip.dispose();
+ mTip = null;
+ }
+ mShowingNodes = null;
+ }
+
+ private void show(Event event) {
+ List<UiViewElementNode> nodes = computeNodes(event);
+ if (nodes != null && !nodes.isEmpty()) {
+ show(nodes);
+ }
+ }
+
+ /** Show a tooltip listing the lint errors for the given nodes */
+ private void show(List<UiViewElementNode> nodes) {
+ hide();
+
+ mTip = new LintTooltip(mCanvas, nodes);
+ Rectangle rect = mCanvas.getBounds();
+ Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ Point pos = mCanvas.toDisplay(rect.x, rect.y + rect.height);
+ if (size.x > rect.width) {
+ size = mTip.computeSize(rect.width, SWT.DEFAULT);
+ }
+ mTip.setBounds(pos.x, pos.y, size.x, size.y);
+
+ mShowingNodes = nodes;
+ mTip.setVisible(true);
+ }
+
+ /**
+ * Compute the list of nodes which have lint warnings near the given mouse
+ * coordinates
+ *
+ * @param event the mouse cursor event
+ * @return a list of nodes, possibly empty
+ */
+ @Nullable
+ private List<UiViewElementNode> computeNodes(Event event) {
+ LayoutPoint p = ControlPoint.create(mCanvas, event.x, event.y).toLayout();
+ LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
+ ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
+ CanvasTransform mHScale = mCanvas.getHorizontalTransform();
+ CanvasTransform mVScale = mCanvas.getVerticalTransform();
+
+ int layoutIconSize = mHScale.inverseScale(ICON_SIZE);
+ int slop = mVScale.inverseScale(10); // extra space around icon where tip triggers
+
+ Collection<Node> xmlNodes = delegate.getLintNodes();
+ if (xmlNodes == null) {
+ return null;
+ }
+ List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>();
+ for (Node xmlNode : xmlNodes) {
+ CanvasViewInfo v = viewHierarchy.findViewInfoFor(xmlNode);
+ if (v != null) {
+ Rectangle b = v.getAbsRect();
+ int x2 = b.x + b.width;
+ int y2 = b.y + b.height;
+ if (p.x < x2 - layoutIconSize - slop
+ || p.x > x2 + slop
+ || p.y < y2 - layoutIconSize - slop
+ || p.y > y2 + slop) {
+ continue;
+ }
+
+ nodes.add(v.getUiViewNode());
+ }
+ }
+
+ return nodes;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
index eca47ab..8b017f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
@@ -16,25 +16,20 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
-import static com.android.util.XmlUtils.ANDROID_URI;
-import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
import static com.android.ide.common.layout.LayoutConstants.ATTR_COLUMN_COUNT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN_SPAN;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN;
-import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ROW_COUNT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC;
import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX;
-import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
-import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL;
-import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_VIEWTAG;
import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
+import static com.android.util.XmlUtils.ANDROID_URI;
import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER;
import com.android.annotations.VisibleForTesting;
@@ -45,7 +40,6 @@ import com.android.ide.common.layout.GridLayoutRule;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
@@ -53,12 +47,12 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
-import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.util.Pair;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
@@ -94,9 +88,18 @@ import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
+import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
@@ -256,6 +259,11 @@ public class OutlinePage extends ContentOutlinePage
}
}
+ /** Refresh all the icon state */
+ public void refreshIcons() {
+ getTreeViewer().refresh();
+ }
+
/**
* Set whether the outline should be shown in the header
*
@@ -442,6 +450,8 @@ public class OutlinePage extends ContentOutlinePage
public void keyReleased(KeyEvent e) {
}
});
+
+ setupTooltip();
}
private void createPropertySheet() {
@@ -686,45 +696,9 @@ public class OutlinePage extends ContentOutlinePage
element = ((CanvasViewInfo) element).getUiViewNode();
}
- if (element instanceof UiElementNode) {
- UiElementNode node = (UiElementNode) element;
- ElementDescriptor desc = node.getDescriptor();
- if (desc != null) {
- Image img = null;
- // Special case for the common case of vertical linear layouts:
- // show vertical linear icon (the default icon shows horizontal orientation)
- String uiName = desc.getUiName();
- if (uiName.equals(LINEAR_LAYOUT)) {
- Element e = (Element) node.getXmlNode();
- if (VALUE_VERTICAL.equals(e.getAttributeNS(ANDROID_URI,
- ATTR_ORIENTATION))) {
- IconFactory factory = IconFactory.getInstance();
- img = factory.getIcon("VerticalLinearLayout"); //$NON-NLS-1$
- }
- } else if (uiName.equals(VIEW_VIEWTAG)) {
- Node xmlNode = node.getXmlNode();
- if (xmlNode instanceof Element) {
- String className = ((Element) xmlNode).getAttribute(ATTR_CLASS);
- if (className != null && className.length() > 0) {
- int index = className.lastIndexOf('.');
- if (index != -1) {
- className = className.substring(index + 1);
- }
- img = IconFactory.getInstance().getIcon(className);
- }
- }
- }
- if (img == null) {
- img = desc.getGenericIcon();
- }
- if (img != null) {
- if (node.hasError()) {
- return new ErrorImageComposite(img).createImage();
- } else {
- return img;
- }
- }
- }
+ if (element instanceof UiViewElementNode) {
+ UiViewElementNode v = (UiViewElementNode) element;
+ return v.getIcon();
}
return AdtPlugin.getAndroidLogo();
@@ -1275,4 +1249,103 @@ public class OutlinePage extends ContentOutlinePage
makeContributions(null, toolBarManager, null);
toolBarManager.update(false);
}
+
+ /**
+ * Sets up a custom tooltip when hovering over tree items. It currently displays the error
+ * message for the lint warning associated with each node, if any (and only if the hover
+ * is over the icon portion).
+ */
+ private void setupTooltip() {
+ final Tree tree = getTreeViewer().getTree();
+
+ // This is based on SWT Snippet 125
+ final Listener listener = new Listener() {
+ Shell mTip = null;
+ Label mLabel = null;
+
+ @Override
+ public void handleEvent(Event event) {
+ switch(event.type) {
+ case SWT.Dispose:
+ case SWT.KeyDown:
+ case SWT.MouseExit:
+ case SWT.MouseDown:
+ case SWT.MouseMove:
+ if (mTip != null) {
+ mTip.dispose();
+ mTip = null;
+ mLabel = null;
+ }
+ break;
+ case SWT.MouseHover:
+ if (mTip != null) {
+ mTip.dispose();
+ mTip = null;
+ mLabel = null;
+ }
+
+ String tooltip = null;
+
+ TreeItem item = tree.getItem(new Point(event.x, event.y));
+ if (item != null) {
+ Rectangle rect = item.getBounds(0);
+ if (event.x - rect.x > 16) { // 16: Standard width of our outline icons
+ return;
+ }
+
+ Object data = item.getData();
+ if (data != null && data instanceof CanvasViewInfo) {
+ LayoutEditorDelegate editor = mGraphicalEditorPart.getEditorDelegate();
+ CanvasViewInfo vi = (CanvasViewInfo) data;
+ IMarker marker = editor.getIssueForNode(vi.getUiViewNode());
+ if (marker != null) {
+ tooltip = marker.getAttribute(IMarker.MESSAGE, null);
+ }
+ }
+
+ if (tooltip != null) {
+ Shell shell = tree.getShell();
+ Display display = tree.getDisplay();
+
+ Color fg = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
+ Color bg = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
+ mTip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
+ mTip.setBackground(bg);
+ FillLayout layout = new FillLayout();
+ layout.marginWidth = 1;
+ layout.marginHeight = 1;
+ mTip.setLayout(layout);
+ mLabel = new Label(mTip, SWT.WRAP);
+ mLabel.setForeground(fg);
+ mLabel.setBackground(bg);
+ mLabel.setText(tooltip);
+ mLabel.addListener(SWT.MouseExit, this);
+ mLabel.addListener(SWT.MouseDown, this);
+
+ Point pt = tree.toDisplay(rect.x, rect.y + rect.height);
+ Rectangle displayBounds = display.getBounds();
+ // -10: Don't extend -all- the way to the edge of the screen
+ // which would make it look like it has been cropped
+ int availableWidth = displayBounds.x + displayBounds.width - pt.x - 10;
+ if (availableWidth < 80) {
+ availableWidth = 80;
+ }
+ Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ if (size.x > availableWidth) {
+ size = mTip.computeSize(availableWidth, SWT.DEFAULT);
+ }
+ mTip.setBounds(pt.x, pt.y, size.x, size.y);
+
+ mTip.setVisible(true);
+ }
+ }
+ }
+ }
+ };
+
+ tree.addListener(SWT.Dispose, listener);
+ tree.addListener(SWT.KeyDown, listener);
+ tree.addListener(SWT.MouseMove, listener);
+ tree.addListener(SWT.MouseHover, listener);
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
index 579ef44..23e42ef 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
@@ -30,6 +30,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.util.Pair;
import org.eclipse.swt.graphics.Rectangle;
+import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.awt.image.BufferedImage;
@@ -117,6 +118,9 @@ public class ViewHierarchy {
/** Map from nodes to canvas view infos */
private Map<UiViewElementNode, CanvasViewInfo> mNodeToView = Collections.emptyMap();
+ /** Map from DOM nodes to canvas view infos */
+ private Map<Node, CanvasViewInfo> mDomNodeToView = Collections.emptyMap();
+
/**
* Disposes the view hierarchy content.
*/
@@ -220,11 +224,17 @@ public class ViewHierarchy {
mInvisibleParents.clear();
addInvisibleParents(mLastValidViewInfoRoot, explodedNodes);
+ mDomNodeToView = new HashMap<Node, CanvasViewInfo>(mNodeToView.size());
+ for (Map.Entry<UiViewElementNode, CanvasViewInfo> entry : mNodeToView.entrySet()) {
+ mDomNodeToView.put(entry.getKey().getXmlNode(), entry.getValue());
+ }
+
// Update the selection
mCanvas.getSelectionManager().sync();
} else {
mIncludedBounds = null;
mInvisibleParents.clear();
+ mDomNodeToView = Collections.emptyMap();
}
}
@@ -424,37 +434,25 @@ public class ViewHierarchy {
* @return The {@link CanvasViewInfo} corresponding to the given node, or
* null if no match was found.
*/
- public CanvasViewInfo findViewInfoFor(Node node) {
- if (mLastValidViewInfoRoot != null) {
- return findViewInfoForNode(node, mLastValidViewInfoRoot);
- }
- return null;
- }
-
- /**
- * Tries to find a child with the same view XML node in the view info sub-tree.
- * Returns null if not found.
- */
- private CanvasViewInfo findViewInfoForNode(Node xmlNode, CanvasViewInfo canvasViewInfo) {
- if (canvasViewInfo == null) {
- return null;
- }
- if (canvasViewInfo.getXmlNode() == xmlNode) {
- return canvasViewInfo;
- }
+ @Nullable
+ public CanvasViewInfo findViewInfoFor(@Nullable Node node) {
+ CanvasViewInfo vi = mDomNodeToView.get(node);
- // Try to find a matching child
- for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
- CanvasViewInfo v = findViewInfoForNode(xmlNode, child);
- if (v != null) {
- return v;
+ if (vi == null) {
+ if (node == null) {
+ return null;
+ } else if (node.getNodeType() == Node.TEXT_NODE) {
+ return mDomNodeToView.get(node.getParentNode());
+ } else if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
+ return mDomNodeToView.get(((Attr) node).getOwnerElement());
+ } else if (node.getNodeType() == Node.DOCUMENT_NODE) {
+ return mDomNodeToView.get(node.getOwnerDocument().getDocumentElement());
}
}
- return null;
+ return vi;
}
-
/**
* Tries to find the inner most child matching the given x,y coordinates in
* the view info sub-tree, starting at the last know view info root. This
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
index b87435c..ec19ea7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
@@ -16,10 +16,21 @@
package com.android.ide.eclipse.adt.internal.editors.layout.uimodel;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION;
import static com.android.ide.common.layout.LayoutConstants.FQCN_FRAME_LAYOUT;
-
+import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
+import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_VIEWTAG;
+import static com.android.util.XmlUtils.ANDROID_URI;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
@@ -29,7 +40,11 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
import com.android.util.XmlUtils;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
+import org.eclipse.swt.graphics.Image;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
/**
* Specialized version of {@link UiElementNode} for the {@link ViewElementDescriptor}s.
@@ -116,6 +131,64 @@ public class UiViewElementNode extends UiElementNode {
return mCachedAttributeDescriptors;
}
+ public Image getIcon() {
+ ElementDescriptor desc = getDescriptor();
+ if (desc != null) {
+ Image img = null;
+ // Special case for the common case of vertical linear layouts:
+ // show vertical linear icon (the default icon shows horizontal orientation)
+ String uiName = desc.getUiName();
+ IconFactory icons = IconFactory.getInstance();
+ if (uiName.equals(LINEAR_LAYOUT)) {
+ Element e = (Element) getXmlNode();
+ if (VALUE_VERTICAL.equals(e.getAttributeNS(ANDROID_URI,
+ ATTR_ORIENTATION))) {
+ IconFactory factory = icons;
+ img = factory.getIcon("VerticalLinearLayout"); //$NON-NLS-1$
+ }
+ } else if (uiName.equals(VIEW_VIEWTAG)) {
+ Node xmlNode = getXmlNode();
+ if (xmlNode instanceof Element) {
+ String className = ((Element) xmlNode).getAttribute(ATTR_CLASS);
+ if (className != null && className.length() > 0) {
+ int index = className.lastIndexOf('.');
+ if (index != -1) {
+ className = className.substring(index + 1);
+ }
+ img = icons.getIcon(className);
+ }
+ }
+ }
+ if (img == null) {
+ img = desc.getGenericIcon();
+ }
+
+ if (img != null) {
+ AndroidXmlEditor editor = getEditor();
+ if (editor != null) {
+ LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor);
+ if (delegate != null) {
+ IMarker marker = delegate.getIssueForNode(this);
+ if (marker != null) {
+ int severity = marker.getAttribute(IMarker.SEVERITY, 0);
+ if (severity == IMarker.SEVERITY_ERROR) {
+ return icons.addErrorIcon(img);
+ } else {
+ return icons.addWarningIcon(img);
+ }
+ }
+ }
+ }
+
+ return img;
+ }
+
+ return img;
+ }
+
+ return AdtPlugin.getAndroidLogo();
+ }
+
/**
* Sets the parent of this UI node.
* <p/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java
index f6ec863..7085e5d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java
@@ -1,5 +1,10 @@
package com.android.ide.eclipse.adt.internal.editors.ui;
+import static org.eclipse.ui.ISharedImages.IMG_DEC_FIELD_ERROR;
+import static org.eclipse.ui.ISharedImages.IMG_DEC_FIELD_WARNING;
+import static org.eclipse.ui.ISharedImages.IMG_OBJS_ERROR_TSK;
+import static org.eclipse.ui.ISharedImages.IMG_OBJS_WARN_TSK;
+
import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
@@ -19,10 +24,30 @@ public class ErrorImageComposite extends CompositeImageDescriptor {
private ImageDescriptor mErrorImageDescriptor;
private Point mSize;
+ /**
+ * Creates a new {@link ErrorImageComposite}
+ *
+ * @param baseImage the base image to overlay an icon on top of
+ */
public ErrorImageComposite(Image baseImage) {
+ this(baseImage, false);
+ }
+
+ /**
+ * Creates a new {@link ErrorImageComposite}
+ *
+ * @param baseImage the base image to overlay an icon on top of
+ * @param warning if true, add a warning icon, otherwise an error icon
+ */
+ public ErrorImageComposite(Image baseImage, boolean warning) {
mBaseImage = baseImage;
- mErrorImageDescriptor = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(
- ISharedImages.IMG_OBJS_ERROR_TSK);
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ mErrorImageDescriptor = sharedImages.getImageDescriptor(
+ warning ? IMG_DEC_FIELD_WARNING : IMG_DEC_FIELD_ERROR);
+ if (mErrorImageDescriptor == null) {
+ mErrorImageDescriptor = sharedImages.getImageDescriptor(
+ warning ? IMG_OBJS_WARN_TSK : IMG_OBJS_ERROR_TSK);
+ }
mSize = new Point(baseImage.getBounds().width, baseImage.getBounds().height);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
index 1d80c8c..e7037ff 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
@@ -41,7 +41,6 @@ import org.w3c.dom.Node;
/**
* Fix for adding {@code tools:ignore="id"} attributes in XML files.
*/
-@SuppressWarnings("restriction") // DOM model
class AddSuppressAttribute implements ICompletionProposal {
private final AndroidXmlEditor mEditor;
private final String mId;
@@ -98,7 +97,8 @@ class AddSuppressAttribute implements ICompletionProposal {
}
/**
- * Adds any applicable suppress lint fix resolutions into the given list
+ * Returns a quickfix to suppress a specific lint issue id on the node corresponding to
+ * the given marker.
*
* @param editor the associated editor containing the marker
* @param marker the marker to create fixes for
@@ -144,4 +144,35 @@ class AddSuppressAttribute implements ICompletionProposal {
Element element = (Element) node;
return new AddSuppressAttribute(editor, id, marker, element, desc);
}
+
+ /**
+ * Returns a quickfix to suppress a given issue type on the <b>root element</b>
+ * of the given editor.
+ *
+ * @param editor the associated editor containing the marker
+ * @param marker the marker to create fixes for
+ * @param id the issue id
+ * @return a fix for this marker, or null if unable
+ */
+ @Nullable
+ public static AddSuppressAttribute createFixForAll(
+ @NonNull AndroidXmlEditor editor,
+ @NonNull IMarker marker,
+ @NonNull String id) {
+ // This only applies to XML files:
+ String fileName = marker.getResource().getName();
+ if (!fileName.endsWith(DOT_XML)) {
+ return null;
+ }
+
+ Node node = DomUtilities.getNode(editor.getStructuredDocument(), 0);
+ if (node != null) {
+ node = node.getOwnerDocument().getDocumentElement();
+ String desc = String.format("Add ignore '%1$s\' to element", id);
+ Element element = (Element) node;
+ return new AddSuppressAttribute(editor, id, marker, element, desc);
+ }
+
+ return null;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index 68b3407..703be80 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -23,6 +23,7 @@ import com.android.annotations.Nullable;
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.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
@@ -78,13 +79,17 @@ import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.io.File;
import java.io.IOException;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
import lombok.ast.ecj.EcjTreeConverter;
import lombok.ast.grammar.ParseProblem;
@@ -102,6 +107,8 @@ public class EclipseLintClient extends LintClient implements IDomParser {
private boolean mWasFatal;
private boolean mFatalOnly;
private EclipseJavaParser mJavaParser;
+ private boolean mCollectNodes;
+ private Map<Node, IMarker> mNodeMap;
/**
* Creates a new {@link EclipseLintClient}.
@@ -119,6 +126,58 @@ public class EclipseLintClient extends LintClient implements IDomParser {
mFatalOnly = fatalOnly;
}
+ /**
+ * Returns true if lint should only check fatal issues
+ *
+ * @return true if lint should only check fatal issues
+ */
+ public boolean isFatalOnly() {
+ return mFatalOnly;
+ }
+
+ /**
+ * Sets whether the lint client should store associated XML nodes for each
+ * reported issue
+ *
+ * @param collectNodes if true, collect node positions for errors in XML
+ * files, retrievable via the {@link #getIssueForNode} method
+ */
+ public void setCollectNodes(boolean collectNodes) {
+ mCollectNodes = collectNodes;
+ }
+
+ /**
+ * Returns one of the issues for the given node (there could be more than one)
+ *
+ * @param node the node to look up lint issues for
+ * @return the marker for one of the issues found for the given node
+ */
+ @Nullable
+ public IMarker getIssueForNode(@NonNull UiViewElementNode node) {
+ if (mNodeMap != null) {
+ return mNodeMap.get(node.getXmlNode());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a collection of nodes that have one or more lint warnings
+ * associated with them (retrievable via
+ * {@link #getIssueForNode(UiViewElementNode)})
+ *
+ * @return a collection of nodes, which should <b>not</b> be modified by the
+ * caller
+ */
+ @Nullable
+ public Collection<Node> getIssueNodes() {
+ if (mNodeMap != null) {
+ return mNodeMap.keySet();
+ }
+
+ return null;
+ }
+
// ----- Extends LintClient -----
@Override
@@ -190,6 +249,7 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return null;
}
+ @NonNull
@Override
public Configuration getConfiguration(Project project) {
if (project != null) {
@@ -249,6 +309,32 @@ public class EclipseLintClient extends LintClient implements IDomParser {
if (s == Severity.FATAL) {
mWasFatal = true;
}
+
+ if (mCollectNodes && location != null && marker != null) {
+ if (location instanceof LazyLocation) {
+ LazyLocation l = (LazyLocation) location;
+ IndexedRegion region = l.mRegion;
+ if (region instanceof Node) {
+ Node node = (Node) region;
+ if (node instanceof Attr) {
+ node = ((Attr) node).getOwnerElement();
+ }
+ if (mNodeMap == null) {
+ mNodeMap = new WeakHashMap<Node, IMarker>();
+ }
+ IMarker prev = mNodeMap.get(node);
+ if (prev != null) {
+ // Only replace the node if this node has higher priority
+ int prevSeverity = prev.getAttribute(IMarker.SEVERITY, 0);
+ if (prevSeverity < severity) {
+ mNodeMap.put(node, marker);
+ }
+ } else {
+ mNodeMap.put(node, marker);
+ }
+ }
+ }
+ }
}
@Override
@@ -325,16 +411,6 @@ public class EclipseLintClient extends LintClient implements IDomParser {
}
/**
- * Returns whether the given resource has one or more lint markers
- *
- * @param resource the resource to be checked, typically a source file
- * @return true if the given resource has one or more lint markers
- */
- public static boolean hasMarkers(IResource resource) {
- return getMarkers(resource).length > 0;
- }
-
- /**
* Returns the lint marker for the given resource (which may be a project, folder or file)
*
* @param resource the resource to be checked, typically a source file
@@ -532,9 +608,13 @@ public class EclipseLintClient extends LintClient implements IDomParser {
*
* @param shell the parent shell to attach the dialog to
* @param file the file to show the errors for
+ * @param editor the editor for the file, if known
*/
- public static void showErrors(Shell shell, final IFile file) {
- LintListDialog dialog = new LintListDialog(shell, file);
+ public static void showErrors(
+ @NonNull Shell shell,
+ @NonNull IFile file,
+ @Nullable IEditorPart editor) {
+ LintListDialog dialog = new LintListDialog(shell, file, editor);
dialog.open();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
index 25e80fc..d69412b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
@@ -15,40 +15,24 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
-import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
-import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
-import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.sdklib.SdkConstants;
import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Shell;
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -75,7 +59,7 @@ public class EclipseLintRunner {
@Nullable IDocument doc,
boolean fatalOnly) {
resources = addLibraries(resources);
- CheckFileJob job = (CheckFileJob) startLint(resources, source, doc, fatalOnly,
+ LintJob job = (LintJob) startLint(resources, source, doc, fatalOnly,
false /*show*/);
try {
job.join();
@@ -116,12 +100,38 @@ public class EclipseLintRunner {
@Nullable IDocument doc,
boolean fatalOnly,
boolean show) {
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ EclipseLintClient client = new EclipseLintClient(registry, resources, doc, fatalOnly);
+ return startLint(client, resources, source, show);
+ }
+
+ /**
+ * Runs lint and updates the markers. Does not wait for the job to finish -
+ * just returns immediately.
+ *
+ * @param client the lint client receiving issue reports etc
+ * @param resources the resources (project, folder or file) to be analyzed
+ * @param source if checking a single source file, the source file. When
+ * single checking an XML file, this is typically the same as the
+ * file passed in the list in the first parameter, but when
+ * checking the .class files of a Java file for example, the
+ * .class file and all the inner classes of the Java file are
+ * passed in the first parameter, and the corresponding .java
+ * source file is passed here.
+ * @param show if true, show the results in a {@link LintViewPart}
+ * @return the job running lint in the background.
+ */
+ public static Job startLint(
+ @NonNull EclipseLintClient client,
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source,
+ boolean show) {
if (resources != null && !resources.isEmpty()) {
resources = addLibraries(resources);
cancelCurrentJobs(false);
- CheckFileJob job = new CheckFileJob(resources, source, doc, fatalOnly);
+ LintJob job = new LintJob(client, resources, source);
job.schedule();
if (show) {
@@ -162,16 +172,10 @@ public class EclipseLintRunner {
return true;
}
- /** Returns the current lint jobs, if any (never returns null but array may be empty) */
- static Job[] getCurrentJobs() {
- IJobManager jobManager = Job.getJobManager();
- return jobManager.find(CheckFileJob.FAMILY_RUN_LINT);
- }
-
/** Cancels the current lint jobs, if any, and optionally waits for them to finish */
static void cancelCurrentJobs(boolean wait) {
// Cancel any current running jobs first
- Job[] currentJobs = getCurrentJobs();
+ Job[] currentJobs = LintJob.getCurrentJobs();
for (Job job : currentJobs) {
job.cancel();
}
@@ -229,132 +233,4 @@ public class EclipseLintRunner {
return resources;
}
-
- private static final class CheckFileJob extends Job {
- /** Job family */
- private static final Object FAMILY_RUN_LINT = new Object();
- private final List<? extends IResource> mResources;
- private final IResource mSource;
- private final IDocument mDocument;
- private LintDriver mLint;
- private boolean mFatal;
- private boolean mFatalOnly;
-
- private CheckFileJob(
- @NonNull List<? extends IResource> resources,
- @Nullable IResource source,
- @Nullable IDocument doc,
- boolean fatalOnly) {
- super("Running Android Lint");
- mResources = resources;
- mSource = source;
- mDocument = doc;
- mFatalOnly = fatalOnly;
- }
-
- @Override
- public boolean belongsTo(Object family) {
- return family == FAMILY_RUN_LINT;
- }
-
- @Override
- protected void canceling() {
- super.canceling();
- if (mLint != null) {
- mLint.cancel();
- }
- }
-
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
- IssueRegistry registry = EclipseLintClient.getRegistry();
- EnumSet<Scope> scope = null;
- List<File> files = new ArrayList<File>(mResources.size());
- for (IResource resource : mResources) {
- File file = AdtUtils.getAbsolutePath(resource).toFile();
- files.add(file);
-
- if (resource instanceof IProject && mSource == null) {
- scope = Scope.ALL;
- } else {
- String name = resource.getName();
- if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
- if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
- scope = EnumSet.of(Scope.MANIFEST);
- } else {
- scope = Scope.RESOURCE_FILE_SCOPE;
- }
- } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
- if (scope != null) {
- if (!scope.contains(Scope.JAVA_FILE)) {
- scope = EnumSet.copyOf(scope);
- scope.add(Scope.JAVA_FILE);
- }
- } else {
- scope = Scope.JAVA_FILE_SCOPE;
- }
- } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
- if (scope != null) {
- if (!scope.contains(Scope.CLASS_FILE)) {
- scope = EnumSet.copyOf(scope);
- scope.add(Scope.CLASS_FILE);
- }
- } else {
- scope = Scope.CLASS_FILE_SCOPE;
- }
- } else {
- return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
- "Only XML files are supported for single file lint", null); //$NON-NLS-1$
- }
- }
- }
- if (scope == null) {
- scope = Scope.ALL;
- }
- if (mSource == null) {
- assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
- }
- // Check single file?
- if (mSource != null) {
- // Delete specific markers
- IMarker[] markers = EclipseLintClient.getMarkers(mSource);
- for (IMarker marker : markers) {
- String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
- Issue issue = registry.getIssue(id);
- if (issue == null) {
- continue;
- }
- if (issue.isAdequate(scope)) {
- marker.delete();
- }
- }
- } else {
- EclipseLintClient.clearMarkers(mResources);
- }
-
- EclipseLintClient client = new EclipseLintClient(registry, mResources,
- mDocument, mFatalOnly);
- mLint = new LintDriver(registry, client);
- mLint.analyze(files, scope);
- mFatal = client.hasFatalErrors();
- return Status.OK_STATUS;
- } catch (Exception e) {
- return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
- "Failed", e); //$NON-NLS-1$
- } finally {
- if (monitor != null) {
- monitor.done();
- }
- }
- }
-
- /**
- * Returns true if a fatal error was encountered
- */
- boolean isFatal() {
- return mFatal;
- }
- }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java
index 8da3465..646d752 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.tools.lint.client.api.Configuration;
@@ -47,6 +48,7 @@ class GlobalLintConfiguration extends Configuration {
*
* @return the singleton configuration
*/
+ @NonNull
public static GlobalLintConfiguration get() {
return sInstance;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
new file mode 100644
index 0000000..aaf55c6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 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 static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.sdklib.SdkConstants;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.eclipse.core.runtime.jobs.Job;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/** Job to check lint on a set of resources */
+final class LintJob extends Job {
+ /** Job family */
+ private static final Object FAMILY_RUN_LINT = new Object();
+ private final EclipseLintClient mClient;
+ private final List<? extends IResource> mResources;
+ private final IResource mSource;
+ private LintDriver mLint;
+ private boolean mFatal;
+
+ LintJob(
+ @NonNull EclipseLintClient client,
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source) {
+ super("Running Android Lint");
+ mClient = client;
+ mResources = resources;
+ mSource = source;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == FAMILY_RUN_LINT;
+ }
+
+ @Override
+ protected void canceling() {
+ super.canceling();
+ if (mLint != null) {
+ mLint.cancel();
+ }
+ }
+
+ @Override
+ @NonNull
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ EnumSet<Scope> scope = null;
+ List<File> files = new ArrayList<File>(mResources.size());
+ for (IResource resource : mResources) {
+ File file = AdtUtils.getAbsolutePath(resource).toFile();
+ files.add(file);
+
+ if (resource instanceof IProject && mSource == null) {
+ scope = Scope.ALL;
+ } else {
+ String name = resource.getName();
+ if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
+ if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
+ scope = EnumSet.of(Scope.MANIFEST);
+ } else {
+ scope = Scope.RESOURCE_FILE_SCOPE;
+ }
+ } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
+ if (scope != null) {
+ if (!scope.contains(Scope.JAVA_FILE)) {
+ scope = EnumSet.copyOf(scope);
+ scope.add(Scope.JAVA_FILE);
+ }
+ } else {
+ scope = Scope.JAVA_FILE_SCOPE;
+ }
+ } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
+ if (scope != null) {
+ if (!scope.contains(Scope.CLASS_FILE)) {
+ scope = EnumSet.copyOf(scope);
+ scope.add(Scope.CLASS_FILE);
+ }
+ } else {
+ scope = Scope.CLASS_FILE_SCOPE;
+ }
+ } else {
+ return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+ "Only XML & Java files are supported for single file lint", null); //$NON-NLS-1$
+ }
+ }
+ }
+ if (scope == null) {
+ scope = Scope.ALL;
+ }
+ if (mSource == null) {
+ assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
+ }
+ // Check single file?
+ if (mSource != null) {
+ // Delete specific markers
+ IMarker[] markers = EclipseLintClient.getMarkers(mSource);
+ for (IMarker marker : markers) {
+ String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY, "");
+ Issue issue = registry.getIssue(id);
+ if (issue == null) {
+ continue;
+ }
+ if (issue.isAdequate(scope)) {
+ marker.delete();
+ }
+ }
+ } else {
+ EclipseLintClient.clearMarkers(mResources);
+ }
+
+ mLint = new LintDriver(registry, mClient);
+ mLint.analyze(files, scope);
+ mFatal = mClient.hasFatalErrors();
+ return Status.OK_STATUS;
+ } catch (Exception e) {
+ return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+ "Failed", e); //$NON-NLS-1$
+ } finally {
+ if (monitor != null) {
+ monitor.done();
+ }
+ }
+ }
+
+ /**
+ * Returns true if a fatal error was encountered
+ *
+ * @return true if a fatal error was encountered
+ */
+ public boolean isFatal() {
+ return mFatal;
+ }
+
+ /**
+ * Returns the associated lint client
+ *
+ * @return the associated lint client
+ */
+ @NonNull
+ public EclipseLintClient getLintClient() {
+ return mClient;
+ }
+
+ /** Returns the current lint jobs, if any (never returns null but array may be empty) */
+ @NonNull
+ static Job[] getCurrentJobs() {
+ IJobManager jobManager = Job.getJobManager();
+ return jobManager.find(LintJob.FAMILY_RUN_LINT);
+ }
+} \ No newline at end of file
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
index 561c5fd..3b01e05 100644
--- 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
@@ -261,6 +261,13 @@ class LintList extends Composite implements IResourceChangeListener, ControlList
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>();
@@ -585,11 +592,25 @@ class LintList extends Composite implements IResourceChangeListener, ControlList
/** 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 ----
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
index d39916f..bd42906 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
@@ -15,6 +15,8 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
@@ -45,22 +47,30 @@ import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
@SuppressWarnings("restriction") // WST DOM access
class LintListDialog extends TitleAreaDialog implements SelectionListener {
private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$
- private IFile mFile;
+ private final IFile mFile;
+ private final IEditorPart mEditor;
private Button mFixButton;
private Button mIgnoreButton;
+ private Button mIgnoreAllButton;
private Button mShowButton;
private Text mDetailsText;
private Button mIgnoreTypeButton;
private LintList mList;
- LintListDialog(Shell parentShell, IFile file) {
+ LintListDialog(
+ @NonNull Shell parentShell,
+ @NonNull IFile file,
+ @Nullable IEditorPart editor) {
super(parentShell);
- this.mFile = file;
+ mFile = file;
+ mEditor = editor;
}
@Override
@@ -97,31 +107,42 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
if (page.getActivePart() != null) {
site = page.getActivePart().getSite();
}
+
mList = new LintList(site, container, null /*memento*/, true /*singleFile*/);
- mList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 5));
+ mList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 6));
mShowButton = new Button(container, SWT.NONE);
mShowButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
mShowButton.setText("Show");
+ mShowButton.setToolTipText("Opens the editor to reveal the XML with the issue");
mShowButton.addSelectionListener(this);
+ mFixButton = new Button(container, SWT.NONE);
+ mFixButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mFixButton.setText("Fix");
+ mFixButton.setToolTipText("Automatically corrects the problem, if possible");
+ mFixButton.setEnabled(false);
+ mFixButton.addSelectionListener(this);
+
mIgnoreButton = new Button(container, SWT.NONE);
mIgnoreButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
- mIgnoreButton.setText("Ignore");
- mIgnoreButton.setEnabled(false);
+ mIgnoreButton.setText("Suppress Issue");
+ mIgnoreButton.setToolTipText("Adds a special attribute in the layout to suppress this specific warning");
mIgnoreButton.addSelectionListener(this);
+ mIgnoreAllButton = new Button(container, SWT.NONE);
+ mIgnoreAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mIgnoreAllButton.setText("Suppress in Layout");
+ mIgnoreAllButton.setEnabled(mEditor instanceof AndroidXmlEditor);
+ mIgnoreAllButton.setToolTipText("Adds an attribute on the root element to suppress all issues of this type in this layout");
+ mIgnoreAllButton.addSelectionListener(this);
+
mIgnoreTypeButton = new Button(container, SWT.NONE);
mIgnoreTypeButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
- mIgnoreTypeButton.setText("Ignore Type");
+ mIgnoreTypeButton.setText("Disable Issue Type");
+ mIgnoreTypeButton.setToolTipText("Turns off checking for this type of error everywhere");
mIgnoreTypeButton.addSelectionListener(this);
- mFixButton = new Button(container, SWT.NONE);
- mFixButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
- mFixButton.setText("Fix");
- mFixButton.setEnabled(false);
- mFixButton.addSelectionListener(this);
-
new Label(container, SWT.NONE);
mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP
@@ -138,6 +159,7 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
mList.addSelectionListener(this);
mList.setResources(Collections.<IResource>singletonList(mFile));
+ mList.selectFirst();
if (mList.getSelectedMarkers().size() > 0) {
updateSelectionState();
}
@@ -205,6 +227,28 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
LintFixGenerator.suppressDetector(id, true, mFile, true /*all*/);
}
}
+ } else if (source == mIgnoreButton) {
+ for (IMarker marker : mList.getSelectedMarkers()) {
+ LintFixGenerator.addSuppressAnnotation(marker);
+ }
+ } else if (source == mIgnoreAllButton) {
+ Set<String> ids = new HashSet<String>();
+ for (IMarker marker : mList.getSelectedMarkers()) {
+ String id = EclipseLintClient.getId(marker);
+ if (id != null && !ids.contains(id)) {
+ ids.add(id);
+ if (mEditor instanceof AndroidXmlEditor) {
+ AndroidXmlEditor editor = (AndroidXmlEditor) mEditor;
+ AddSuppressAttribute fix = AddSuppressAttribute.createFixForAll(editor,
+ marker, id);
+ if (fix != null) {
+ IStructuredDocument document = editor.getStructuredDocument();
+ fix.apply(document);
+ }
+ }
+ }
+ }
+ mList.refresh();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
index 39c6d25..3761fde 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
@@ -291,7 +291,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha
}
private void refreshStopIcon() {
- Job[] currentJobs = EclipseLintRunner.getCurrentJobs();
+ Job[] currentJobs = LintJob.getCurrentJobs();
if (currentJobs.length > 0) {
ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
mRefreshAction.setImageDescriptor(sharedImages.getImageDescriptor(
@@ -466,7 +466,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha
workbench.saveAllEditors(false /*confirm*/);
}
- Job[] jobs = EclipseLintRunner.getCurrentJobs();
+ Job[] jobs = LintJob.getCurrentJobs();
if (jobs.length > 0) {
EclipseLintRunner.cancelCurrentJobs(false);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
index 827f8a4..b2d7361 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -338,6 +338,10 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
prefs.setLintOnSave(mCheckFileCheckbox.getSelection());
}
+ if (mConfiguration == null) {
+ return;
+ }
+
mConfiguration.startBulkEditing();
try {
// Severities
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java
index 8f6de3a..45590b7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java
@@ -163,7 +163,7 @@ public class CompatibilityLibraryHelper {
for (IJavaProject javaProject : AdtUtils.getOpenAndroidProjects()) {
IProject project = javaProject.getProject();
ProjectState state = Sdk.getProjectState(project);
- if (state.isLibrary()) {
+ if (state != null && state.isLibrary()) {
ManifestInfo manifestInfo = ManifestInfo.get(project);
if (manifestInfo.getPackage().equals("android.support.v7.gridlayout")) { //$NON-NLS-1$
return project;