1 /* 2 * Copyright (C) 2010 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 17 package com.android.adt.gscripts; 18 19 public class BaseLayout extends BaseView { 20 21 public boolean onInitialize(String fqcn) { 22 return super.onInitialize(fqcn); 23 } 24 25 public void onDispose() { 26 super.onDispose(); 27 } 28 29 // ==== Utility methods used by derived layouts ==== 30 31 // TODO revisit. 32 protected String[] getLayoutAttrFilter() { 33 return [ 34 // from AbsoluteLayout 35 "layout_x", 36 "layout_y", 37 38 // from RelativeLayout 39 "layout_above", 40 "layout_below", 41 "layout_toLeftOf", 42 "layout_toRightOf", 43 "layout_alignBaseline", 44 "layout_alignTop", 45 "layout_alignBottom", 46 "layout_alignLeft", 47 "layout_alignRight", 48 "layout_alignParentTop", 49 "layout_alignParentBottom", 50 "layout_alignParentLeft", 51 "layout_alignParentRight", 52 "layout_alignWithParentMissing", 53 "layout_centerHorizontal", 54 "layout_centerInParent", 55 "layout_centerVertical", 56 ]; 57 } 58 59 /** 60 * Draws the bounds of the given elements and all its children elements 61 * in the canvas with the specified offet. 62 */ 63 protected void drawElement(IGraphics gc, IDragElement element, int offsetX, int offsetY) { 64 Rect b = element.getBounds(); 65 if (b.isValid()) { 66 b = b.copy().offsetBy(offsetX, offsetY); 67 gc.drawRect(b); 68 } 69 70 for(inner in element.getInnerElements()) { 71 drawElement(gc, inner, offsetX, offsetY); 72 } 73 } 74 75 /** 76 * Collect all the "android:id" IDs from the dropped elements. 77 * 78 * When moving objects within the same canvas, that's all there is to do. 79 * However if the objects are moved to a different canvas or are copied 80 * then set createNewIds to true to find the existing IDs under targetNode 81 * and create a map with new non-conflicting unique IDs as needed. 82 * 83 * Returns a map String old-id => tuple (String new-id, String fqcn) 84 * where fqcn is the FQCN of the element. 85 */ 86 protected Map getDropIdMap(INode targetNode, 87 IDragElement[] elements, 88 boolean createNewIds) { 89 def idMap = [:]; 90 91 if (createNewIds) { 92 collectIds(idMap, elements); 93 // Need to remap ids if necessary 94 idMap = remapIds(targetNode, idMap); 95 } 96 97 return idMap; 98 } 99 100 101 /** 102 * Fills idMap with a map String id => tuple (String id, String fqcn) 103 * where fqcn is the FQCN of the element (in case we want to generate 104 * new IDs based on the element type.) 105 * 106 * @see #getDropIdMap 107 */ 108 protected Map collectIds(Map idMap, IDragElement[] elements) { 109 for (element in elements) { 110 def attr = element.getAttribute(ANDROID_URI, ATTR_ID); 111 if (attr != null) { 112 String id = attr.getValue(); 113 if (id != null && id != "") { 114 idMap.put(id, [id, element.getFqcn()]); 115 } 116 } 117 118 collectIds(idMap, element.getInnerElements()); 119 } 120 121 return idMap; 122 } 123 124 /** 125 * Used by #getDropIdMap to find new IDs in case of conflict. 126 */ 127 protected Map remapIds(INode node, Map idMap) { 128 // Visit the document to get a list of existing ids 129 def existingIdMap = [:]; 130 collectExistingIds(node.getRoot(), existingIdMap); 131 132 def new_map = [:]; 133 idMap.each() { key, value -> 134 def id = normalizeId(key); 135 136 if (!existingIdMap.containsKey(id)) { 137 // Not a conflict. Use as-is. 138 new_map.put(key, value); 139 if (key != id) { 140 new_map.put(id, value); 141 } 142 } else { 143 // There is a conflict. Get a new id. 144 def new_id = findNewId(value[1], existingIdMap); 145 value[0] = new_id; 146 new_map.put(id, value); 147 new_map.put(id.replaceFirst("@\\+", "@"), value); 148 } 149 } 150 151 return new_map; 152 } 153 154 /** 155 * Used by #remapIds to find a new ID for a conflicting element. 156 */ 157 protected String findNewId(String fqcn, Map existingIdMap) { 158 // Get the last component of the FQCN (e.g. "android.view.Button" => "Button") 159 String name = fqcn[fqcn.lastIndexOf(".")+1 .. fqcn.length()-1]; 160 161 for (int i = 1; i < 1000000; i++) { 162 String id = String.format("@+id/%s%02d", name, i); 163 if (!existingIdMap.containsKey(id)) { 164 existingIdMap.put(id, id); 165 return id; 166 } 167 } 168 169 // We'll never reach here. 170 return null; 171 } 172 173 /** 174 * Used by #getDropIdMap to find existing IDs recursively. 175 */ 176 protected void collectExistingIds(INode root, Map existingIdMap) { 177 if (root == null) { 178 return; 179 } 180 181 def id = root.getStringAttr(ANDROID_URI, ATTR_ID); 182 if (id != null) { 183 id = normalizeId(id); 184 185 if (!existingIdMap.containsKey(id)) { 186 existingIdMap.put(id, id); 187 } 188 } 189 190 for(child in root.getChildren()) { 191 collectExistingIds(child, existingIdMap); 192 } 193 } 194 195 /** 196 * Transforms @id/name into @+id/name to treat both forms the same way. 197 */ 198 protected String normalizeId(String id) { 199 if (id.indexOf("@+") == -1) { 200 id = id.replaceFirst("@", "@+"); 201 } 202 return id; 203 } 204 205 /** 206 * Copies all the attributes from oldElement to newNode. 207 * 208 * Uses the idMap to transform the value of all attributes of Format.REFERENCE, 209 * If filter is non-null, it's a closure that takes for argument: 210 * String attribue-uri (namespace), String attribute-name, String attribute-value 211 * The closure should return a valid replacement string. 212 * The closure can return either null, false or an empty string to prevent the attribute 213 * from being copied into the new node. 214 */ 215 protected void addAttributes(INode newNode, IDragElement oldElement, 216 Map idMap, Closure filter) { 217 218 // A little trick here: when creating new UI widgets by dropping them from 219 // the palette, we assign them a new id and then set the text attribute 220 // to that id, so for example a Button will have android:text="@+id/Button01". 221 // Here we detect if such an id is being remapped to a new id and if there's 222 // a text attribute with exactly the same id name, we update it too. 223 String oldText = null; 224 String oldId = null; 225 String newId = null; 226 227 for (attr in oldElement.getAttributes()) { 228 String uri = attr.getUri(); 229 String name = attr.getName(); 230 String value = attr.getValue(); 231 232 if (uri == ANDROID_URI) { 233 if (name == ATTR_ID) { 234 oldId = value; 235 } else if (name == ATTR_TEXT) { 236 oldText = value; 237 } 238 } 239 240 def attrInfo = newNode.getAttributeInfo(uri, name); 241 if (attrInfo != null) { 242 def formats = attrInfo.getFormats(); 243 if (formats != null && IAttributeInfo.Format.REFERENCE in formats) { 244 if (idMap.containsKey(value)) { 245 value = idMap[value][0]; 246 } 247 } 248 } 249 250 if (filter != null) { 251 value = filter(uri, name, value); 252 } 253 if (value != null && value != false && value != "") { 254 newNode.setAttribute(uri, name, value); 255 256 if (uri == ANDROID_URI && name == ATTR_ID && oldId != null && value != oldId) { 257 newId = value; 258 } 259 } 260 } 261 262 if (newId != null && oldText == oldId) { 263 newNode.setAttribute(ANDROID_URI, ATTR_TEXT, newId); 264 } 265 } 266 267 /** 268 * Adds all the children elements of oldElement to newNode, recursively. 269 * Attributes are adjusted by calling addAttributes with idMap as necessary, with 270 * no closure filter. 271 */ 272 protected void addInnerElements(INode newNode, IDragElement oldElement, Map idMap) { 273 274 for (element in oldElement.getInnerElements()) { 275 String fqcn = element.getFqcn(); 276 INode childNode = newNode.appendChild(fqcn); 277 278 addAttributes(childNode, element, idMap, null /* closure */); 279 addInnerElements(childNode, element, idMap); 280 } 281 } 282 283 } 284