OSDN Git Service

Add deletion hooks for view rules
authorTor Norbye <tnorbye@google.com>
Thu, 7 Jul 2011 18:36:11 +0000 (11:36 -0700)
committerTor Norbye <tnorbye@google.com>
Thu, 7 Jul 2011 19:52:41 +0000 (12:52 -0700)
This changeset adds a deletion hook to the view rule interface which
lets specific layouts perform cleanup when some of its children are
removed. This is vital for GridLayout where various rows and columns
need to be cleaned up.

In this changeset, this is used by RelativeLayout to remove constraint
references to widgets that have been deleted. Without this, you could
have Button2 reference Button1, then delete Button1, and when you drop
a new button, it will be assigned the now available name "Button1" and
the old constraint would kick back in!

Change-Id: Ic5829228cff38bfdc291988b1a2ef172e9aa4de5

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IViewRule.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/RelativeLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java

index 1e37245..b351df1 100755 (executable)
@@ -229,6 +229,21 @@ public interface IViewRule {
     void onChildInserted(INode child, INode parent, InsertType insertType);
 
     /**
+     * Called when one or more children are about to be deleted by the user. Note that
+     * children deleted programmatically from view rules (via
+     * {@link INode#removeChild(INode)}) will not notify about deletion.
+     * <p>
+     * Note that this method will be called under an edit lock, so rules can directly
+     * add/remove nodes and attributes as part of the deletion handling (and their
+     * actions will be part of the same undo-unit.)
+     *
+     * @param deleted a nonempty list of children about to be deleted
+     * @param parent the parent of the deleted children (which still contains the children
+     *            since this method is called before the deletion is performed)
+     */
+    void onRemovingChildren(List<INode> deleted, INode parent);
+
+    /**
      * Called by the IDE on the parent layout when a child widget is being resized. This
      * is called once at the beginning of the resizing operation. A horizontal edge,
      * or a vertical edge, or both, can be resized simultaneously.
index 920aaf3..005b2ba 100644 (file)
@@ -29,6 +29,7 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
 
 import com.android.ide.common.api.DropFeedback;
 import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
 import com.android.ide.common.api.IClientRulesEngine;
 import com.android.ide.common.api.IDragElement;
 import com.android.ide.common.api.IGraphics;
@@ -42,7 +43,6 @@ import com.android.ide.common.api.MenuAction;
 import com.android.ide.common.api.Point;
 import com.android.ide.common.api.Rect;
 import com.android.ide.common.api.SegmentType;
-import com.android.ide.common.api.IAttributeInfo.Format;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -458,7 +458,7 @@ public class BaseViewRule implements IViewRule {
      * Returns true if the given node is "filled" (e.g. has layout width set to match
      * parent or fill parent
      */
-    protected boolean isFilled(INode node, String attribute) {
+    protected final boolean isFilled(INode node, String attribute) {
         String value = node.getStringAttr(ANDROID_URI, attribute);
         return VALUE_MATCH_PARENT.equals(value) || VALUE_FILL_PARENT.equals(value);
     }
@@ -469,7 +469,7 @@ public class BaseViewRule implements IViewRule {
      *
      * @return match_parent or fill_parent depending on which is supported by the project
      */
-    protected String getFillParentValueName() {
+    protected final String getFillParentValueName() {
         return supportsMatchParent() ? VALUE_MATCH_PARENT : VALUE_FILL_PARENT;
     }
 
@@ -478,7 +478,7 @@ public class BaseViewRule implements IViewRule {
      *
      * @return true if the project supports match_parent instead of just fill_parent
      */
-    protected boolean supportsMatchParent() {
+    protected final boolean supportsMatchParent() {
         // fill_parent was renamed match_parent in API level 8
         return mRulesEngine.getMinApiLevel() >= 8;
     }
@@ -647,7 +647,7 @@ public class BaseViewRule implements IViewRule {
      *
      * @return a source attribute to use for sample images, never null
      */
-    protected String getSampleImageSrc() {
+    protected final String getSampleImageSrc() {
         // For now, we point to the sample icon which is written into new Android projects
         // created in ADT. We could alternatively look into the project resources folder
         // and try to pick something else, or even return some builtin image resource
@@ -662,6 +662,9 @@ public class BaseViewRule implements IViewRule {
     public void onChildInserted(INode node, INode parent, InsertType insertType) {
     }
 
+    public void onRemovingChildren(List<INode> deleted, INode parent) {
+    }
+
     public static String stripIdPrefix(String id) {
         if (id == null) {
             return ""; //$NON-NLS-1$
index c332649..d53436f 100755 (executable)
@@ -18,6 +18,7 @@ package com.android.ide.common.layout;
 
 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_GRAVITY;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ABOVE;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
@@ -36,6 +37,8 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_CENTER_V
 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF;
+import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
 import static com.android.ide.common.layout.LayoutConstants.VALUE_TRUE;
 
 import com.android.ide.common.api.DropFeedback;
@@ -43,6 +46,7 @@ import com.android.ide.common.api.IDragElement;
 import com.android.ide.common.api.IGraphics;
 import com.android.ide.common.api.IMenuCallback;
 import com.android.ide.common.api.INode;
+import com.android.ide.common.api.INode.IAttribute;
 import com.android.ide.common.api.INodeHandler;
 import com.android.ide.common.api.IViewRule;
 import com.android.ide.common.api.InsertType;
@@ -60,8 +64,10 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * An {@link IViewRule} for android.widget.RelativeLayout and all its derived
@@ -130,11 +136,11 @@ public class RelativeLayoutRule extends BaseLayoutRule {
         super.paintSelectionFeedback(graphics, parentNode, childNodes);
 
         boolean showDependents = true;
-        if (RelativeLayoutRule.sShowStructure) {
+        if (sShowStructure) {
             childNodes = Arrays.asList(parentNode.getChildren());
             // Avoid painting twice - both as incoming and outgoing
             showDependents = false;
-        } else if (!RelativeLayoutRule.sShowConstraints) {
+        } else if (!sShowConstraints) {
             return;
         }
 
@@ -238,6 +244,44 @@ public class RelativeLayoutRule extends BaseLayoutRule {
         //}
     }
 
+    @Override
+    public void onRemovingChildren(List<INode> deleted, INode parent) {
+        super.onRemovingChildren(deleted, parent);
+
+        // Remove any attachments pointing to the deleted nodes.
+
+        // Produce set of attribute values that we want to delete if
+        // present in a layout attribute
+        Set<String> removeValues = new HashSet<String>(deleted.size() * 2);
+        for (INode node : deleted) {
+            String id = node.getStringAttr(ANDROID_URI, ATTR_ID);
+            if (id != null) {
+                removeValues.add(id);
+                if (id.startsWith(NEW_ID_PREFIX)) {
+                    removeValues.add(ID_PREFIX + stripIdPrefix(id));
+                } else {
+                    removeValues.add(NEW_ID_PREFIX + stripIdPrefix(id));
+                }
+            }
+        }
+
+        for (INode child : parent.getChildren()) {
+            if (deleted.contains(child)) {
+                continue;
+            }
+            for (IAttribute attribute : child.getLiveAttributes()) {
+                if (attribute.getName().startsWith(ATTR_LAYOUT_PREFIX) &&
+                        ANDROID_URI.equals(attribute.getUri())) {
+                    String value = attribute.getValue();
+                    if (removeValues.contains(value)) {
+                        // Unset this reference to a deleted widget.
+                        child.setAttribute(ANDROID_URI, attribute.getName(), null);
+                    }
+                }
+            }
+        }
+    }
+
     // ==== Resize Support ====
 
     @Override
index 89b9f8e..0c68600 100644 (file)
@@ -17,12 +17,14 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
 
 import com.android.ide.common.api.IDragElement;
 import com.android.ide.common.api.IDragElement.IDragAttribute;
+import com.android.ide.common.api.INode;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
 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.editors.uimodel.UiElementNode;
@@ -36,7 +38,10 @@ import org.eclipse.swt.dnd.Transfer;
 import org.eclipse.swt.dnd.TransferData;
 import org.eclipse.swt.widgets.Composite;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The {@link ClipboardSupport} class manages the native clipboard, providing operations
@@ -199,6 +204,30 @@ public class ClipboardSupport {
         // resetting the selection.
         mCanvas.getLayoutEditor().wrapUndoEditXmlModel(title, new Runnable() {
             public void run() {
+                // Segment the deleted nodes into clusters of siblings
+                Map<NodeProxy, List<NodeProxy>> clusters =
+                        new HashMap<NodeProxy, List<NodeProxy>>();
+                for (SelectionItem cs : selection) {
+                    NodeProxy node = cs.getNode();
+                    INode parent = node.getParent();
+                    List<NodeProxy> children = clusters.get(parent);
+                    if (children == null) {
+                        children = new ArrayList<NodeProxy>();
+                        clusters.put((NodeProxy) parent, children);
+                    }
+                    children.add(node);
+                }
+
+                // Notify parent views about children getting deleted
+                RulesEngine rulesEngine = mCanvas.getRulesEngine();
+                LayoutEditor editor = mCanvas.getLayoutEditor();
+                for (Map.Entry<NodeProxy, List<NodeProxy>> entry : clusters.entrySet()) {
+                    NodeProxy parent = entry.getKey();
+                    List<NodeProxy> children = entry.getValue();
+                    assert children != null && children.size() > 0;
+                    rulesEngine.callOnRemovingChildren(editor, parent, children);
+                }
+
                 for (SelectionItem cs : selection) {
                     CanvasViewInfo vi = cs.getViewInfo();
                     // You can't delete the root element
index e5f3e15..bc304f9 100755 (executable)
@@ -575,6 +575,21 @@ public class RulesEngine {
         return mInsertType;
     }
 
+    // ---- Deletion ----
+
+    public void callOnRemovingChildren(AndroidXmlEditor editor, NodeProxy parentNode,
+            List<NodeProxy> children) {
+        if (parentNode != null) {
+            UiViewElementNode parentUiNode = parentNode.getNode();
+            IViewRule parentRule = loadRule(parentUiNode);
+            if (parentRule != null) {
+                @SuppressWarnings({"unchecked", "cast", "rawtypes"})
+                List<INode> childrenList = (List<INode>) (List) children;
+                parentRule.onRemovingChildren(childrenList, parentNode);
+            }
+        }
+    }
+
     // ---- private ---
 
     /**