2 * Copyright (C) 2009 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.ide.eclipse.adt.internal.editors.layout.gre;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
21 import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
22 import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
23 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
24 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
25 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
26 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
27 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
28 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
29 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
30 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
31 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
32 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
33 import com.android.sdklib.SdkConstants;
35 import org.eclipse.swt.graphics.Rectangle;
36 import org.w3c.dom.NamedNodeMap;
37 import org.w3c.dom.Node;
39 import java.util.HashSet;
42 import groovy.lang.Closure;
47 public class NodeProxy implements INodeProxy {
49 private final UiViewElementNode mNode;
50 private final Rect mBounds;
51 private boolean mXmlEditOK;
54 * Creates a new {@link INodeProxy} that wraps an {@link UiViewElementNode} that is
55 * actually valid in the current UI/XML model. The view may not be part of the canvas
56 * yet (e.g. if it has just been dynamically added and the canvas hasn't reloaded yet.)
58 * @param node The node to wrap.
59 * @param bounds The bounds of a the view in the canvas. Must be a valid rect for a view
60 * that is actually in the canvas and must be null (or an invalid rect) for a view
61 * that has just been added dynamically to the model.
63 public NodeProxy(UiViewElementNode node, Rectangle bounds) {
68 mBounds = new Rect(bounds.x, bounds.y, bounds.width, bounds.height);
72 public void debugPrintf(String msg, Object...params) {
73 AdtPlugin.printToConsole(
74 mNode == null ? "Groovy" : mNode.getDescriptor().getXmlLocalName() + ".groovy",
75 String.format(msg, params)
79 public Rect getBounds() {
83 /* package */ UiViewElementNode getNode() {
87 // ---- XML Editing ---
89 public void editXml(String undoName, final Closure c) {
91 throw new RuntimeException("Error: nested calls to INodeProxy.editXml!");
96 final AndroidEditor editor = mNode.getEditor();
98 if (editor instanceof LayoutEditor) {
99 // Create an undo wrapper, which takes a runnable
100 ((LayoutEditor) editor).wrapUndoRecording(
104 // Create an edit-XML wrapper, which takes a runnable
105 editor.editXmlModel(new Runnable() {
107 // Finally execute the closure that will act on the XML
119 private void checkEditOK() {
121 throw new RuntimeException("Error: XML edit call without using INodeProxy.editXml!");
125 public INodeProxy createChild(String viewFqcn) {
128 // Find the descriptor for this FQCN
129 ViewElementDescriptor vd = getFqcnViewDescritor(viewFqcn);
131 debugPrintf("Can't create a new %s element", viewFqcn);
135 // TODO use UiElementNode.insertNewUiChild() to control the position, which is
136 // needed for a relative layout.
137 UiElementNode uiNew = mNode.appendNewUiChild(vd);
139 // TODO we probably want to defer that to the GRE to use IViewRule#getDefaultAttributes()
140 DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/);
142 Node xmlNode = uiNew.createXmlNode();
144 if (!(uiNew instanceof UiViewElementNode) || xmlNode == null) {
145 // Both things are not supposed to happen. When they do, we're in big trouble.
146 // We don't really know how to revert the state at this point and the UI model is
147 // now out of sync with the XML model.
149 // The best bet is to abort now. The edit wrapper will release the edit and the
150 // XML/UI should get reloaded properly (with a likely invalid XML.)
151 debugPrintf("Failed to create a new %s element", viewFqcn);
152 throw new RuntimeException("XML node creation failed."); //$NON-NLS-1$
155 return new NodeProxy((UiViewElementNode) uiNew, null);
158 public boolean setAttribute(String attributeName, String value) {
161 UiAttributeNode attr = mNode.setAttributeValue(attributeName, value, true /* override */);
162 mNode.commitDirtyAttributesToXml();
168 // --- internal helpers ---
171 * Returns a given XML attribute.
172 * @param attrName The local name of the attribute.
173 * @return the attribute as a {@link String}, if it exists, or <code>null</code>
175 private String getStringAttr(String attrName) {
176 // TODO this was just copy-pasted from the GLE1 edit code. Need to adapt to this context.
177 UiElementNode uiNode = mNode;
178 if (uiNode.getXmlNode() != null) {
179 Node xmlNode = uiNode.getXmlNode();
180 if (xmlNode != null) {
181 NamedNodeMap nodeAttributes = xmlNode.getAttributes();
182 if (nodeAttributes != null) {
183 Node attr = nodeAttributes.getNamedItemNS(SdkConstants.NS_RESOURCES, attrName);
185 return attr.getNodeValue();
195 * Helper methods that returns a {@link ViewElementDescriptor} for the requested FQCN.
196 * Will return null if we can't find that FQCN or we lack the editor/data/descriptors info
197 * (which shouldn't really happen since at this point the SDK should be fully loaded and
198 * isn't reloading, or we wouldn't be here editing XML for a groovy script.)
200 private ViewElementDescriptor getFqcnViewDescritor(String fqcn) {
201 AndroidEditor editor = mNode.getEditor();
202 if (editor != null) {
203 AndroidTargetData data = editor.getTargetData();
205 LayoutDescriptors layoutDesc = data.getLayoutDescriptors();
206 if (layoutDesc != null) {
207 DocumentDescriptor docDesc = layoutDesc.getDescriptor();
208 if (docDesc != null) {
209 return internalFindFqcnViewDescritor(fqcn, docDesc.getChildren(), null);
219 * Internal helper to recursively search for a {@link ViewElementDescriptor} that matches
220 * the requested FQCN.
222 * @param fqcn The target View FQCN to find.
223 * @param descriptors A list of cildren descriptors to iterate through.
224 * @param visited A set we use to remember which descriptors have already been visited,
225 * necessary since the view descriptor hierarchy is cyclic.
226 * @return Either a matching {@link ViewElementDescriptor} or null.
228 private ViewElementDescriptor internalFindFqcnViewDescritor(String fqcn,
229 ElementDescriptor[] descriptors,
230 Set<ElementDescriptor> visited) {
231 if (visited == null) {
232 visited = new HashSet<ElementDescriptor>();
235 if (descriptors != null) {
236 for (ElementDescriptor desc : descriptors) {
237 if (visited.add(desc)) {
238 // Set.add() returns true if this a new element that was added to the set.
239 // That means we haven't visited this descriptor yet.
240 // We want a ViewElementDescriptor with a matching FQCN.
241 if (desc instanceof ViewElementDescriptor &&
242 fqcn.equals(((ViewElementDescriptor) desc).getFullClassName())) {
243 return (ViewElementDescriptor) desc;
246 // Visit its children
247 ViewElementDescriptor vd =
248 internalFindFqcnViewDescritor(fqcn, desc.getChildren(), visited);