OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / gscripts / BaseLayout.groovy
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 }