OSDN Git Service

Merge "ADT/GLE: Fix config selector to not select a config that has a better match...
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / layout / gre / NodeProxy.java
1 /*
2  * Copyright (C) 2009 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.ide.eclipse.adt.internal.editors.layout.gre;
18
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.gscripts.INodeProxy;
21 import com.android.ide.eclipse.adt.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;
34
35 import org.eclipse.swt.graphics.Rectangle;
36 import org.w3c.dom.NamedNodeMap;
37 import org.w3c.dom.Node;
38
39 import java.util.HashSet;
40 import java.util.Set;
41
42 import groovy.lang.Closure;
43
44 /**
45  *
46  */
47 public class NodeProxy implements INodeProxy {
48
49     private final UiViewElementNode mNode;
50     private final Rect mBounds;
51     private boolean mXmlEditOK;
52
53     /**
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.)
57      *
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.
62      */
63     public NodeProxy(UiViewElementNode node, Rectangle bounds) {
64         mNode = node;
65         if (bounds == null) {
66             mBounds = new Rect();
67         } else {
68             mBounds = new Rect(bounds.x, bounds.y, bounds.width, bounds.height);
69         }
70     }
71
72     public void debugPrintf(String msg, Object...params) {
73         AdtPlugin.printToConsole(
74                 mNode == null ? "Groovy" : mNode.getDescriptor().getXmlLocalName() + ".groovy",
75                 String.format(msg, params)
76                 );
77     }
78
79     public Rect getBounds() {
80         return mBounds;
81     }
82
83     /* package */ UiViewElementNode getNode() {
84         return mNode;
85     }
86
87     // ---- XML Editing ---
88
89     public void editXml(String undoName, final Closure c) {
90         if (mXmlEditOK) {
91             throw new RuntimeException("Error: nested calls to INodeProxy.editXml!");
92         }
93         try {
94             mXmlEditOK = true;
95
96             final AndroidEditor editor = mNode.getEditor();
97
98             if (editor instanceof LayoutEditor) {
99                 // Create an undo wrapper, which takes a runnable
100                 ((LayoutEditor) editor).wrapUndoRecording(
101                         undoName,
102                         new Runnable() {
103                             public void run() {
104                                 // Create an edit-XML wrapper, which takes a runnable
105                                 editor.editXmlModel(new Runnable() {
106                                     public void run() {
107                                         // Finally execute the closure that will act on the XML
108                                         c.call(this);
109                                     }
110                                 });
111                             }
112                         });
113             }
114         } finally {
115             mXmlEditOK = false;
116         }
117     }
118
119     private void checkEditOK() {
120         if (!mXmlEditOK) {
121             throw new RuntimeException("Error: XML edit call without using INodeProxy.editXml!");
122         }
123     }
124
125     public INodeProxy createChild(String viewFqcn) {
126         checkEditOK();
127
128         // Find the descriptor for this FQCN
129         ViewElementDescriptor vd = getFqcnViewDescritor(viewFqcn);
130         if (vd == null) {
131             debugPrintf("Can't create a new %s element", viewFqcn);
132             return null;
133         }
134
135         // TODO use UiElementNode.insertNewUiChild() to control the position, which is
136         // needed for a relative layout.
137         UiElementNode uiNew = mNode.appendNewUiChild(vd);
138
139         // TODO we probably want to defer that to the GRE to use IViewRule#getDefaultAttributes()
140         DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/);
141
142         Node xmlNode = uiNew.createXmlNode();
143
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.
148             // Panic ensues.
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$
153         }
154
155         return new NodeProxy((UiViewElementNode) uiNew, null);
156     }
157
158     public boolean setAttribute(String attributeName, String value) {
159         checkEditOK();
160
161         UiAttributeNode attr = mNode.setAttributeValue(attributeName, value, true /* override */);
162         mNode.commitDirtyAttributesToXml();
163
164         return attr != null;
165     }
166
167
168     // --- internal helpers ---
169
170     /**
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>
174      */
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);
184                     if (attr != null) {
185                         return attr.getNodeValue();
186                     }
187                 }
188             }
189         }
190         return null;
191     }
192
193
194     /**
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.)
199      */
200     private ViewElementDescriptor getFqcnViewDescritor(String fqcn) {
201         AndroidEditor editor = mNode.getEditor();
202         if (editor != null) {
203             AndroidTargetData data = editor.getTargetData();
204             if (data != null) {
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);
210                     }
211                 }
212             }
213         }
214
215         return null;
216     }
217
218     /**
219      * Internal helper to recursively search for a {@link ViewElementDescriptor} that matches
220      * the requested FQCN.
221      *
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.
227      */
228     private ViewElementDescriptor internalFindFqcnViewDescritor(String fqcn,
229             ElementDescriptor[] descriptors,
230             Set<ElementDescriptor> visited) {
231         if (visited == null) {
232             visited = new HashSet<ElementDescriptor>();
233         }
234
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;
244                     }
245
246                     // Visit its children
247                     ViewElementDescriptor vd =
248                         internalFindFqcnViewDescritor(fqcn, desc.getChildren(), visited);
249                     if (vd != null) {
250                         return vd;
251                     }
252                 }
253             }
254         }
255
256         return null;
257     }
258
259 }