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.
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;
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;
* 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);
}
*
* @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;
}
*
* @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;
}
*
* @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
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$
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;
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;
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;
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
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;
}
//}
}
+ @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
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;
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
// 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
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 ---
/**