OSDN Git Service

ADT GRE: Move gscripts package.
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / layout / gre / NodeProxy.java
index 864d0ec..fd0f475 100755 (executable)
 
 package com.android.ide.eclipse.adt.internal.editors.layout.gre;
 
-import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule.Rect;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.sdklib.SdkConstants;
+
+import org.eclipse.swt.graphics.Rectangle;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import groovy.lang.Closure;
 
 /**
  *
  */
-public class NodeProxy {
+public class NodeProxy implements INodeProxy {
 
-    private final UiElementNode mNode;
+    private final UiViewElementNode mNode;
     private final Rect mBounds;
+    private boolean mXmlEditOK;
 
-    public NodeProxy(UiElementNode node, IViewRule.Rect bounds) {
+    /**
+     * Creates a new {@link INodeProxy} that wraps an {@link UiViewElementNode} that is
+     * actually valid in the current UI/XML model. The view may not be part of the canvas
+     * yet (e.g. if it has just been dynamically added and the canvas hasn't reloaded yet.)
+     *
+     * @param node The node to wrap.
+     * @param bounds The bounds of a the view in the canvas. Must be a valid rect for a view
+     *   that is actually in the canvas and must be null (or an invalid rect) for a view
+     *   that has just been added dynamically to the model.
+     */
+    public NodeProxy(UiViewElementNode node, Rectangle bounds) {
         mNode = node;
-        mBounds = bounds;
+        if (bounds == null) {
+            mBounds = new Rect();
+        } else {
+            mBounds = new Rect(bounds.x, bounds.y, bounds.width, bounds.height);
+        }
+    }
+
+    public void debugPrintf(String msg, Object...params) {
+        AdtPlugin.printToConsole(
+                mNode == null ? "Groovy" : mNode.getDescriptor().getXmlLocalName() + ".groovy",
+                String.format(msg, params)
+                );
     }
 
     public Rect getBounds() {
         return mBounds;
     }
 
+    /* package */ UiViewElementNode getNode() {
+        return mNode;
+    }
+
+    // ---- XML Editing ---
+
+    public void editXml(String undoName, final Closure c) {
+        if (mXmlEditOK) {
+            throw new RuntimeException("Error: nested calls to INodeProxy.editXml!");
+        }
+        try {
+            mXmlEditOK = true;
+
+            final AndroidEditor editor = mNode.getEditor();
+
+            if (editor instanceof LayoutEditor) {
+                // Create an undo wrapper, which takes a runnable
+                ((LayoutEditor) editor).wrapUndoRecording(
+                        undoName,
+                        new Runnable() {
+                            public void run() {
+                                // Create an edit-XML wrapper, which takes a runnable
+                                editor.editXmlModel(new Runnable() {
+                                    public void run() {
+                                        // Finally execute the closure that will act on the XML
+                                        c.call(this);
+                                    }
+                                });
+                            }
+                        });
+            }
+        } finally {
+            mXmlEditOK = false;
+        }
+    }
+
+    private void checkEditOK() {
+        if (!mXmlEditOK) {
+            throw new RuntimeException("Error: XML edit call without using INodeProxy.editXml!");
+        }
+    }
+
+    public INodeProxy createChild(String viewFqcn) {
+        checkEditOK();
+
+        // Find the descriptor for this FQCN
+        ViewElementDescriptor vd = getFqcnViewDescritor(viewFqcn);
+        if (vd == null) {
+            debugPrintf("Can't create a new %s element", viewFqcn);
+            return null;
+        }
+
+        // TODO use UiElementNode.insertNewUiChild() to control the position, which is
+        // needed for a relative layout.
+        UiElementNode uiNew = mNode.appendNewUiChild(vd);
+
+        // TODO we probably want to defer that to the GRE to use IViewRule#getDefaultAttributes()
+        DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/);
+
+        Node xmlNode = uiNew.createXmlNode();
+
+        if (!(uiNew instanceof UiViewElementNode) || xmlNode == null) {
+            // Both things are not supposed to happen. When they do, we're in big trouble.
+            // We don't really know how to revert the state at this point and the UI model is
+            // now out of sync with the XML model.
+            // Panic ensues.
+            // The best bet is to abort now. The edit wrapper will release the edit and the
+            // XML/UI should get reloaded properly (with a likely invalid XML.)
+            debugPrintf("Failed to create a new %s element", viewFqcn);
+            throw new RuntimeException("XML node creation failed."); //$NON-NLS-1$
+        }
+
+        return new NodeProxy((UiViewElementNode) uiNew, null);
+    }
+
+    public boolean setAttribute(String attributeName, String value) {
+        checkEditOK();
+
+        UiAttributeNode attr = mNode.setAttributeValue(attributeName, value, true /* override */);
+        mNode.commitDirtyAttributesToXml();
+
+        return attr != null;
+    }
+
+
+    // --- internal helpers ---
+
+    /**
+     * Returns a given XML attribute.
+     * @param attrName The local name of the attribute.
+     * @return the attribute as a {@link String}, if it exists, or <code>null</code>
+     */
+    private String getStringAttr(String attrName) {
+        // TODO this was just copy-pasted from the GLE1 edit code. Need to adapt to this context.
+        UiElementNode uiNode = mNode;
+        if (uiNode.getXmlNode() != null) {
+            Node xmlNode = uiNode.getXmlNode();
+            if (xmlNode != null) {
+                NamedNodeMap nodeAttributes = xmlNode.getAttributes();
+                if (nodeAttributes != null) {
+                    Node attr = nodeAttributes.getNamedItemNS(SdkConstants.NS_RESOURCES, attrName);
+                    if (attr != null) {
+                        return attr.getNodeValue();
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Helper methods that returns a {@link ViewElementDescriptor} for the requested FQCN.
+     * Will return null if we can't find that FQCN or we lack the editor/data/descriptors info
+     * (which shouldn't really happen since at this point the SDK should be fully loaded and
+     * isn't reloading, or we wouldn't be here editing XML for a groovy script.)
+     */
+    private ViewElementDescriptor getFqcnViewDescritor(String fqcn) {
+        AndroidEditor editor = mNode.getEditor();
+        if (editor != null) {
+            AndroidTargetData data = editor.getTargetData();
+            if (data != null) {
+                LayoutDescriptors layoutDesc = data.getLayoutDescriptors();
+                if (layoutDesc != null) {
+                    DocumentDescriptor docDesc = layoutDesc.getDescriptor();
+                    if (docDesc != null) {
+                        return internalFindFqcnViewDescritor(fqcn, docDesc.getChildren(), null);
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Internal helper to recursively search for a {@link ViewElementDescriptor} that matches
+     * the requested FQCN.
+     *
+     * @param fqcn The target View FQCN to find.
+     * @param descriptors A list of cildren descriptors to iterate through.
+     * @param visited A set we use to remember which descriptors have already been visited,
+     *  necessary since the view descriptor hierarchy is cyclic.
+     * @return Either a matching {@link ViewElementDescriptor} or null.
+     */
+    private ViewElementDescriptor internalFindFqcnViewDescritor(String fqcn,
+            ElementDescriptor[] descriptors,
+            Set<ElementDescriptor> visited) {
+        if (visited == null) {
+            visited = new HashSet<ElementDescriptor>();
+        }
+
+        if (descriptors != null) {
+            for (ElementDescriptor desc : descriptors) {
+                if (visited.add(desc)) {
+                    // Set.add() returns true if this a new element that was added to the set.
+                    // That means we haven't visited this descriptor yet.
+                    // We want a ViewElementDescriptor with a matching FQCN.
+                    if (desc instanceof ViewElementDescriptor &&
+                            fqcn.equals(((ViewElementDescriptor) desc).getFullClassName())) {
+                        return (ViewElementDescriptor) desc;
+                    }
+
+                    // Visit its children
+                    ViewElementDescriptor vd =
+                        internalFindFqcnViewDescritor(fqcn, desc.getChildren(), visited);
+                    if (vd != null) {
+                        return vd;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
 }