2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.adt.gscripts;
19 public class BaseLayout extends BaseView {
21 public boolean onInitialize(String fqcn) {
22 return super.onInitialize(fqcn);
25 public void onDispose() {
29 // ==== Utility methods used by derived layouts ====
32 protected String[] getLayoutAttrFilter() {
34 // from AbsoluteLayout
38 // from RelativeLayout
43 "layout_alignBaseline",
48 "layout_alignParentTop",
49 "layout_alignParentBottom",
50 "layout_alignParentLeft",
51 "layout_alignParentRight",
52 "layout_alignWithParentMissing",
53 "layout_centerHorizontal",
54 "layout_centerInParent",
55 "layout_centerVertical",
60 * Draws the bounds of the given elements and all its children elements
61 * in the canvas with the specified offet.
63 protected void drawElement(IGraphics gc, IDragElement element, int offsetX, int offsetY) {
64 Rect b = element.getBounds();
66 b = b.copy().offsetBy(offsetX, offsetY);
70 for(inner in element.getInnerElements()) {
71 drawElement(gc, inner, offsetX, offsetY);
76 * Collect all the "android:id" IDs from the dropped elements.
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.
83 * Returns a map String old-id => tuple (String new-id, String fqcn)
84 * where fqcn is the FQCN of the element.
86 protected Map getDropIdMap(INode targetNode,
87 IDragElement[] elements,
88 boolean createNewIds) {
92 collectIds(idMap, elements);
93 // Need to remap ids if necessary
94 idMap = remapIds(targetNode, idMap);
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.)
108 protected Map collectIds(Map idMap, IDragElement[] elements) {
109 for (element in elements) {
110 def attr = element.getAttribute(ANDROID_URI, ATTR_ID);
112 String id = attr.getValue();
113 if (id != null && id != "") {
114 idMap.put(id, [id, element.getFqcn()]);
118 collectIds(idMap, element.getInnerElements());
125 * Used by #getDropIdMap to find new IDs in case of conflict.
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);
133 idMap.each() { key, value ->
134 def id = normalizeId(key);
136 if (!existingIdMap.containsKey(id)) {
137 // Not a conflict. Use as-is.
138 new_map.put(key, value);
140 new_map.put(id, value);
143 // There is a conflict. Get a new id.
144 def new_id = findNewId(value[1], existingIdMap);
146 new_map.put(id, value);
147 new_map.put(id.replaceFirst("@\\+", "@"), value);
155 * Used by #remapIds to find a new ID for a conflicting element.
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];
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);
169 // We'll never reach here.
174 * Used by #getDropIdMap to find existing IDs recursively.
176 protected void collectExistingIds(INode root, Map existingIdMap) {
181 def id = root.getStringAttr(ANDROID_URI, ATTR_ID);
183 id = normalizeId(id);
185 if (!existingIdMap.containsKey(id)) {
186 existingIdMap.put(id, id);
190 for(child in root.getChildren()) {
191 collectExistingIds(child, existingIdMap);
196 * Transforms @id/name into @+id/name to treat both forms the same way.
198 protected String normalizeId(String id) {
199 if (id.indexOf("@+") == -1) {
200 id = id.replaceFirst("@", "@+");
206 * Copies all the attributes from oldElement to newNode.
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.
215 protected void addAttributes(INode newNode, IDragElement oldElement,
216 Map idMap, Closure filter) {
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;
227 for (attr in oldElement.getAttributes()) {
228 String uri = attr.getUri();
229 String name = attr.getName();
230 String value = attr.getValue();
232 if (uri == ANDROID_URI) {
233 if (name == ATTR_ID) {
235 } else if (name == ATTR_TEXT) {
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];
250 if (filter != null) {
251 value = filter(uri, name, value);
253 if (value != null && value != false && value != "") {
254 newNode.setAttribute(uri, name, value);
256 if (uri == ANDROID_URI && name == ATTR_ID && oldId != null && value != oldId) {
262 if (newId != null && oldText == oldId) {
263 newNode.setAttribute(ANDROID_URI, ATTR_TEXT, newId);
268 * Adds all the children elements of oldElement to newNode, recursively.
269 * Attributes are adjusted by calling addAttributes with idMap as necessary, with
272 protected void addInnerElements(INode newNode, IDragElement oldElement, Map idMap) {
274 for (element in oldElement.getInnerElements()) {
275 String fqcn = element.getFqcn();
276 INode childNode = newNode.appendChild(fqcn);
278 addAttributes(childNode, element, idMap, null /* closure */);
279 addInnerElements(childNode, element, idMap);