• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17package com.android.adt.gscripts;
18
19public 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