PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java

https://github.com/CyanogenMod/android_sdk
Java | 267 lines | 181 code | 24 blank | 62 comment | 48 complexity | 0d67f3e71893cc385a7160c3d36517d1 MD5 | raw file
  1. /*
  2. * Copyright (C) 2012 The Android Open Source Project
  3. *
  4. * Licensed under the Eclipse Public License, Version 1.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.eclipse.org/org/documents/epl-v10.php
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.android.ide.common.layout.relative;
  17. import static com.android.SdkConstants.ANDROID_URI;
  18. import static com.android.SdkConstants.ATTR_ID;
  19. import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
  20. import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
  21. import static com.android.SdkConstants.ID_PREFIX;
  22. import static com.android.SdkConstants.NEW_ID_PREFIX;
  23. import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix;
  24. import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_HORIZONTAL;
  25. import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_VERTICAL;
  26. import com.android.SdkConstants;
  27. import com.android.annotations.NonNull;
  28. import com.android.annotations.Nullable;
  29. import com.android.ide.common.api.INode;
  30. import com.android.ide.common.api.INode.IAttribute;
  31. import com.google.common.collect.Maps;
  32. import com.google.common.collect.Sets;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.Set;
  36. /**
  37. * Handles deletions in a relative layout, transferring constraints across
  38. * deleted nodes
  39. * <p>
  40. * TODO: Consider adding the
  41. * {@link SdkConstants#ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING} attribute to a
  42. * node if it's pointing to a node which is deleted and which has no transitive
  43. * reference to another node.
  44. */
  45. public class DeletionHandler {
  46. private final INode mLayout;
  47. private final INode[] mChildren;
  48. private final List<INode> mDeleted;
  49. private final Set<String> mDeletedIds;
  50. private final Map<String, INode> mNodeMap;
  51. private final List<INode> mMoved;
  52. /**
  53. * Creates a new {@link DeletionHandler}
  54. *
  55. * @param deleted the deleted nodes
  56. * @param moved nodes that were moved (e.g. deleted, but also inserted elsewhere)
  57. * @param layout the parent layout of the deleted nodes
  58. */
  59. public DeletionHandler(@NonNull List<INode> deleted, @NonNull List<INode> moved,
  60. @NonNull INode layout) {
  61. mDeleted = deleted;
  62. mMoved = moved;
  63. mLayout = layout;
  64. mChildren = mLayout.getChildren();
  65. mNodeMap = Maps.newHashMapWithExpectedSize(mChildren.length);
  66. for (INode child : mChildren) {
  67. String id = child.getStringAttr(ANDROID_URI, ATTR_ID);
  68. if (id != null) {
  69. mNodeMap.put(stripIdPrefix(id), child);
  70. }
  71. }
  72. mDeletedIds = Sets.newHashSetWithExpectedSize(mDeleted.size());
  73. for (INode node : mDeleted) {
  74. String id = node.getStringAttr(ANDROID_URI, ATTR_ID);
  75. if (id != null) {
  76. mDeletedIds.add(stripIdPrefix(id));
  77. }
  78. }
  79. // Any widgets that remain (e.g. typically because they were moved) should
  80. // keep their incoming dependencies
  81. for (INode node : mMoved) {
  82. String id = node.getStringAttr(ANDROID_URI, ATTR_ID);
  83. if (id != null) {
  84. mDeletedIds.remove(stripIdPrefix(id));
  85. }
  86. }
  87. }
  88. @Nullable
  89. private static String getId(@NonNull IAttribute attribute) {
  90. if (attribute.getName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
  91. && ANDROID_URI.equals(attribute.getUri())
  92. && !attribute.getName().startsWith(ATTR_LAYOUT_MARGIN)) {
  93. String id = attribute.getValue();
  94. // It might not be an id reference, so check manually rather than just
  95. // calling stripIdPrefix():
  96. if (id.startsWith(NEW_ID_PREFIX)) {
  97. return id.substring(NEW_ID_PREFIX.length());
  98. } else if (id.startsWith(ID_PREFIX)) {
  99. return id.substring(ID_PREFIX.length());
  100. }
  101. }
  102. return null;
  103. }
  104. /**
  105. * Updates the constraints in the layout to handle deletion of a set of
  106. * nodes. This ensures that any constraints pointing to one of the deleted
  107. * nodes are changed properly to point to a non-deleted node with similar
  108. * constraints.
  109. */
  110. public void updateConstraints() {
  111. if (mChildren.length == mDeleted.size()) {
  112. // Deleting everything: Nothing to be done
  113. return;
  114. }
  115. // Now remove incoming edges to any views that were deleted. If possible,
  116. // don't just delete them but replace them with a transitive constraint, e.g.
  117. // if we have "A <= B <= C" and "B" is removed, then we end up with "A <= C",
  118. for (INode child : mChildren) {
  119. if (mDeleted.contains(child)) {
  120. continue;
  121. }
  122. for (IAttribute attribute : child.getLiveAttributes()) {
  123. String id = getId(attribute);
  124. if (id != null) {
  125. if (mDeletedIds.contains(id)) {
  126. // Unset this reference to a deleted widget. It might be
  127. // replaced if the pointed to node points to some other node
  128. // on the same side, but it may use a different constraint name,
  129. // or have none at all (e.g. parent).
  130. String name = attribute.getName();
  131. child.setAttribute(ANDROID_URI, name, null);
  132. INode deleted = mNodeMap.get(id);
  133. if (deleted != null) {
  134. ConstraintType type = ConstraintType.fromAttribute(name);
  135. if (type != null) {
  136. transfer(deleted, child, type, 0);
  137. }
  138. }
  139. }
  140. }
  141. }
  142. }
  143. }
  144. private void transfer(INode deleted, INode target, ConstraintType targetType, int depth) {
  145. if (depth == 20) {
  146. // Prevent really deep flow or unbounded recursion in case there is a bug in
  147. // the cycle detection code
  148. return;
  149. }
  150. assert mDeleted.contains(deleted);
  151. for (IAttribute attribute : deleted.getLiveAttributes()) {
  152. String name = attribute.getName();
  153. ConstraintType type = ConstraintType.fromAttribute(name);
  154. if (type == null) {
  155. continue;
  156. }
  157. ConstraintType transfer = getCompatibleConstraint(type, targetType);
  158. if (transfer != null) {
  159. String id = getId(attribute);
  160. if (id != null) {
  161. if (mDeletedIds.contains(id)) {
  162. INode nextDeleted = mNodeMap.get(id);
  163. if (nextDeleted != null) {
  164. // Points to another deleted node: recurse
  165. transfer(nextDeleted, target, targetType, depth + 1);
  166. }
  167. } else {
  168. // Found an undeleted node destination: point to it directly.
  169. // Note that we're using the
  170. target.setAttribute(ANDROID_URI, transfer.name, attribute.getValue());
  171. }
  172. } else {
  173. // Pointing to parent or center etc (non-id ref): replicate this on the target
  174. target.setAttribute(ANDROID_URI, name, attribute.getValue());
  175. }
  176. }
  177. }
  178. }
  179. /**
  180. * Determines if two constraints are in the same direction and if so returns
  181. * the constraint in the same direction. Rather than returning boolean true
  182. * or false, this returns the constraint which is sometimes modified. For
  183. * example, if you have a node which points left to a node which is centered
  184. * in parent, then the constraint is turned into center horizontal.
  185. */
  186. @Nullable
  187. private static ConstraintType getCompatibleConstraint(
  188. @NonNull ConstraintType first, @NonNull ConstraintType second) {
  189. if (first == second) {
  190. return first;
  191. }
  192. switch (second) {
  193. case ALIGN_LEFT:
  194. case LAYOUT_RIGHT_OF:
  195. switch (first) {
  196. case LAYOUT_CENTER_HORIZONTAL:
  197. case LAYOUT_LEFT_OF:
  198. case ALIGN_LEFT:
  199. return first;
  200. case LAYOUT_CENTER_IN_PARENT:
  201. return LAYOUT_CENTER_HORIZONTAL;
  202. }
  203. return null;
  204. case ALIGN_RIGHT:
  205. case LAYOUT_LEFT_OF:
  206. switch (first) {
  207. case LAYOUT_CENTER_HORIZONTAL:
  208. case ALIGN_RIGHT:
  209. case LAYOUT_LEFT_OF:
  210. return first;
  211. case LAYOUT_CENTER_IN_PARENT:
  212. return LAYOUT_CENTER_HORIZONTAL;
  213. }
  214. return null;
  215. case ALIGN_TOP:
  216. case LAYOUT_BELOW:
  217. case ALIGN_BASELINE:
  218. switch (first) {
  219. case LAYOUT_CENTER_VERTICAL:
  220. case ALIGN_TOP:
  221. case LAYOUT_BELOW:
  222. case ALIGN_BASELINE:
  223. return first;
  224. case LAYOUT_CENTER_IN_PARENT:
  225. return LAYOUT_CENTER_VERTICAL;
  226. }
  227. return null;
  228. case ALIGN_BOTTOM:
  229. case LAYOUT_ABOVE:
  230. switch (first) {
  231. case LAYOUT_CENTER_VERTICAL:
  232. case ALIGN_BOTTOM:
  233. case LAYOUT_ABOVE:
  234. case ALIGN_BASELINE:
  235. return first;
  236. case LAYOUT_CENTER_IN_PARENT:
  237. return LAYOUT_CENTER_VERTICAL;
  238. }
  239. return null;
  240. }
  241. return null;
  242. }
  243. }