diff options
author | Bob Badour <bbadour@google.com> | 2020-05-06 14:17:12 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-05-06 14:17:12 +0000 |
commit | fc7cda06f54946e3a03ea008c1ba086d90aeef84 (patch) | |
tree | fd845444b59dfc72656b7781596e0b1a0662c4c7 /eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java | |
parent | 1aa104fb55f1b87494e4e2bc38e23416f486adfd (diff) | |
parent | 512ba8f80c0a2422f9e58a1401cb142db190a56c (diff) | |
download | sdk-fc7cda06f54946e3a03ea008c1ba086d90aeef84.tar.gz |
Merge "Revert "Remove unused project.""
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java new file mode 100644 index 000000000..3eac510df --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java @@ -0,0 +1,267 @@ +/* + * 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.common.layout.relative; + +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ATTR_ID; +import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN; +import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; +import static com.android.SdkConstants.ID_PREFIX; +import static com.android.SdkConstants.NEW_ID_PREFIX; +import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix; +import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_HORIZONTAL; +import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_VERTICAL; + +import com.android.SdkConstants; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.api.INode; +import com.android.ide.common.api.INode.IAttribute; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Handles deletions in a relative layout, transferring constraints across + * deleted nodes + * <p> + * TODO: Consider adding the + * {@link SdkConstants#ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING} attribute to a + * node if it's pointing to a node which is deleted and which has no transitive + * reference to another node. + */ +public class DeletionHandler { + private final INode mLayout; + private final INode[] mChildren; + private final List<INode> mDeleted; + private final Set<String> mDeletedIds; + private final Map<String, INode> mNodeMap; + private final List<INode> mMoved; + + /** + * Creates a new {@link DeletionHandler} + * + * @param deleted the deleted nodes + * @param moved nodes that were moved (e.g. deleted, but also inserted elsewhere) + * @param layout the parent layout of the deleted nodes + */ + public DeletionHandler(@NonNull List<INode> deleted, @NonNull List<INode> moved, + @NonNull INode layout) { + mDeleted = deleted; + mMoved = moved; + mLayout = layout; + + mChildren = mLayout.getChildren(); + mNodeMap = Maps.newHashMapWithExpectedSize(mChildren.length); + for (INode child : mChildren) { + String id = child.getStringAttr(ANDROID_URI, ATTR_ID); + if (id != null) { + mNodeMap.put(stripIdPrefix(id), child); + } + } + + mDeletedIds = Sets.newHashSetWithExpectedSize(mDeleted.size()); + for (INode node : mDeleted) { + String id = node.getStringAttr(ANDROID_URI, ATTR_ID); + if (id != null) { + mDeletedIds.add(stripIdPrefix(id)); + } + } + + // Any widgets that remain (e.g. typically because they were moved) should + // keep their incoming dependencies + for (INode node : mMoved) { + String id = node.getStringAttr(ANDROID_URI, ATTR_ID); + if (id != null) { + mDeletedIds.remove(stripIdPrefix(id)); + } + } + } + + @Nullable + private static String getId(@NonNull IAttribute attribute) { + if (attribute.getName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX) + && ANDROID_URI.equals(attribute.getUri()) + && !attribute.getName().startsWith(ATTR_LAYOUT_MARGIN)) { + String id = attribute.getValue(); + // It might not be an id reference, so check manually rather than just + // calling stripIdPrefix(): + if (id.startsWith(NEW_ID_PREFIX)) { + return id.substring(NEW_ID_PREFIX.length()); + } else if (id.startsWith(ID_PREFIX)) { + return id.substring(ID_PREFIX.length()); + } + } + + return null; + } + + /** + * Updates the constraints in the layout to handle deletion of a set of + * nodes. This ensures that any constraints pointing to one of the deleted + * nodes are changed properly to point to a non-deleted node with similar + * constraints. + */ + public void updateConstraints() { + if (mChildren.length == mDeleted.size()) { + // Deleting everything: Nothing to be done + return; + } + + // Now remove incoming edges to any views that were deleted. If possible, + // don't just delete them but replace them with a transitive constraint, e.g. + // if we have "A <= B <= C" and "B" is removed, then we end up with "A <= C", + + for (INode child : mChildren) { + if (mDeleted.contains(child)) { + continue; + } + + for (IAttribute attribute : child.getLiveAttributes()) { + String id = getId(attribute); + if (id != null) { + if (mDeletedIds.contains(id)) { + // Unset this reference to a deleted widget. It might be + // replaced if the pointed to node points to some other node + // on the same side, but it may use a different constraint name, + // or have none at all (e.g. parent). + String name = attribute.getName(); + child.setAttribute(ANDROID_URI, name, null); + + INode deleted = mNodeMap.get(id); + if (deleted != null) { + ConstraintType type = ConstraintType.fromAttribute(name); + if (type != null) { + transfer(deleted, child, type, 0); + } + } + } + } + } + } + } + + private void transfer(INode deleted, INode target, ConstraintType targetType, int depth) { + if (depth == 20) { + // Prevent really deep flow or unbounded recursion in case there is a bug in + // the cycle detection code + return; + } + + assert mDeleted.contains(deleted); + + for (IAttribute attribute : deleted.getLiveAttributes()) { + String name = attribute.getName(); + ConstraintType type = ConstraintType.fromAttribute(name); + if (type == null) { + continue; + } + + ConstraintType transfer = getCompatibleConstraint(type, targetType); + if (transfer != null) { + String id = getId(attribute); + if (id != null) { + if (mDeletedIds.contains(id)) { + INode nextDeleted = mNodeMap.get(id); + if (nextDeleted != null) { + // Points to another deleted node: recurse + transfer(nextDeleted, target, targetType, depth + 1); + } + } else { + // Found an undeleted node destination: point to it directly. + // Note that we're using the + target.setAttribute(ANDROID_URI, transfer.name, attribute.getValue()); + } + } else { + // Pointing to parent or center etc (non-id ref): replicate this on the target + target.setAttribute(ANDROID_URI, name, attribute.getValue()); + } + } + } + } + + /** + * Determines if two constraints are in the same direction and if so returns + * the constraint in the same direction. Rather than returning boolean true + * or false, this returns the constraint which is sometimes modified. For + * example, if you have a node which points left to a node which is centered + * in parent, then the constraint is turned into center horizontal. + */ + @Nullable + private static ConstraintType getCompatibleConstraint( + @NonNull ConstraintType first, @NonNull ConstraintType second) { + if (first == second) { + return first; + } + + switch (second) { + case ALIGN_LEFT: + case LAYOUT_RIGHT_OF: + switch (first) { + case LAYOUT_CENTER_HORIZONTAL: + case LAYOUT_LEFT_OF: + case ALIGN_LEFT: + return first; + case LAYOUT_CENTER_IN_PARENT: + return LAYOUT_CENTER_HORIZONTAL; + } + return null; + + case ALIGN_RIGHT: + case LAYOUT_LEFT_OF: + switch (first) { + case LAYOUT_CENTER_HORIZONTAL: + case ALIGN_RIGHT: + case LAYOUT_LEFT_OF: + return first; + case LAYOUT_CENTER_IN_PARENT: + return LAYOUT_CENTER_HORIZONTAL; + } + return null; + + case ALIGN_TOP: + case LAYOUT_BELOW: + case ALIGN_BASELINE: + switch (first) { + case LAYOUT_CENTER_VERTICAL: + case ALIGN_TOP: + case LAYOUT_BELOW: + case ALIGN_BASELINE: + return first; + case LAYOUT_CENTER_IN_PARENT: + return LAYOUT_CENTER_VERTICAL; + } + return null; + case ALIGN_BOTTOM: + case LAYOUT_ABOVE: + switch (first) { + case LAYOUT_CENTER_VERTICAL: + case ALIGN_BOTTOM: + case LAYOUT_ABOVE: + case ALIGN_BASELINE: + return first; + case LAYOUT_CENTER_IN_PARENT: + return LAYOUT_CENTER_VERTICAL; + } + return null; + } + + return null; + } +} |