OSDN Git Service

Add drop-support for include tags
authorTor Norbye <tnorbye@google.com>
Wed, 9 Mar 2011 21:18:34 +0000 (13:18 -0800)
committerTor Norbye <tnorbye@google.com>
Thu, 10 Mar 2011 00:08:06 +0000 (16:08 -0800)
Add the include tag back into the palette, and add special drop
support for it such that when it is drop, a resource chooser pops up
and asks you which layout to include. A new validator prevents any
layouts from being chosen that would result in a cyclic
dependency.

This requires some infrastructure changes: First, drop handlers must
distinguish between a view getting created as part of a previewing
operation and getting created interactively. Second, in order to
support cancel removing an inserted include if the user decides not to
set an include, the node wrappers need to support removing an element.
Also, use the metadata originally intended for the preview icon
factory to also bypass palette drag previews for widgets that don't
have UI.

Change-Id: I1bdd1766ca4cfa2fdbca25b77c50c74e9c332cbd

28 files changed:
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/INode.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/InsertType.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java [changed mode: 0755->0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java [changed mode: 0755->0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutWizard.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInWizard.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepositoryTest.java

index efd8086..e849340 100755 (executable)
@@ -133,5 +133,15 @@ public interface IClientRulesEngine {
      *         right, top and bottom margins respectively
      */
     String[] displayMarginInput(String all, String left, String right, String top, String bottom);
+
+    /**
+     * Displays an input dialog tailored for inputing the source of an {@code <include>}
+     * layout tag. This is similar to {@link #displayResourceInput} for resource type
+     * "layout", but should also attempt to filter out layout resources that cannot be
+     * included from the current context (because it would result in a cyclic dependency).
+     *
+     * @return the layout resource to include
+     */
+    String displayIncludeSourceInput();
 }
 
index 8c2bed1..dd64dfa 100755 (executable)
@@ -128,6 +128,15 @@ public interface INode {
     INode insertChildAt(String viewFqcn, int index);
 
     /**
+     * Removes the given XML element child from this node's list of children.
+     * <p/>
+     * This call must be done in the context of editXml().
+     *
+     * @param node The child to be deleted.
+     */
+    void removeChild(INode node);
+
+    /**
      * Sets an attribute for the underlying XML element.
      * Attributes are not written immediately -- instead the XML editor batches edits and
      * then commits them all together at once later.
index 15d3a98..c5a4435 100644 (file)
@@ -24,6 +24,12 @@ public enum InsertType {
     /** The view is newly created (by for example a palette drag) */
     CREATE,
 
+    /**
+     * Same as {@link #CREATE} but when the views are constructed for previewing, for
+     * example as part of a palette drag.
+     */
+    CREATE_PREVIEW,
+
     /** The view is being inserted here because it was moved from somewhere else */
     MOVE,
 
@@ -32,4 +38,15 @@ public enum InsertType {
      * (including drags, but not from the palette)
      */
     PASTE;
+
+    /**
+     * Returns true if this insert type is for a newly created view (for example a by
+     * palette drag). Note that this includes both normal create events as well as well as
+     * views created as part of previewing operations.
+     *
+     * @return true if this {@link InsertType} is for a newly created view
+     */
+    public boolean isCreate() {
+        return this == CREATE || this == CREATE_PREVIEW;
+    }
 }
index d21c43d..1571e03 100644 (file)
@@ -674,21 +674,4 @@ public class BaseViewRule implements IViewRule {
         }
         return value;
     }
-
-    private static class PropertySettingNodeHandler implements INodeHandler {
-        private final String mNamespaceUri;
-        private final String mAttribute;
-        private final String mValue;
-
-        public PropertySettingNodeHandler(String namespaceUri, String attribute, String value) {
-            super();
-            mNamespaceUri = namespaceUri;
-            mAttribute = attribute;
-            mValue = value;
-        }
-
-        public void handle(INode node) {
-            node.setAttribute(mNamespaceUri, mAttribute, mValue);
-        }
-    }
 }
index b3cf5ce..9aa476e 100644 (file)
@@ -37,7 +37,7 @@ public class DialerFilterRule extends BaseViewRule {
         super.onCreate(node, parent, insertType);
 
         // A DialerFilter requires a couple of nested EditTexts with fixed ids:
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             String fillParent = getFillParentValueName();
             INode hint = node.appendChild(FQCN_EDIT_TEXT);
             hint.setAttribute(ANDROID_URI, ATTR_TEXT, "Hint");
index 71bd704..f73c114 100644 (file)
@@ -46,7 +46,7 @@ public class HorizontalScrollViewRule extends FrameLayoutRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             // Insert a horizontal linear layout which is commonly used with horizontal scrollbars
             // as described by the documentation for HorizontalScrollbars.
             INode linearLayout = node.appendChild(FQCN_LINEAR_LAYOUT);
index 4338a9a..fe2e346 100644 (file)
@@ -32,7 +32,7 @@ public class ImageButtonRule extends BaseViewRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc());
         }
     }
index 9d26e75..d0f4da7 100644 (file)
@@ -32,7 +32,7 @@ public class ImageViewRule extends BaseViewRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc());
         }
     }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java
new file mode 100644 (file)
index 0000000..5b1830e
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.ATTR_LAYOUT;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.IViewRule;
+import com.android.ide.common.api.InsertType;
+
+/**
+ * An {@link IViewRule} for the special XML {@code <include>} tag.
+ */
+public class IncludeRule extends BaseViewRule {
+    @Override
+    public void onCreate(INode node, INode parent, InsertType insertType) {
+        // When dropping an include tag, ask the user which layout to include.
+        if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW
+            String include = mRulesEngine.displayIncludeSourceInput();
+            if (include != null) {
+                node.editXml("Include Layout",
+                    // Note -- the layout attribute is NOT in the Android namespace!
+                    new PropertySettingNodeHandler(null, ATTR_LAYOUT, include));
+            } else {
+                // Remove the view; the insertion was canceled
+                parent.removeChild(node);
+            }
+        }
+    }
+}
index 301385a..eb1cf47 100644 (file)
@@ -34,7 +34,7 @@ public class MapViewRule extends BaseViewRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             node.setAttribute(ANDROID_URI, "android:apiKey",  //$NON-NLS-1$
                    "Your API key: see " + //$NON-NLS-1$
                    "http://code.google.com/android/add-ons/google-apis/mapkey.html"); //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java
new file mode 100644 (file)
index 0000000..8c57da8
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.INodeHandler;
+
+/**
+ * A convenience implementation of {@link INodeHandler} for setting a given attribute to a
+ * given value on a particular node.
+ */
+class PropertySettingNodeHandler implements INodeHandler {
+    private final String mNamespaceUri;
+    private final String mAttribute;
+    private final String mValue;
+
+    PropertySettingNodeHandler(String namespaceUri, String attribute, String value) {
+        super();
+        mNamespaceUri = namespaceUri;
+        mAttribute = attribute;
+        mValue = value;
+    }
+
+    public void handle(INode node) {
+        node.setAttribute(mNamespaceUri, mAttribute, mValue);
+    }
+}
\ No newline at end of file
index 039b495..88cce52 100644 (file)
@@ -33,7 +33,7 @@ public class RadioGroupRule extends LinearLayoutRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             for (int i = 0; i < 3; i++) {
                 INode handle = node.appendChild(LayoutConstants.FQCN_RADIO_BUTTON);
                 handle.setAttribute(ANDROID_URI, ATTR_ID, String.format("@+id/radio%d", i));
index e3c349a..bf57456 100644 (file)
@@ -44,7 +44,7 @@ public class ScrollViewRule extends FrameLayoutRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             // Insert a default linear layout (which will in turn be registered as
             // a child of this node and the create child method above will set its
             // fill parent attributes, its id, etc.
index 15c3b4c..4af0ae9 100644 (file)
@@ -37,7 +37,7 @@ public class SlidingDrawerRule extends BaseLayoutRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             String matchParent = getFillParentValueName();
             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent);
             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent);
old mode 100755 (executable)
new mode 100644 (file)
index 92decde..2d7625b
@@ -43,7 +43,7 @@ public class TabHostRule extends IgnoredLayoutRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             String fillParent = getFillParentValueName();
 
             // Configure default Table setup as described in the Table tutorial
old mode 100755 (executable)
new mode 100644 (file)
index 8ec53db..00085c8
@@ -35,7 +35,7 @@ public class WebViewRule extends IgnoredLayoutRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             String matchParent = getFillParentValueName();
             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent);
             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent);
index ca0413e..1200da9 100644 (file)
@@ -26,7 +26,7 @@ public class ZoomButtonRule extends BaseViewRule {
     public void onCreate(INode node, INode parent, InsertType insertType) {
         super.onCreate(node, parent, insertType);
 
-        if (insertType == InsertType.CREATE) {
+        if (insertType.isCreate()) {
             node.setAttribute(ANDROID_URI, ATTR_SRC, "@android:drawable/btn_plus"); //$NON-NLS-1$
         }
     }
index 3461d18..3d3cc57 100644 (file)
@@ -21,7 +21,6 @@ import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
 import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS;
 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
 import static com.android.resources.ResourceType.LAYOUT;
-
 import static org.eclipse.core.resources.IResourceDelta.ADDED;
 import static org.eclipse.core.resources.IResourceDelta.CHANGED;
 import static org.eclipse.core.resources.IResourceDelta.CONTENT;
@@ -67,6 +66,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -186,7 +186,6 @@ public class IncludeFinder {
         }
     }
 
-    /** For test suite only -- do not call */
     @VisibleForTesting
     /* package */ List<String> getIncludedBy(String included) {
         ensureInitialized();
@@ -1001,5 +1000,71 @@ public class IncludeFinder {
         public static Reference create(IFile file) {
             return new Reference(file.getProject(), getMapKey(file));
         }
+
+        /**
+         * Returns the resource name of this layout, such as {@code @layout/foo}.
+         *
+         * @return the resource name
+         */
+        public String getResourceName() {
+            return '@' + FD_RES_LAYOUT + '/' + getName();
+        }
+    }
+
+    /**
+     * Returns a collection of layouts (expressed as resource names, such as
+     * {@code @layout/foo} which would be invalid includes in the given layout
+     * (because it would introduce a cycle)
+     *
+     * @param layout the layout file to check for cyclic dependencies from
+     * @return a collection of layout resources which cannot be included from
+     *         the given layout, never null
+     */
+    public Collection<String> getInvalidIncludes(IFile layout) {
+        IProject project = layout.getProject();
+        Reference self = Reference.create(layout);
+
+        // Add anyone who transitively can reach this file via includes.
+        LinkedList<Reference> queue = new LinkedList<Reference>();
+        List<Reference> invalid = new ArrayList<Reference>();
+        queue.add(self);
+        invalid.add(self);
+        Set<String> seen = new HashSet<String>();
+        seen.add(self.getId());
+        while (!queue.isEmpty()) {
+            Reference reference = queue.removeFirst();
+            String refId = reference.getId();
+
+            // Look up both configuration specific includes as well as includes in the
+            // base versions
+            List<String> included = getIncludedBy(refId);
+            if (refId.indexOf('/') != -1) {
+                List<String> baseIncluded = getIncludedBy(reference.getName());
+                if (included == null) {
+                    included = baseIncluded;
+                } else if (baseIncluded != null) {
+                    included = new ArrayList<String>(included);
+                    included.addAll(baseIncluded);
+                }
+            }
+
+            if (included != null && included.size() > 0) {
+                for (String id : included) {
+                    if (!seen.contains(id)) {
+                        seen.add(id);
+                        Reference ref = new Reference(project, id);
+                        invalid.add(ref);
+                        queue.addLast(ref);
+                    }
+                }
+            }
+        }
+
+        List<String> result = new ArrayList<String>();
+        for (Reference reference : invalid) {
+            result.add(reference.getResourceName());
+        }
+
+        return result;
     }
 }
index fcf87d4..78e906c 100755 (executable)
@@ -42,6 +42,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode;
 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
 import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
 import com.android.ide.eclipse.adt.internal.editors.ui.IDecorContent;
@@ -770,6 +771,12 @@ public class PaletteControl extends Composite {
 
         /** Performs the actual rendering of the descriptor into an image */
         private Image renderPreview() {
+            ViewMetadataRepository repository = ViewMetadataRepository.get();
+            RenderMode renderMode = repository.getRenderMode(mDesc.getFullClassName());
+            if (renderMode == RenderMode.SKIP) {
+                return null;
+            }
+
             // Create blank XML document
             Document document = null;
             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -836,7 +843,7 @@ public class PaletteControl extends Composite {
                 UiViewElementNode childUiNode = (UiViewElementNode) child;
                 NodeProxy childNode = nodeFactory.create(childUiNode);
                 canvas.getRulesEngine().callCreateHooks(layoutEditor,
-                        null, childNode, InsertType.CREATE);
+                        null, childNode, InsertType.CREATE_PREVIEW);
             }
 
             Integer overrideBgColor = null;
index 59c6e15..bd8e75e 100755 (executable)
@@ -203,6 +203,12 @@ public class NodeProxy implements INode {
         return insertOrAppend(viewFqcn, index);
     }
 
+    public void removeChild(INode node) {
+        checkEditOK();
+
+        ((NodeProxy) node).mNode.deleteXmlNode();
+    }
+
     private INode insertOrAppend(String viewFqcn, int index) {
         checkEditOK();
 
index 7a1e06e..dd47986 100755 (executable)
@@ -36,6 +36,7 @@ import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleElement;
 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
@@ -63,6 +64,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -874,6 +876,11 @@ public class RulesEngine {
         }
 
         public String displayResourceInput(String resourceTypeName, String currentValue) {
+            return displayResourceInput(resourceTypeName, currentValue, null);
+        }
+
+        private String displayResourceInput(String resourceTypeName, String currentValue,
+                IInputValidator validator) {
             AndroidXmlEditor editor = mEditor.getLayoutEditor();
             IProject project = editor.getProject();
             ResourceType type = ResourceType.getEnum(resourceTypeName);
@@ -893,6 +900,12 @@ public class RulesEngine {
                 ResourceChooser dlg = new ResourceChooser(project, type, projectRepository,
                         systemRepository, shell);
 
+                if (validator != null) {
+                    // Ensure wide enough to accommodate validator error message
+                    dlg.setSize(70, 10);
+                    dlg.setInputValidator(validator);
+                }
+
                 dlg.setCurrentResource(currentValue);
 
                 if (dlg.open() == Window.OK) {
@@ -922,5 +935,29 @@ public class RulesEngine {
 
             return null;
         }
+
+        public String displayIncludeSourceInput() {
+            AndroidXmlEditor editor = mEditor.getLayoutEditor();
+            IProject project = editor.getProject();
+            if (project != null) {
+                IncludeFinder includeFinder = IncludeFinder.get(project);
+                final Collection<String> invalid =
+                    includeFinder.getInvalidIncludes(editor.getInputFile());
+                IInputValidator validator = new IInputValidator() {
+                    public String isValid(String newText) {
+                        if (invalid.contains(newText)) {
+                            return String.format(
+                                    "Cyclic include, not valid",
+                                    newText);
+                        }
+                        return null;
+                    }
+                };
+
+                return displayResourceInput(ResourceType.LAYOUT.getName(), null, validator);
+            }
+
+            return null;
+        }
     }
 }
index 96e9a01..f11b862 100644 (file)
             fill="opposite"
             render="skip" />
         <view
+            class="include"
+            name="Include Other Layout"
+            render="skip" />
+        <view
             class="android.widget.TableLayout"
             fill="opposite"
             render="skip" />
         <view
             class="android.widget.ZoomControls" />
         <view
-            class="include"
-            skip="true"
-            render="skip" />
-        <view
             class="merge"
             skip="true"
             render="skip" />
index b182ea4..6927568 100644 (file)
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
 
 import static com.android.ide.common.layout.LayoutConstants.FQCN_RELATIVE_LAYOUT;
 import static com.android.ide.common.layout.LayoutConstants.RELATIVE_LAYOUT;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
 
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
 
@@ -33,7 +34,9 @@ import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Label;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 class ChangeLayoutWizard extends VisualRefactoringWizard {
 
@@ -100,9 +103,12 @@ class ChangeLayoutWizard extends VisualRefactoringWizard {
             // We don't exclude RelativeLayout even if the current layout is RelativeLayout,
             // in case you are trying to flatten the hierarchy for a hierarchy that has a
             // RelativeLayout at the root.
+            Set<String> exclude = new HashSet<String>();
+            exclude.add(VIEW_INCLUDE);
             boolean oldIsRelativeLayout = mOldType.equals(FQCN_RELATIVE_LAYOUT);
-            String exclude = oldIsRelativeLayout ? null : mOldType;
-
+            if (oldIsRelativeLayout) {
+                exclude.add(mOldType);
+            }
             mClassNames = WrapInWizard.addLayouts(mProject, mOldType, mTypeCombo, exclude, false);
 
             mTypeCombo.select(0);
index caee8f7..033a657 100644 (file)
@@ -21,6 +21,7 @@ import static com.android.ide.common.layout.LayoutConstants.FQCN_LINEAR_LAYOUT;
 import static com.android.ide.common.layout.LayoutConstants.FQCN_RADIO_BUTTON;
 import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW;
 import static com.android.ide.common.layout.LayoutConstants.RADIO_GROUP;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
 import static com.android.sdklib.SdkConstants.CLASS_VIEW;
 import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
 import static com.android.sdklib.SdkConstants.FN_FRAMEWORK_LIBRARY;
@@ -68,7 +69,9 @@ import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 @SuppressWarnings("restriction") // JDT model access for custom-view class lookup
 class WrapInWizard extends VisualRefactoringWizard {
@@ -139,7 +142,8 @@ class WrapInWizard extends VisualRefactoringWizard {
             mUpdateReferences.setText("Update layout references");
             mUpdateReferences.addSelectionListener(selectionListener);
 
-            mClassNames = addLayouts(mProject, mOldType, mTypeCombo, null, true);
+            Set<String> exclude = Collections.singleton(VIEW_INCLUDE);
+            mClassNames = addLayouts(mProject, mOldType, mTypeCombo, exclude, true);
             mTypeCombo.select(0);
 
             setControl(composite);
@@ -194,8 +198,8 @@ class WrapInWizard extends VisualRefactoringWizard {
         }
     }
 
-    static List<String> addLayouts(IProject project, String oldType, Combo combo, String exclude,
-            boolean addGestureOverlay) {
+    static List<String> addLayouts(IProject project, String oldType, Combo combo,
+            Set<String> exclude, boolean addGestureOverlay) {
         List<String> classNames = new ArrayList<String>();
 
         if (oldType.equals(FQCN_RADIO_BUTTON)) {
@@ -245,7 +249,7 @@ class WrapInWizard extends VisualRefactoringWizard {
                     if (layoutDescriptors != null) {
                         for (ViewElementDescriptor d : layoutDescriptors) {
                             String className = d.getFullClassName();
-                            if (exclude == null || !exclude.equals(className)) {
+                            if (exclude == null || !exclude.contains(className)) {
                                 combo.add(d.getUiName());
                                 classNames.add(className);
                             }
index b57de72..cfa29f8 100644 (file)
@@ -42,6 +42,7 @@ import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IInputValidator;
 import org.eclipse.jface.window.Window;
 import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
 import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
@@ -77,6 +78,7 @@ import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -96,6 +98,7 @@ public class ResourceChooser extends AbstractElementListSelectionDialog {
     private Button mNewButton;
     private String mCurrentResource;
     private final IProject mProject;
+    private IInputValidator mInputValidator;
 
     /**
      * Creates a Resource Chooser dialog.
@@ -135,6 +138,10 @@ public class ResourceChooser extends AbstractElementListSelectionDialog {
         return mCurrentResource;
     }
 
+    public void setInputValidator(IInputValidator inputValidator) {
+        mInputValidator = inputValidator;
+    }
+
     @Override
     protected void computeResult() {
         Object[] elements = getSelectedElements();
@@ -143,6 +150,10 @@ public class ResourceChooser extends AbstractElementListSelectionDialog {
 
             mCurrentResource = ResourceHelper.getXmlString(mResourceType, item,
                     mSystemButton.getSelection());
+
+            if (mInputValidator != null && mInputValidator.isValid(mCurrentResource) != null) {
+                mCurrentResource = null;
+            }
         }
     }
 
@@ -229,6 +240,27 @@ public class ResourceChooser extends AbstractElementListSelectionDialog {
         });
     }
 
+    @Override
+    protected void handleSelectionChanged() {
+        super.handleSelectionChanged();
+        if (mInputValidator != null) {
+            Object[] elements = getSelectedElements();
+            if (elements.length == 1 && elements[0] instanceof ResourceItem) {
+                ResourceItem item = (ResourceItem)elements[0];
+                String current = ResourceHelper.getXmlString(mResourceType, item,
+                        mSystemButton.getSelection());
+                String error = mInputValidator.isValid(current);
+                IStatus status;
+                if (error != null) {
+                    status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error);
+                } else {
+                    status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null);
+                }
+                updateStatus(status);
+            }
+        }
+    }
+
     private String createNewValue(ResourceType type) {
         // Show a name/value dialog entering the key name and the value
         Shell shell = AdtPlugin.getDisplay().getActiveShell();
@@ -376,6 +408,10 @@ public class ResourceChooser extends AbstractElementListSelectionDialog {
             items = mFrameworkResources.getResourceItemsOfType(mResourceType);
         }
 
+        if (items == null) {
+            items = Collections.emptyList();
+        }
+
         ResourceItem[] arrayItems = items.toArray(new ResourceItem[items.size()]);
 
         // sort the array
@@ -410,19 +446,21 @@ public class ResourceChooser extends AbstractElementListSelectionDialog {
         boolean isSystem = false;
         String itemName = null;
 
-        // Is this a system resource?
-        // If not a system resource or if they are not available, this will be a project res.
-        Matcher m = mSystemResourcePattern.matcher(resourceString);
-        if (m.matches()) {
-            itemName = m.group(1);
-            isSystem = true;
-        }
-
-        if (!isSystem && itemName == null) {
-            // Try to match project resource name
-            m = mProjectResourcePattern.matcher(resourceString);
+        if (resourceString != null) {
+            // Is this a system resource?
+            // If not a system resource or if they are not available, this will be a project res.
+            Matcher m = mSystemResourcePattern.matcher(resourceString);
             if (m.matches()) {
                 itemName = m.group(1);
+                isSystem = true;
+            }
+
+            if (!isSystem && itemName == null) {
+                // Try to match project resource name
+                m = mProjectResourcePattern.matcher(resourceString);
+                if (m.matches()) {
+                    itemName = m.group(1);
+                }
             }
         }
 
index 9c60d33..a7aae04 100644 (file)
@@ -244,6 +244,11 @@ public class LayoutTestBase extends TestCase {
             fail("Not supported in tests yet");
             return null;
         }
+
+        public String displayIncludeSourceInput() {
+            fail("Not supported in tests yet");
+            return null;
+        }
     }
 
     public void testDummy() {
index 14430a5..d5f1ae9 100644 (file)
@@ -154,6 +154,13 @@ public class TestNode implements INode {
         return child;
     }
 
+    public void removeChild(INode node) {
+        int index = mChildren.indexOf(node);
+        if (index != -1) {
+            removeChild(index);
+        }
+    }
+
     public boolean setAttribute(String uri, String localName, String value) {
         mAttributes.put(uri + localName, new TestAttribute(uri, localName, value));
         return true;
@@ -164,4 +171,5 @@ public class TestNode implements INode {
         return "TestNode [fqn=" + mFqcn + ", infos=" + mAttributeInfos
                 + ", attributes=" + mAttributes + ", bounds=" + mBounds + "]";
     }
+
 }
\ No newline at end of file
index 36f4ccd..5921e85 100644 (file)
@@ -50,7 +50,7 @@ public class ViewMetadataRepositoryTest extends TestCase {
 
     public void testSkip() throws Exception {
         ViewMetadataRepository repository = ViewMetadataRepository.get();
-        assertTrue(repository.getSkip("include"));
+        assertTrue(repository.getSkip("merge"));
         assertFalse(repository.getSkip("android.widget.Button"));
     }