aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiSettableAttributeNode.java32
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiUpdateListener.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAbstractTextAttributeNode.java120
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java174
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java160
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java2160
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiFlagAttributeNode.java310
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java220
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java523
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiSeparatorAttributeNode.java146
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextAttributeNode.java196
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextValueNode.java118
12 files changed, 0 insertions, 4206 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiSettableAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiSettableAttributeNode.java
deleted file mode 100644
index dd908ad7b..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiSettableAttributeNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-/**
- * This interface decoration indicates that a given UiAttributeNode can both
- * set and get its current value.
- */
-public interface IUiSettableAttributeNode {
-
- /** Returns the current value of the node. */
- public String getCurrentValue();
-
- /** Sets the current value of the node. Cannot be null (use an empty string). */
- public void setCurrentValue(String value);
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiUpdateListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiUpdateListener.java
deleted file mode 100644
index a4f1f74ea..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/IUiUpdateListener.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-
-/**
- * Listen to update notifications in UI nodes.
- */
-public interface IUiUpdateListener {
-
- /** Update state of the UI node */
- public enum UiUpdateState {
- /** The node's attributes have been updated. They may or may not actually have changed. */
- ATTR_UPDATED,
- /** The node sub-structure (i.e. child nodes) has changed */
- CHILDREN_CHANGED,
- /** The XML counterpart for the UI node has just been created. */
- CREATED,
- /** The XML counterpart for the UI node has just been deleted.
- * Note that mandatory UI nodes are never actually deleted. */
- DELETED
- }
-
- /**
- * Indicates that an UiElementNode has been updated.
- * <p/>
- * This happens when an {@link UiElementNode} is refreshed to match the
- * XML model. The actual UI element node may or may not have changed.
- *
- * @param ui_node The {@link UiElementNode} being updated.
- */
- public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state);
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAbstractTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAbstractTextAttributeNode.java
deleted file mode 100644
index 4f795904d..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAbstractTextAttributeNode.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-
-import org.w3c.dom.Node;
-
-/**
- * Represents an XML attribute in that can be modified using a simple text field
- * in the XML editor's user interface.
- * <p/>
- * The XML attribute has no default value. When unset, the text field is blank.
- * When updating the XML, if the field is empty, the attribute will be removed
- * from the XML element.
- * <p/>
- * See {@link UiAttributeNode} for more information.
- */
-public abstract class UiAbstractTextAttributeNode extends UiAttributeNode
- implements IUiSettableAttributeNode {
-
- protected static final String DEFAULT_VALUE = ""; //$NON-NLS-1$
-
- /** Prevent internal listener from firing when internally modifying the text */
- private boolean mInternalTextModification;
- /** Last value read from the XML model. Cannot be null. */
- private String mCurrentValue = DEFAULT_VALUE;
-
- public UiAbstractTextAttributeNode(AttributeDescriptor attributeDescriptor,
- UiElementNode uiParent) {
- super(attributeDescriptor, uiParent);
- }
-
- /** Returns the current value of the node. */
- @Override
- public final String getCurrentValue() {
- return mCurrentValue;
- }
-
- /** Sets the current value of the node. Cannot be null (use an empty string). */
- @Override
- public final void setCurrentValue(String value) {
- mCurrentValue = value;
- }
-
- /** Returns if the attribute node is valid, and its UI has been created. */
- public abstract boolean isValid();
-
- /** Returns the text value present in the UI. */
- public abstract String getTextWidgetValue();
-
- /** Sets the text value to be displayed in the UI. */
- public abstract void setTextWidgetValue(String value);
-
-
- /**
- * Updates the current text field's value when the XML has changed.
- * <p/>
- * The caller doesn't really know if attributes have changed,
- * so it will call this to refresh the attribute anyway. The value
- * is only set if it has changed.
- * <p/>
- * This also resets the "dirty" flag.
- */
- @Override
- public void updateValue(Node xml_attribute_node) {
- mCurrentValue = DEFAULT_VALUE;
- if (xml_attribute_node != null) {
- mCurrentValue = xml_attribute_node.getNodeValue();
- }
-
- if (isValid() && !getTextWidgetValue().equals(mCurrentValue)) {
- try {
- mInternalTextModification = true;
- setTextWidgetValue(mCurrentValue);
- setDirty(false);
- } finally {
- mInternalTextModification = false;
- }
- }
- }
-
- /* (non-java doc)
- * Called by the user interface when the editor is saved or its state changed
- * and the modified attributes must be committed (i.e. written) to the XML model.
- */
- @Override
- public void commit() {
- UiElementNode parent = getUiParent();
- if (parent != null && isValid() && isDirty()) {
- String value = getTextWidgetValue();
- if (parent.commitAttributeToXml(this, value)) {
- mCurrentValue = value;
- setDirty(false);
- }
- }
- }
-
- protected final boolean isInInternalTextModification() {
- return mInternalTextModification;
- }
-
- protected final void setInInternalTextModification(boolean internalTextModification) {
- mInternalTextModification = internalTextModification;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java
deleted file mode 100644
index ffe637c5d..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.common.xml.XmlAttributeSortOrder;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.ui.forms.IManagedForm;
-import org.w3c.dom.Node;
-
-/**
- * Represents an XML attribute that can be modified by the XML editor's user interface.
- * <p/>
- * The characteristics of an {@link UiAttributeNode} are declared by a
- * corresponding {@link AttributeDescriptor}.
- * <p/>
- * This is an abstract class. Derived classes must implement the creation of the UI
- * and manage its synchronization with the XML.
- */
-public abstract class UiAttributeNode implements Comparable<UiAttributeNode> {
-
- private AttributeDescriptor mDescriptor;
- private UiElementNode mUiParent;
- private boolean mIsDirty;
- private boolean mHasError;
-
- /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor}
- * and the corresponding runtime {@link UiElementNode} parent. */
- public UiAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
- mDescriptor = attributeDescriptor;
- mUiParent = uiParent;
- }
-
- /** Returns the {@link AttributeDescriptor} specific to this UI attribute node */
- public final AttributeDescriptor getDescriptor() {
- return mDescriptor;
- }
-
- /** Returns the {@link UiElementNode} that owns this {@link UiAttributeNode} */
- public final UiElementNode getUiParent() {
- return mUiParent;
- }
-
- /** Returns the current value of the node. */
- public abstract String getCurrentValue();
-
- /**
- * @return True if the attribute has been changed since it was last loaded
- * from the XML model.
- */
- public final boolean isDirty() {
- return mIsDirty;
- }
-
- /**
- * Sets whether the attribute is dirty and also notifies the editor some part's dirty
- * flag as changed.
- * <p/>
- * Subclasses should set the to true as a result of user interaction with the widgets in
- * the section and then should set to false when the commit() method completed.
- *
- * @param isDirty the new value to set the dirty-flag to
- */
- public void setDirty(boolean isDirty) {
- boolean wasDirty = mIsDirty;
- mIsDirty = isDirty;
- // TODO: for unknown attributes, getParent() != null && getParent().getEditor() != null
- if (wasDirty != isDirty) {
- AndroidXmlEditor editor = getUiParent().getEditor();
- if (editor != null) {
- editor.editorDirtyStateChanged();
- }
- }
- }
-
- /**
- * Sets the error flag value.
- * @param errorFlag the error flag
- */
- public final void setHasError(boolean errorFlag) {
- mHasError = errorFlag;
- }
-
- /**
- * Returns whether this node has errors.
- */
- public final boolean hasError() {
- return mHasError;
- }
-
- /**
- * Called once by the parent user interface to creates the necessary
- * user interface to edit this attribute.
- * <p/>
- * This method can be called more than once in the life cycle of an UI node,
- * typically when the UI is part of a master-detail tree, as pages are swapped.
- *
- * @param parent The composite where to create the user interface.
- * @param managedForm The managed form owning this part.
- */
- public abstract void createUiControl(Composite parent, IManagedForm managedForm);
-
- /**
- * Used to get a list of all possible values for this UI attribute.
- * <p/>
- * This is used, among other things, by the XML Content Assists to complete values
- * for an attribute.
- * <p/>
- * Implementations that do not have any known values should return null.
- *
- * @param prefix An optional prefix string, which is whatever the user has already started
- * typing. Can be null or an empty string. The implementation can use this to filter choices
- * and only return strings that match this prefix. A lazy or default implementation can
- * simply ignore this and return everything.
- * @return A list of possible completion values, and empty array or null.
- */
- public abstract String[] getPossibleValues(String prefix);
-
- /**
- * Called when the XML is being loaded or has changed to
- * update the value held by this user interface attribute node.
- * <p/>
- * The XML Node <em>may</em> be null, which denotes that the attribute is not
- * specified in the XML model. In general, this means the "default" value of the
- * attribute should be used.
- * <p/>
- * The caller doesn't really know if attributes have changed,
- * so it will call this to refresh the attribute anyway. It's up to the
- * UI implementation to minimize refreshes.
- *
- * @param node the node to read the value from
- */
- public abstract void updateValue(Node node);
-
- /**
- * Called by the user interface when the editor is saved or its state changed
- * and the modified attributes must be committed (i.e. written) to the XML model.
- * <p/>
- * Important behaviors:
- * <ul>
- * <li>The caller *must* have called IStructuredModel.aboutToChangeModel before.
- * The implemented methods must assume it is safe to modify the XML model.
- * <li>On success, the implementation *must* call setDirty(false).
- * <li>On failure, the implementation can fail with an exception, which
- * is trapped and logged by the caller, or do nothing, whichever is more
- * appropriate.
- * </ul>
- */
- public abstract void commit();
-
- // ---- Implements Comparable ----
-
- @Override
- public int compareTo(UiAttributeNode o) {
- return XmlAttributeSortOrder.compareAttributes(mDescriptor.getXmlLocalName(),
- o.mDescriptor.getXmlLocalName());
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java
deleted file mode 100644
index 1a85ea682..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents an XML document node that can be modified by the user interface in the XML editor.
- * <p/>
- * The structure of a given {@link UiDocumentNode} is declared by a corresponding
- * {@link DocumentDescriptor}.
- */
-public class UiDocumentNode extends UiElementNode {
-
- /**
- * Creates a new {@link UiDocumentNode} described by a given {@link DocumentDescriptor}.
- *
- * @param documentDescriptor The {@link DocumentDescriptor} for the XML node. Cannot be null.
- */
- public UiDocumentNode(DocumentDescriptor documentDescriptor) {
- super(documentDescriptor);
- }
-
- /**
- * Computes a short string describing the UI node suitable for tree views.
- * Uses the element's attribute "android:name" if present, or the "android:label" one
- * followed by the element's name.
- *
- * @return A short string describing the UI node suitable for tree views.
- */
- @Override
- public String getShortDescription() {
- return "Document"; //$NON-NLS-1$
- }
-
- /**
- * Computes a "breadcrumb trail" description for this node.
- *
- * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect
- * when called on the root node itself.
- * @return The "breadcrumb trail" description for this node.
- */
- @Override
- public String getBreadcrumbTrailDescription(boolean include_root) {
- return "Document"; //$NON-NLS-1$
- }
-
- /**
- * This method throws an exception when attempted to assign a parent, since XML documents
- * cannot have a parent. It is OK to assign null.
- */
- @Override
- protected void setUiParent(UiElementNode parent) {
- if (parent != null) {
- // DEBUG. Change to log warning.
- throw new UnsupportedOperationException("Documents can't have UI parents"); //$NON-NLS-1$
- }
- super.setUiParent(null);
- }
-
- /**
- * Populate this element node with all values from the given XML node.
- *
- * This fails if the given XML node has a different element name -- it won't change the
- * type of this ui node.
- *
- * This method can be both used for populating values the first time and updating values
- * after the XML model changed.
- *
- * @param xml_node The XML node to mirror
- * @return Returns true if the XML structure has changed (nodes added, removed or replaced)
- */
- @Override
- public boolean loadFromXmlNode(Node xml_node) {
- boolean structure_changed = (getXmlDocument() != xml_node);
- setXmlDocument((Document) xml_node);
- structure_changed |= super.loadFromXmlNode(xml_node);
- if (structure_changed) {
- invokeUiUpdateListeners(UiUpdateState.CHILDREN_CHANGED);
- }
- return structure_changed;
- }
-
- /**
- * This method throws an exception if there is no underlying XML document.
- * <p/>
- * XML documents cannot be created per se -- they are a by-product of the StructuredEditor
- * XML parser.
- *
- * @return The current value of getXmlDocument().
- */
- @Override
- public Node createXmlNode() {
- if (getXmlDocument() == null) {
- // By design, a document node cannot be created, it is owned by the XML parser.
- // By "design" this should never happen since the XML parser always creates an XML
- // document container, even for an empty file.
- throw new UnsupportedOperationException("Documents cannot be created"); //$NON-NLS-1$
- }
- return getXmlDocument();
- }
-
- /**
- * This method throws an exception and does not even try to delete the XML document.
- * <p/>
- * XML documents cannot be deleted per se -- they are a by-product of the StructuredEditor
- * XML parser.
- *
- * @return The removed node or null if it didn't exist in the first place.
- */
- @Override
- public Node deleteXmlNode() {
- // DEBUG. Change to log warning.
- throw new UnsupportedOperationException("Documents cannot be deleted"); //$NON-NLS-1$
- }
-
- /**
- * Returns all elements in this document.
- *
- * @param document the document
- * @return all elements in the document
- */
- public static List<UiElementNode> getAllElements(UiDocumentNode document) {
- List<UiElementNode> elements = new ArrayList<UiElementNode>(64);
- for (UiElementNode child : document.getUiChildren()) {
- addElements(child, elements);
- }
- return elements;
- }
-
- private static void addElements(UiElementNode node, List<UiElementNode> elements) {
- elements.add(node);
-
- for (UiElementNode child : node.getUiChildren()) {
- addElements(child, elements);
- }
- }
-}
-
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
deleted file mode 100644
index ed447c634..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ /dev/null
@@ -1,2160 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
-import static com.android.SdkConstants.ANDROID_SUPPORT_PKG_PREFIX;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-
-import com.android.SdkConstants;
-import com.android.annotations.VisibleForTesting;
-import com.android.ide.common.api.IAttributeInfo.Format;
-import com.android.ide.common.resources.platform.AttributeInfo;
-import com.android.ide.common.xml.XmlAttributeSortOrder;
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.IUnknownDescriptorProvider;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
-import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
-import com.android.ide.eclipse.adt.internal.editors.otherxml.descriptors.OtherXmlDescriptors;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
-import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-import com.android.utils.SdkUtils;
-import com.android.utils.XmlUtils;
-
-import org.eclipse.jface.text.TextUtilities;
-import org.eclipse.jface.viewers.StyledString;
-import org.eclipse.ui.views.properties.IPropertyDescriptor;
-import org.eclipse.ui.views.properties.IPropertySource;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.xml.core.internal.document.ElementImpl;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * Represents an XML node that can be modified by the user interface in the XML editor.
- * <p/>
- * Each tree viewer used in the application page's parts needs to keep a model representing
- * each underlying node in the tree. This interface represents the base type for such a node.
- * <p/>
- * Each node acts as an intermediary model between the actual XML model (the real data support)
- * and the tree viewers or the corresponding page parts.
- * <p/>
- * Element nodes don't contain data per se. Their data is contained in their attributes
- * as well as their children's attributes, see {@link UiAttributeNode}.
- * <p/>
- * The structure of a given {@link UiElementNode} is declared by a corresponding
- * {@link ElementDescriptor}.
- * <p/>
- * The class implements {@link IPropertySource}, in order to fill the Eclipse property tab when
- * an element is selected. The {@link AttributeDescriptor} are used property descriptors.
- */
-@SuppressWarnings("restriction") // XML model
-public class UiElementNode implements IPropertySource {
-
- /** List of prefixes removed from android:id strings when creating short descriptions. */
- private static String[] ID_PREFIXES = {
- "@android:id/", //$NON-NLS-1$
- NEW_ID_PREFIX, ID_PREFIX, "@+", "@" }; //$NON-NLS-1$ //$NON-NLS-2$
-
- /** The element descriptor for the node. Always present, never null. */
- private ElementDescriptor mDescriptor;
- /** The parent element node in the UI model. It is null for a root element or until
- * the node is attached to its parent. */
- private UiElementNode mUiParent;
- /** The {@link AndroidXmlEditor} handling the UI hierarchy. This is defined only for the
- * root node. All children have the value set to null and query their parent. */
- private AndroidXmlEditor mEditor;
- /** The XML {@link Document} model that is being mirror by the UI model. This is defined
- * only for the root node. All children have the value set to null and query their parent. */
- private Document mXmlDocument;
- /** The XML {@link Node} mirror by this UI node. This can be null for mandatory UI node which
- * have no corresponding XML node or for new UI nodes before their XML node is set. */
- private Node mXmlNode;
- /** The list of all UI children nodes. Can be empty but never null. There's one UI children
- * node per existing XML children node. */
- private ArrayList<UiElementNode> mUiChildren;
- /** The list of <em>all</em> UI attributes, as declared in the {@link ElementDescriptor}.
- * The list is always defined and never null. Unlike the UiElementNode children list, this
- * is always defined, even for attributes that do not exist in the XML model - that's because
- * "missing" attributes in the XML model simply mean a default value is used. Also note that
- * the underlying collection is a map, so order is not respected. To get the desired attribute
- * order, iterate through the {@link ElementDescriptor}'s attribute list. */
- private HashMap<AttributeDescriptor, UiAttributeNode> mUiAttributes;
- private HashSet<UiAttributeNode> mUnknownUiAttributes;
- /** A read-only view of the UI children node collection. */
- private List<UiElementNode> mReadOnlyUiChildren;
- /** A read-only view of the UI attributes collection. */
- private Collection<UiAttributeNode> mCachedAllUiAttributes;
- /** A map of hidden attribute descriptors. Key is the XML name. */
- private Map<String, AttributeDescriptor> mCachedHiddenAttributes;
- /** An optional list of {@link IUiUpdateListener}. Most element nodes will not have any
- * listeners attached, so the list is only created on demand and can be null. */
- private List<IUiUpdateListener> mUiUpdateListeners;
- /** A provider that knows how to create {@link ElementDescriptor} from unmapped XML names.
- * The default is to have one that creates new {@link ElementDescriptor}. */
- private IUnknownDescriptorProvider mUnknownDescProvider;
- /** Error Flag */
- private boolean mHasError;
-
- /**
- * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}.
- *
- * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null.
- */
- public UiElementNode(ElementDescriptor elementDescriptor) {
- mDescriptor = elementDescriptor;
- clearContent();
- }
-
- @Override
- public String toString() {
- return String.format("%s [desc: %s, parent: %s, children: %d]", //$NON-NLS-1$
- this.getClass().getSimpleName(),
- mDescriptor,
- mUiParent != null ? mUiParent.toString() : "none", //$NON-NLS-1$
- mUiChildren != null ? mUiChildren.size() : 0
- );
- }
-
- /**
- * Clears the {@link UiElementNode} by resetting the children list and
- * the {@link UiAttributeNode}s list.
- * Also resets the attached XML node, document, editor if any.
- * <p/>
- * The parent {@link UiElementNode} node is not reset so that it's position
- * in the hierarchy be left intact, if any.
- */
- /* package */ void clearContent() {
- mXmlNode = null;
- mXmlDocument = null;
- mEditor = null;
- clearAttributes();
- mReadOnlyUiChildren = null;
- if (mUiChildren == null) {
- mUiChildren = new ArrayList<UiElementNode>();
- } else {
- // We can't remove mandatory nodes, we just clear them.
- for (int i = mUiChildren.size() - 1; i >= 0; --i) {
- removeUiChildAtIndex(i);
- }
- }
- }
-
- /**
- * Clears the internal list of attributes, the read-only cached version of it
- * and the read-only cached hidden attribute list.
- */
- private void clearAttributes() {
- mUiAttributes = null;
- mCachedAllUiAttributes = null;
- mCachedHiddenAttributes = null;
- mUnknownUiAttributes = new HashSet<UiAttributeNode>();
- }
-
- /**
- * Gets or creates the internal UiAttributes list.
- * <p/>
- * When the descriptor derives from ViewElementDescriptor, this list depends on the
- * current UiParent node.
- *
- * @return A new set of {@link UiAttributeNode} that matches the expected
- * attributes for this node.
- */
- private HashMap<AttributeDescriptor, UiAttributeNode> getInternalUiAttributes() {
- if (mUiAttributes == null) {
- AttributeDescriptor[] attrList = getAttributeDescriptors();
- mUiAttributes = new HashMap<AttributeDescriptor, UiAttributeNode>(attrList.length);
- for (AttributeDescriptor desc : attrList) {
- UiAttributeNode uiNode = desc.createUiNode(this);
- if (uiNode != null) { // Some AttributeDescriptors do not have UI associated
- mUiAttributes.put(desc, uiNode);
- }
- }
- }
- return mUiAttributes;
- }
-
- /**
- * Computes a short string describing the UI node suitable for tree views.
- * Uses the element's attribute "android:name" if present, or the "android:label" one
- * followed by the element's name if not repeated.
- *
- * @return A short string describing the UI node suitable for tree views.
- */
- public String getShortDescription() {
- String name = mDescriptor.getUiName();
- String attr = getDescAttribute();
- if (attr != null) {
- // If the ui name is repeated in the attribute value, don't use it.
- // Typical case is to avoid ".pkg.MyActivity (Activity)".
- if (attr.contains(name)) {
- return attr;
- } else {
- return String.format("%1$s (%2$s)", attr, name);
- }
- }
-
- return name;
- }
-
- /** Returns the key attribute that can be used to describe this node, or null */
- private String getDescAttribute() {
- if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) {
- // Application and Manifest nodes have a special treatment: they are unique nodes
- // so we don't bother trying to differentiate their strings and we fall back to
- // just using the UI name below.
- Element elem = (Element) mXmlNode;
-
- String attr = _Element_getAttributeNS(elem,
- SdkConstants.NS_RESOURCES,
- AndroidManifestDescriptors.ANDROID_NAME_ATTR);
- if (attr == null || attr.length() == 0) {
- attr = _Element_getAttributeNS(elem,
- SdkConstants.NS_RESOURCES,
- AndroidManifestDescriptors.ANDROID_LABEL_ATTR);
- } else if (mXmlNode.getNodeName().equals(SdkConstants.VIEW_FRAGMENT)) {
- attr = attr.substring(attr.lastIndexOf('.') + 1);
- }
- if (attr == null || attr.length() == 0) {
- attr = _Element_getAttributeNS(elem,
- SdkConstants.NS_RESOURCES,
- OtherXmlDescriptors.PREF_KEY_ATTR);
- }
- if (attr == null || attr.length() == 0) {
- attr = _Element_getAttributeNS(elem,
- null, // no namespace
- SdkConstants.ATTR_NAME);
- }
- if (attr == null || attr.length() == 0) {
- attr = _Element_getAttributeNS(elem,
- SdkConstants.NS_RESOURCES,
- SdkConstants.ATTR_ID);
-
- if (attr != null && attr.length() > 0) {
- for (String prefix : ID_PREFIXES) {
- if (attr.startsWith(prefix)) {
- attr = attr.substring(prefix.length());
- break;
- }
- }
- }
- }
- if (attr != null && attr.length() > 0) {
- return attr;
- }
- }
-
- return null;
- }
-
- /**
- * Computes a styled string describing the UI node suitable for tree views.
- * Similar to {@link #getShortDescription()} but styles the Strings.
- *
- * @return A styled string describing the UI node suitable for tree views.
- */
- public StyledString getStyledDescription() {
- String uiName = mDescriptor.getUiName();
-
- // Special case: for <view>, show the class attribute value instead.
- // This is done here rather than in the descriptor since this depends on
- // node instance data.
- if (SdkConstants.VIEW_TAG.equals(uiName) && mXmlNode instanceof Element) {
- Element element = (Element) mXmlNode;
- String cls = element.getAttribute(ATTR_CLASS);
- if (cls != null) {
- uiName = cls.substring(cls.lastIndexOf('.') + 1);
- }
- }
-
- StyledString styledString = new StyledString();
- String attr = getDescAttribute();
- if (attr != null) {
- // Don't append the two when it's a repeat, e.g. Button01 (Button),
- // only when the ui name is not part of the attribute
- if (attr.toLowerCase(Locale.US).indexOf(uiName.toLowerCase(Locale.US)) == -1) {
- styledString.append(attr);
- styledString.append(String.format(" (%1$s)", uiName),
- StyledString.DECORATIONS_STYLER);
- } else {
- styledString.append(attr);
- }
- }
-
- if (styledString.length() == 0) {
- styledString.append(uiName);
- }
-
- return styledString;
- }
-
- /**
- * Retrieves an attribute value by local name and namespace URI.
- * <br>Per [<a href='http://www.w3.org/TR/1999/REC-xml-names-19990114/'>XML Namespaces</a>]
- * , applications must use the value <code>null</code> as the
- * <code>namespaceURI</code> parameter for methods if they wish to have
- * no namespace.
- * <p/>
- * Note: This is a wrapper around {@link Element#getAttributeNS(String, String)}.
- * In some versions of webtools, the getAttributeNS implementation crashes with an NPE.
- * This wrapper will return an empty string instead.
- *
- * @see Element#getAttributeNS(String, String)
- * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=318108">https://bugs.eclipse.org/bugs/show_bug.cgi?id=318108</a>
- * @return The result from {@link Element#getAttributeNS(String, String)} or an empty string.
- */
- private String _Element_getAttributeNS(Element element,
- String namespaceURI,
- String localName) {
- try {
- return element.getAttributeNS(namespaceURI, localName);
- } catch (Exception ignore) {
- return "";
- }
- }
-
- /**
- * Computes a "breadcrumb trail" description for this node.
- * It will look something like "Manifest > Application > .myactivity (Activity) > Intent-Filter"
- *
- * @param includeRoot Whether to include the root (e.g. "Manifest") or not. Has no effect
- * when called on the root node itself.
- * @return The "breadcrumb trail" description for this node.
- */
- public String getBreadcrumbTrailDescription(boolean includeRoot) {
- StringBuilder sb = new StringBuilder(getShortDescription());
-
- for (UiElementNode uiNode = getUiParent();
- uiNode != null;
- uiNode = uiNode.getUiParent()) {
- if (!includeRoot && uiNode.getUiParent() == null) {
- break;
- }
- sb.insert(0, String.format("%1$s > ", uiNode.getShortDescription())); //$NON-NLS-1$
- }
-
- return sb.toString();
- }
-
- /**
- * Sets the XML {@link Document}.
- * <p/>
- * The XML {@link Document} is initially null. The XML {@link Document} must be set only on the
- * UI root element node (this method takes care of that.)
- * @param xmlDoc The new XML document to associate this node with.
- */
- public void setXmlDocument(Document xmlDoc) {
- if (mUiParent == null) {
- mXmlDocument = xmlDoc;
- } else {
- mUiParent.setXmlDocument(xmlDoc);
- }
- }
-
- /**
- * Returns the XML {@link Document}.
- * <p/>
- * The value is initially null until the UI node is attached to its UI parent -- the value
- * of the document is then propagated.
- *
- * @return the XML {@link Document} or the parent's XML {@link Document} or null.
- */
- public Document getXmlDocument() {
- if (mXmlDocument != null) {
- return mXmlDocument;
- } else if (mUiParent != null) {
- return mUiParent.getXmlDocument();
- }
- return null;
- }
-
- /**
- * Returns the XML node associated with this UI node.
- * <p/>
- * Some {@link ElementDescriptor} are declared as being "mandatory". This means the
- * corresponding UI node will exist even if there is no corresponding XML node. Such structure
- * is created and enforced by the parent of the tree, not the element themselves. However
- * such nodes will likely not have an XML node associated, so getXmlNode() can return null.
- *
- * @return The associated XML node. Can be null for mandatory nodes.
- */
- public Node getXmlNode() {
- return mXmlNode;
- }
-
- /**
- * Returns the {@link ElementDescriptor} for this node. This is never null.
- * <p/>
- * Do not use this to call getDescriptor().getAttributes(), instead call
- * getAttributeDescriptors() which can be overridden by derived classes.
- * @return The {@link ElementDescriptor} for this node. This is never null.
- */
- public ElementDescriptor getDescriptor() {
- return mDescriptor;
- }
-
- /**
- * Returns the {@link AttributeDescriptor} array for the descriptor of this node.
- * <p/>
- * Use this instead of getDescriptor().getAttributes() -- derived classes can override
- * this to manipulate the attribute descriptor list depending on the current UI node.
- * @return The {@link AttributeDescriptor} array for the descriptor of this node.
- */
- public AttributeDescriptor[] getAttributeDescriptors() {
- return mDescriptor.getAttributes();
- }
-
- /**
- * Returns the hidden {@link AttributeDescriptor} array for the descriptor of this node.
- * This is a subset of the getAttributeDescriptors() list.
- * <p/>
- * Use this instead of getDescriptor().getHiddenAttributes() -- potentially derived classes
- * could override this to manipulate the attribute descriptor list depending on the current
- * UI node. There's no need for it right now so keep it private.
- */
- private Map<String, AttributeDescriptor> getHiddenAttributeDescriptors() {
- if (mCachedHiddenAttributes == null) {
- mCachedHiddenAttributes = new HashMap<String, AttributeDescriptor>();
- for (AttributeDescriptor attrDesc : getAttributeDescriptors()) {
- if (attrDesc instanceof XmlnsAttributeDescriptor) {
- mCachedHiddenAttributes.put(
- ((XmlnsAttributeDescriptor) attrDesc).getXmlNsName(),
- attrDesc);
- }
- }
- }
- return mCachedHiddenAttributes;
- }
-
- /**
- * Sets the parent of this UiElementNode.
- * <p/>
- * The root node has no parent.
- */
- protected void setUiParent(UiElementNode parent) {
- mUiParent = parent;
- // Invalidate the internal UiAttributes list, as it may depend on the actual UiParent.
- clearAttributes();
- }
-
- /**
- * @return The parent {@link UiElementNode} or null if this is the root node.
- */
- public UiElementNode getUiParent() {
- return mUiParent;
- }
-
- /**
- * Returns the root {@link UiElementNode}.
- *
- * @return The root {@link UiElementNode}.
- */
- public UiElementNode getUiRoot() {
- UiElementNode root = this;
- while (root.mUiParent != null) {
- root = root.mUiParent;
- }
-
- return root;
- }
-
- /**
- * Returns the index of this sibling (where the first child has index 0, the second child
- * has index 1, and so on.)
- *
- * @return The sibling index of this node
- */
- public int getUiSiblingIndex() {
- if (mUiParent != null) {
- int index = 0;
- for (UiElementNode node : mUiParent.getUiChildren()) {
- if (node == this) {
- break;
- }
- index++;
- }
- return index;
- }
-
- return 0;
- }
-
- /**
- * Returns the previous UI sibling of this UI node. If the node does not have a previous
- * sibling, returns null.
- *
- * @return The previous UI sibling of this UI node, or null if not applicable.
- */
- public UiElementNode getUiPreviousSibling() {
- if (mUiParent != null) {
- List<UiElementNode> childlist = mUiParent.getUiChildren();
- if (childlist != null && childlist.size() > 1 && childlist.get(0) != this) {
- int index = childlist.indexOf(this);
- return index > 0 ? childlist.get(index - 1) : null;
- }
- }
- return null;
- }
-
- /**
- * Returns the next UI sibling of this UI node.
- * If the node does not have a next sibling, returns null.
- *
- * @return The next UI sibling of this UI node, or null.
- */
- public UiElementNode getUiNextSibling() {
- if (mUiParent != null) {
- List<UiElementNode> childlist = mUiParent.getUiChildren();
- if (childlist != null) {
- int size = childlist.size();
- if (size > 1 && childlist.get(size - 1) != this) {
- int index = childlist.indexOf(this);
- return index >= 0 && index < size - 1 ? childlist.get(index + 1) : null;
- }
- }
- }
- return null;
- }
-
- /**
- * Sets the {@link AndroidXmlEditor} handling this {@link UiElementNode} hierarchy.
- * <p/>
- * The editor must always be set on the root node. This method takes care of that.
- *
- * @param editor The editor to associate this node with.
- */
- public void setEditor(AndroidXmlEditor editor) {
- if (mUiParent == null) {
- mEditor = editor;
- } else {
- mUiParent.setEditor(editor);
- }
- }
-
- /**
- * Returns the {@link AndroidXmlEditor} that embeds this {@link UiElementNode}.
- * <p/>
- * The value is initially null until the node is attached to its parent -- the value
- * of the root node is then propagated.
- *
- * @return The embedding {@link AndroidXmlEditor} or null.
- */
- public AndroidXmlEditor getEditor() {
- return mUiParent == null ? mEditor : mUiParent.getEditor();
- }
-
- /**
- * Returns the Android target data for the file being edited.
- *
- * @return The Android target data for the file being edited.
- */
- public AndroidTargetData getAndroidTarget() {
- return getEditor().getTargetData();
- }
-
- /**
- * @return A read-only version of the children collection.
- */
- public List<UiElementNode> getUiChildren() {
- if (mReadOnlyUiChildren == null) {
- mReadOnlyUiChildren = Collections.unmodifiableList(mUiChildren);
- }
- return mReadOnlyUiChildren;
- }
-
- /**
- * Returns a collection containing all the known attributes as well as
- * all the unknown ui attributes.
- *
- * @return A read-only version of the attributes collection.
- */
- public Collection<UiAttributeNode> getAllUiAttributes() {
- if (mCachedAllUiAttributes == null) {
-
- List<UiAttributeNode> allValues =
- new ArrayList<UiAttributeNode>(getInternalUiAttributes().values());
- allValues.addAll(mUnknownUiAttributes);
-
- mCachedAllUiAttributes = Collections.unmodifiableCollection(allValues);
- }
- return mCachedAllUiAttributes;
- }
-
- /**
- * Returns all the unknown ui attributes, that is those we found defined in the
- * actual XML but that we don't have descriptors for.
- *
- * @return A read-only version of the unknown attributes collection.
- */
- public Collection<UiAttributeNode> getUnknownUiAttributes() {
- return Collections.unmodifiableCollection(mUnknownUiAttributes);
- }
-
- /**
- * Sets the error flag value.
- *
- * @param errorFlag the error flag
- */
- public final void setHasError(boolean errorFlag) {
- mHasError = errorFlag;
- }
-
- /**
- * Returns whether this node, its attributes, or one of the children nodes (and attributes)
- * has errors.
- *
- * @return True if this node, its attributes, or one of the children nodes (and attributes)
- * has errors.
- */
- public final boolean hasError() {
- if (mHasError) {
- return true;
- }
-
- // get the error value from the attributes.
- for (UiAttributeNode attribute : getAllUiAttributes()) {
- if (attribute.hasError()) {
- return true;
- }
- }
-
- // and now from the children.
- for (UiElementNode child : mUiChildren) {
- if (child.hasError()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns the provider that knows how to create {@link ElementDescriptor} from unmapped
- * XML names.
- * <p/>
- * The default is to have one that creates new {@link ElementDescriptor}.
- * <p/>
- * There is only one such provider in any UI model tree, attached to the root node.
- *
- * @return An instance of {@link IUnknownDescriptorProvider}. Can never be null.
- */
- public IUnknownDescriptorProvider getUnknownDescriptorProvider() {
- if (mUiParent != null) {
- return mUiParent.getUnknownDescriptorProvider();
- }
- if (mUnknownDescProvider == null) {
- // Create the default one on demand.
- mUnknownDescProvider = new IUnknownDescriptorProvider() {
-
- private final HashMap<String, ElementDescriptor> mMap =
- new HashMap<String, ElementDescriptor>();
-
- /**
- * The default is to create a new ElementDescriptor wrapping
- * the unknown XML local name and reuse previously created descriptors.
- */
- @Override
- public ElementDescriptor getDescriptor(String xmlLocalName) {
-
- ElementDescriptor desc = mMap.get(xmlLocalName);
-
- if (desc == null) {
- desc = new ElementDescriptor(xmlLocalName);
- mMap.put(xmlLocalName, desc);
- }
-
- return desc;
- }
- };
- }
- return mUnknownDescProvider;
- }
-
- /**
- * Sets the provider that knows how to create {@link ElementDescriptor} from unmapped
- * XML names.
- * <p/>
- * The default is to have one that creates new {@link ElementDescriptor}.
- * <p/>
- * There is only one such provider in any UI model tree, attached to the root node.
- *
- * @param unknownDescProvider The new provider to use. Must not be null.
- */
- public void setUnknownDescriptorProvider(IUnknownDescriptorProvider unknownDescProvider) {
- if (mUiParent == null) {
- mUnknownDescProvider = unknownDescProvider;
- } else {
- mUiParent.setUnknownDescriptorProvider(unknownDescProvider);
- }
- }
-
- /**
- * Adds a new {@link IUiUpdateListener} to the internal update listener list.
- *
- * @param listener The listener to add.
- */
- public void addUpdateListener(IUiUpdateListener listener) {
- if (mUiUpdateListeners == null) {
- mUiUpdateListeners = new ArrayList<IUiUpdateListener>();
- }
- if (!mUiUpdateListeners.contains(listener)) {
- mUiUpdateListeners.add(listener);
- }
- }
-
- /**
- * Removes an existing {@link IUiUpdateListener} from the internal update listener list.
- * Does nothing if the list is empty or the listener is not registered.
- *
- * @param listener The listener to remove.
- */
- public void removeUpdateListener(IUiUpdateListener listener) {
- if (mUiUpdateListeners != null) {
- mUiUpdateListeners.remove(listener);
- }
- }
-
- /**
- * Finds a child node relative to this node using a path-like expression.
- * F.ex. "node1/node2" would find a child "node1" that contains a child "node2" and
- * returns the latter. If there are multiple nodes with the same name at the same
- * level, always uses the first one found.
- *
- * @param path The path like expression to select a child node.
- * @return The ui node found or null.
- */
- public UiElementNode findUiChildNode(String path) {
- String[] items = path.split("/"); //$NON-NLS-1$
- UiElementNode uiNode = this;
- for (String item : items) {
- boolean nextSegment = false;
- for (UiElementNode c : uiNode.mUiChildren) {
- if (c.getDescriptor().getXmlName().equals(item)) {
- uiNode = c;
- nextSegment = true;
- break;
- }
- }
- if (!nextSegment) {
- return null;
- }
- }
- return uiNode;
- }
-
- /**
- * Finds an {@link UiElementNode} which contains the give XML {@link Node}.
- * Looks recursively in all children UI nodes.
- *
- * @param xmlNode The XML node to look for.
- * @return The {@link UiElementNode} that contains xmlNode or null if not found,
- */
- public UiElementNode findXmlNode(Node xmlNode) {
- if (xmlNode == null) {
- return null;
- }
- if (getXmlNode() == xmlNode) {
- return this;
- }
-
- for (UiElementNode uiChild : mUiChildren) {
- UiElementNode found = uiChild.findXmlNode(xmlNode);
- if (found != null) {
- return found;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the {@link UiAttributeNode} matching this attribute descriptor or
- * null if not found.
- *
- * @param attrDesc The {@link AttributeDescriptor} to match.
- * @return the {@link UiAttributeNode} matching this attribute descriptor or null
- * if not found.
- */
- public UiAttributeNode findUiAttribute(AttributeDescriptor attrDesc) {
- return getInternalUiAttributes().get(attrDesc);
- }
-
- /**
- * Populate this element node with all values from the given XML node.
- *
- * This fails if the given XML node has a different element name -- it won't change the
- * type of this ui node.
- *
- * This method can be both used for populating values the first time and updating values
- * after the XML model changed.
- *
- * @param xmlNode The XML node to mirror
- * @return Returns true if the XML structure has changed (nodes added, removed or replaced)
- */
- public boolean loadFromXmlNode(Node xmlNode) {
- boolean structureChanged = (mXmlNode != xmlNode);
- mXmlNode = xmlNode;
- if (xmlNode != null) {
- updateAttributeList(xmlNode);
- structureChanged |= updateElementList(xmlNode);
- invokeUiUpdateListeners(structureChanged ? UiUpdateState.CHILDREN_CHANGED
- : UiUpdateState.ATTR_UPDATED);
- }
- return structureChanged;
- }
-
- /**
- * Clears the UI node and reload it from the given XML node.
- * <p/>
- * This works by clearing all references to any previous XML or UI nodes and
- * then reloads the XML document from scratch. The editor reference is kept.
- * <p/>
- * This is used in the special case where the ElementDescriptor structure has changed.
- * Rather than try to diff inflated UI nodes (as loadFromXmlNode does), we don't bother
- * and reload everything. This is not subtle and should be used very rarely.
- *
- * @param xmlNode The XML node or document to reload. Can be null.
- */
- public void reloadFromXmlNode(Node xmlNode) {
- // The editor needs to be preserved, it is not affected by an XML change.
- AndroidXmlEditor editor = getEditor();
- clearContent();
- setEditor(editor);
- if (xmlNode != null) {
- setXmlDocument(xmlNode.getOwnerDocument());
- }
- // This will reload all the XML and recreate the UI structure from scratch.
- loadFromXmlNode(xmlNode);
- }
-
- /**
- * Called by attributes when they want to commit their value
- * to an XML node.
- * <p/>
- * For mandatory nodes, this makes sure the underlying XML element node
- * exists in the model. If not, it is created and assigned as the underlying
- * XML node.
- * </br>
- * For non-mandatory nodes, simply return the underlying XML node, which
- * must always exists.
- *
- * @return The XML node matching this {@link UiElementNode} or null.
- */
- public Node prepareCommit() {
- if (getDescriptor().getMandatory() != Mandatory.NOT_MANDATORY) {
- createXmlNode();
- // The new XML node has been created.
- // We don't need to refresh using loadFromXmlNode() since there are
- // no attributes or elements that need to be loading into this node.
- }
- return getXmlNode();
- }
-
- /**
- * Commits the attributes (all internal, inherited from UI parent & unknown attributes).
- * This is called by the UI when the embedding part needs to be committed.
- */
- public void commit() {
- for (UiAttributeNode uiAttr : getAllUiAttributes()) {
- uiAttr.commit();
- }
- }
-
- /**
- * Returns true if the part has been modified with respect to the data
- * loaded from the model.
- * @return True if the part has been modified with respect to the data
- * loaded from the model.
- */
- public boolean isDirty() {
- for (UiAttributeNode uiAttr : getAllUiAttributes()) {
- if (uiAttr.isDirty()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Creates the underlying XML element node for this UI node if it doesn't already
- * exists.
- *
- * @return The new value of getXmlNode() (can be null if creation failed)
- */
- public Node createXmlNode() {
- if (mXmlNode != null) {
- return null;
- }
- Node parentXmlNode = null;
- if (mUiParent != null) {
- parentXmlNode = mUiParent.prepareCommit();
- if (parentXmlNode == null) {
- // The parent failed to create its own backing XML node. Abort.
- // No need to throw an exception, the parent will most likely
- // have done so itself.
- return null;
- }
- }
-
- String elementName = getDescriptor().getXmlName();
- Document doc = getXmlDocument();
-
- // We *must* have a root node. If not, we need to abort.
- if (doc == null) {
- throw new RuntimeException(
- String.format("Missing XML document for %1$s XML node.", elementName));
- }
-
- // If we get here and parentXmlNode is null, the node is to be created
- // as the root node of the document (which can't be null, cf. check above).
- if (parentXmlNode == null) {
- parentXmlNode = doc;
- }
-
- mXmlNode = doc.createElement(elementName);
-
- // If this element does not have children, mark it as an empty tag
- // such that the XML looks like <tag/> instead of <tag></tag>
- if (!mDescriptor.hasChildren()) {
- if (mXmlNode instanceof ElementImpl) {
- ElementImpl element = (ElementImpl) mXmlNode;
- element.setEmptyTag(true);
- }
- }
-
- Node xmlNextSibling = null;
-
- UiElementNode uiNextSibling = getUiNextSibling();
- if (uiNextSibling != null) {
- xmlNextSibling = uiNextSibling.getXmlNode();
- }
-
- Node previousTextNode = null;
- if (xmlNextSibling != null) {
- Node previousNode = xmlNextSibling.getPreviousSibling();
- if (previousNode != null && previousNode.getNodeType() == Node.TEXT_NODE) {
- previousTextNode = previousNode;
- }
- } else {
- Node lastChild = parentXmlNode.getLastChild();
- if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) {
- previousTextNode = lastChild;
- }
- }
-
- String insertAfter = null;
-
- // Try to figure out the indentation node to insert. Even in auto-formatting
- // we need to do this, because it turns out the XML editor's formatter does
- // not do a very good job with completely botched up XML; it does a much better
- // job if the new XML is already mostly well formatted. Thus, the main purpose
- // of applying the real XML formatter after our own indentation attempts here is
- // to make it apply its own tab-versus-spaces indentation properties, have it
- // insert line breaks before attributes (if the user has configured that), etc.
-
- // First figure out the indentation level of the newly inserted element;
- // this is either the same as the previous sibling, or if there is no sibling,
- // it's the indentation of the parent plus one indentation level.
- boolean isFirstChild = getUiPreviousSibling() == null
- || parentXmlNode.getFirstChild() == null;
- AndroidXmlEditor editor = getEditor();
- String indent;
- String parentIndent = ""; //$NON-NLS-1$
- if (isFirstChild) {
- indent = parentIndent = editor.getIndent(parentXmlNode);
- // We need to add one level of indentation. Are we using tabs?
- // Can't get to formatting settings so let's just look at the
- // parent indentation and see if we can guess
- if (indent.length() > 0 && indent.charAt(indent.length()-1) == '\t') {
- indent = indent + '\t';
- } else {
- // Not using tabs, or we can't figure it out (because parent had no
- // indentation). In that case, indent with 4 spaces, as seems to
- // be the Android default.
- indent = indent + " "; //$NON-NLS-1$
- }
- } else {
- // Find out the indent of the previous sibling
- indent = editor.getIndent(getUiPreviousSibling().getXmlNode());
- }
-
- // We want to insert the new element BEFORE the text node which precedes
- // the next element, since that text node is the next element's indentation!
- if (previousTextNode != null) {
- xmlNextSibling = previousTextNode;
- } else {
- // If there's no previous text node, we are probably inside an
- // empty element (<LinearLayout>|</LinearLayout>) and in that case we need
- // to not only insert a newline and indentation before the new element, but
- // after it as well.
- insertAfter = parentIndent;
- }
-
- // Insert indent text node before the new element
- IStructuredDocument document = editor.getStructuredDocument();
- String newLine;
- if (document != null) {
- newLine = TextUtilities.getDefaultLineDelimiter(document);
- } else {
- newLine = SdkUtils.getLineSeparator();
- }
- Text indentNode = doc.createTextNode(newLine + indent);
- parentXmlNode.insertBefore(indentNode, xmlNextSibling);
-
- // Insert the element itself
- parentXmlNode.insertBefore(mXmlNode, xmlNextSibling);
-
- // Insert a separator after the tag. We only do this when we've inserted
- // a tag into an area where there was no whitespace before
- // (e.g. a new child of <LinearLayout></LinearLayout>).
- if (insertAfter != null) {
- Text sep = doc.createTextNode(newLine + insertAfter);
- parentXmlNode.insertBefore(sep, xmlNextSibling);
- }
-
- // Set all initial attributes in the XML node if they are not empty.
- // Iterate on the descriptor list to get the desired order and then use the
- // internal values, if any.
- List<UiAttributeNode> addAttributes = new ArrayList<UiAttributeNode>();
-
- for (AttributeDescriptor attrDesc : getAttributeDescriptors()) {
- if (attrDesc instanceof XmlnsAttributeDescriptor) {
- XmlnsAttributeDescriptor desc = (XmlnsAttributeDescriptor) attrDesc;
- Attr attr = doc.createAttributeNS(SdkConstants.XMLNS_URI,
- desc.getXmlNsName());
- attr.setValue(desc.getValue());
- attr.setPrefix(desc.getXmlNsPrefix());
- mXmlNode.getAttributes().setNamedItemNS(attr);
- } else {
- UiAttributeNode uiAttr = getInternalUiAttributes().get(attrDesc);
-
- // Don't apply the attribute immediately, instead record this attribute
- // such that we can gather all attributes and sort them first.
- // This is necessary because the XML model will *append* all attributes
- // so we want to add them in a particular order.
- // (Note that we only have to worry about UiAttributeNodes with non null
- // values, since this is a new node and we therefore don't need to attempt
- // to remove existing attributes)
- String value = uiAttr.getCurrentValue();
- if (value != null && value.length() > 0) {
- addAttributes.add(uiAttr);
- }
- }
- }
-
- // Sort and apply the attributes in order, because the Eclipse XML model will always
- // append the XML attributes, so by inserting them in our desired order they will
- // appear that way in the XML
- Collections.sort(addAttributes);
-
- for (UiAttributeNode node : addAttributes) {
- commitAttributeToXml(node, node.getCurrentValue());
- node.setDirty(false);
- }
-
- getEditor().scheduleNodeReformat(this, false);
-
- // Notify per-node listeners
- invokeUiUpdateListeners(UiUpdateState.CREATED);
- // Notify global listeners
- fireNodeCreated(this, getUiSiblingIndex());
-
- return mXmlNode;
- }
-
- /**
- * Removes the XML node corresponding to this UI node if it exists
- * and also removes all mirrored information in this UI node (i.e. children, attributes)
- *
- * @return The removed node or null if it didn't exist in the first place.
- */
- public Node deleteXmlNode() {
- if (mXmlNode == null) {
- return null;
- }
-
- int previousIndex = getUiSiblingIndex();
-
- // First clear the internals of the node and *then* actually deletes the XML
- // node (because doing so will generate an update even and this node may be
- // revisited via loadFromXmlNode).
- Node oldXmlNode = mXmlNode;
- clearContent();
-
- Node xmlParent = oldXmlNode.getParentNode();
- if (xmlParent == null) {
- xmlParent = getXmlDocument();
- }
- Node previousSibling = oldXmlNode.getPreviousSibling();
- oldXmlNode = xmlParent.removeChild(oldXmlNode);
-
- // We need to remove the text node BEFORE the removed element, since THAT's the
- // indentation node for the removed element.
- if (previousSibling != null && previousSibling.getNodeType() == Node.TEXT_NODE
- && previousSibling.getNodeValue().trim().length() == 0) {
- xmlParent.removeChild(previousSibling);
- }
-
- invokeUiUpdateListeners(UiUpdateState.DELETED);
- fireNodeDeleted(this, previousIndex);
-
- return oldXmlNode;
- }
-
- /**
- * Updates the element list for this UiElementNode.
- * At the end, the list of children UiElementNode here will match the one from the
- * provided XML {@link Node}:
- * <ul>
- * <li> Walk both the current ui children list and the xml children list at the same time.
- * <li> If we have a new xml child but already reached the end of the ui child list, add the
- * new xml node.
- * <li> Otherwise, check if the xml node is referenced later in the ui child list and if so,
- * move it here. It means the XML child list has been reordered.
- * <li> Otherwise, this is a new XML node that we add in the middle of the ui child list.
- * <li> At the end, we may have finished walking the xml child list but still have remaining
- * ui children, simply delete them as they matching trailing xml nodes that have been
- * removed unless they are mandatory ui nodes.
- * </ul>
- * Note that only the first case is used when populating the ui list the first time.
- *
- * @param xmlNode The XML node to mirror
- * @return True when the XML structure has changed.
- */
- protected boolean updateElementList(Node xmlNode) {
- boolean structureChanged = false;
- boolean hasMandatoryLast = false;
- int uiIndex = 0;
- Node xmlChild = xmlNode.getFirstChild();
- while (xmlChild != null) {
- if (xmlChild.getNodeType() == Node.ELEMENT_NODE) {
- String elementName = xmlChild.getNodeName();
- UiElementNode uiNode = null;
- CustomViewDescriptorService service = CustomViewDescriptorService.getInstance();
- if (mUiChildren.size() <= uiIndex) {
- // A new node is being added at the end of the list
- ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
- false /* recursive */);
- if (desc == null && elementName.indexOf('.') != -1 &&
- (!elementName.startsWith(ANDROID_PKG_PREFIX)
- || elementName.startsWith(ANDROID_SUPPORT_PKG_PREFIX))) {
- AndroidXmlEditor editor = getEditor();
- if (editor != null && editor.getProject() != null) {
- desc = service.getDescriptor(editor.getProject(), elementName);
- }
- }
- if (desc == null) {
- // Unknown node. Create a temporary descriptor for it.
- // We'll add unknown attributes to it later.
- IUnknownDescriptorProvider p = getUnknownDescriptorProvider();
- desc = p.getDescriptor(elementName);
- }
- structureChanged = true;
- uiNode = appendNewUiChild(desc);
- uiIndex++;
- } else {
- // A new node is being inserted or moved.
- // Note: mandatory nodes can be created without an XML node in which case
- // getXmlNode() is null.
- UiElementNode uiChild;
- int n = mUiChildren.size();
- for (int j = uiIndex; j < n; j++) {
- uiChild = mUiChildren.get(j);
- if (uiChild.getXmlNode() != null && uiChild.getXmlNode() == xmlChild) {
- if (j > uiIndex) {
- // Found the same XML node at some later index, now move it here.
- mUiChildren.remove(j);
- mUiChildren.add(uiIndex, uiChild);
- structureChanged = true;
- }
- uiNode = uiChild;
- uiIndex++;
- break;
- }
- }
-
- if (uiNode == null) {
- // Look for an unused mandatory node with no XML node attached
- // referencing the same XML element name
- for (int j = uiIndex; j < n; j++) {
- uiChild = mUiChildren.get(j);
- if (uiChild.getXmlNode() == null &&
- uiChild.getDescriptor().getMandatory() !=
- Mandatory.NOT_MANDATORY &&
- uiChild.getDescriptor().getXmlName().equals(elementName)) {
-
- if (j > uiIndex) {
- // Found it, now move it here
- mUiChildren.remove(j);
- mUiChildren.add(uiIndex, uiChild);
- }
- // Assign the XML node to this empty mandatory element.
- uiChild.mXmlNode = xmlChild;
- structureChanged = true;
- uiNode = uiChild;
- uiIndex++;
- }
- }
- }
-
- if (uiNode == null) {
- // Inserting new node
- ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
- false /* recursive */);
- if (desc == null && elementName.indexOf('.') != -1 &&
- (!elementName.startsWith(ANDROID_PKG_PREFIX)
- || elementName.startsWith(ANDROID_SUPPORT_PKG_PREFIX))) {
- AndroidXmlEditor editor = getEditor();
- if (editor != null && editor.getProject() != null) {
- desc = service.getDescriptor(editor.getProject(), elementName);
- }
- }
- if (desc == null) {
- // Unknown node. Create a temporary descriptor for it.
- // We'll add unknown attributes to it later.
- IUnknownDescriptorProvider p = getUnknownDescriptorProvider();
- desc = p.getDescriptor(elementName);
- } else {
- structureChanged = true;
- uiNode = insertNewUiChild(uiIndex, desc);
- uiIndex++;
- }
- }
- }
- if (uiNode != null) {
- // If we touched an UI Node, even an existing one, refresh its content.
- // For new nodes, this will populate them recursively.
- structureChanged |= uiNode.loadFromXmlNode(xmlChild);
-
- // Remember if there are any mandatory-last nodes to reorder.
- hasMandatoryLast |=
- uiNode.getDescriptor().getMandatory() == Mandatory.MANDATORY_LAST;
- }
- }
- xmlChild = xmlChild.getNextSibling();
- }
-
- // There might be extra UI nodes at the end if the XML node list got shorter.
- for (int index = mUiChildren.size() - 1; index >= uiIndex; --index) {
- structureChanged |= removeUiChildAtIndex(index);
- }
-
- if (hasMandatoryLast) {
- // At least one mandatory-last uiNode was moved. Let's see if we can
- // move them back to the last position. That's possible if the only
- // thing between these and the end are other mandatory empty uiNodes
- // (mandatory uiNodes with no XML attached are pure "virtual" reserved
- // slots and it's ok to reorganize them but other can't.)
- int n = mUiChildren.size() - 1;
- for (int index = n; index >= 0; index--) {
- UiElementNode uiChild = mUiChildren.get(index);
- Mandatory mand = uiChild.getDescriptor().getMandatory();
- if (mand == Mandatory.MANDATORY_LAST && index < n) {
- // Remove it from index and move it back at the end of the list.
- mUiChildren.remove(index);
- mUiChildren.add(uiChild);
- } else if (mand == Mandatory.NOT_MANDATORY || uiChild.getXmlNode() != null) {
- // We found at least one non-mandatory or a mandatory node with an actual
- // XML attached, so there's nothing we can reorganize past this point.
- break;
- }
- }
- }
-
- return structureChanged;
- }
-
- /**
- * Internal helper to remove an UI child node given by its index in the
- * internal child list.
- *
- * Also invokes the update listener on the node to be deleted *after* the node has
- * been removed.
- *
- * @param uiIndex The index of the UI child to remove, range 0 .. mUiChildren.size()-1
- * @return True if the structure has changed
- * @throws IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you
- * know that could never happen unless the computer is on fire or something.
- */
- private boolean removeUiChildAtIndex(int uiIndex) {
- UiElementNode uiNode = mUiChildren.get(uiIndex);
- ElementDescriptor desc = uiNode.getDescriptor();
-
- try {
- if (uiNode.getDescriptor().getMandatory() != Mandatory.NOT_MANDATORY) {
- // This is a mandatory node. Such a node must exist in the UiNode hierarchy
- // even if there's no XML counterpart. However we only need to keep one.
-
- // Check if the parent (e.g. this node) has another similar ui child node.
- boolean keepNode = true;
- for (UiElementNode child : mUiChildren) {
- if (child != uiNode && child.getDescriptor() == desc) {
- // We found another child with the same descriptor that is not
- // the node we want to remove. This means we have one mandatory
- // node so we can safely remove uiNode.
- keepNode = false;
- break;
- }
- }
-
- if (keepNode) {
- // We can't remove a mandatory node as we need to keep at least one
- // mandatory node in the parent. Instead we just clear its content
- // (including its XML Node reference).
-
- // A mandatory node with no XML means it doesn't really exist, so it can't be
- // deleted. So the structure will change only if the ui node is actually
- // associated to an XML node.
- boolean xmlExists = (uiNode.getXmlNode() != null);
-
- uiNode.clearContent();
- return xmlExists;
- }
- }
-
- mUiChildren.remove(uiIndex);
-
- return true;
- } finally {
- // Tell listeners that a node has been removed.
- // The model has already been modified.
- invokeUiUpdateListeners(UiUpdateState.DELETED);
- }
- }
-
- /**
- * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
- * and appends it to the end of the element children list.
- *
- * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
- * @return The new UI node that has been appended
- */
- public UiElementNode appendNewUiChild(ElementDescriptor descriptor) {
- UiElementNode uiNode;
- uiNode = descriptor.createUiNode();
- mUiChildren.add(uiNode);
- uiNode.setUiParent(this);
- uiNode.invokeUiUpdateListeners(UiUpdateState.CREATED);
- return uiNode;
- }
-
- /**
- * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
- * and inserts it in the element children list at the specified position.
- *
- * @param index The position where to insert in the element children list.
- * Shifts the element currently at that position (if any) and any
- * subsequent elements to the right (adds one to their indices).
- * Index must >= 0 and <= getUiChildren.size().
- * Using size() means to append to the end of the list.
- * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
- * @return The new UI node.
- */
- public UiElementNode insertNewUiChild(int index, ElementDescriptor descriptor) {
- UiElementNode uiNode;
- uiNode = descriptor.createUiNode();
- mUiChildren.add(index, uiNode);
- uiNode.setUiParent(this);
- uiNode.invokeUiUpdateListeners(UiUpdateState.CREATED);
- return uiNode;
- }
-
- /**
- * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}
- * using the values from the XML element.
- * <p/>
- * For a given {@link UiElementNode}, the attribute list always exists in
- * full and is totally independent of whether the XML model actually
- * has the corresponding attributes.
- * <p/>
- * For each attribute declared in this {@link UiElementNode}, get
- * the corresponding XML attribute. It may not exist, in which case the
- * value will be null. We don't really know if a value has changed, so
- * the updateValue() is called on the UI attribute in all cases.
- *
- * @param xmlNode The XML node to mirror
- */
- protected void updateAttributeList(Node xmlNode) {
- NamedNodeMap xmlAttrMap = xmlNode.getAttributes();
- HashSet<Node> visited = new HashSet<Node>();
-
- // For all known (i.e. expected) UI attributes, find an existing XML attribute of
- // same (uri, local name) and update the internal Ui attribute value.
- for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) {
- AttributeDescriptor desc = uiAttr.getDescriptor();
- if (!(desc instanceof SeparatorAttributeDescriptor)) {
- Node xmlAttr = xmlAttrMap == null ? null :
- xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName());
- uiAttr.updateValue(xmlAttr);
- visited.add(xmlAttr);
- }
- }
-
- // Clone the current list of unknown attributes. We'll then remove from this list when
- // we find attributes which are still unknown. What will be left are the old unknown
- // attributes that have been deleted in the current XML attribute list.
- @SuppressWarnings("unchecked")
- HashSet<UiAttributeNode> deleted = (HashSet<UiAttributeNode>) mUnknownUiAttributes.clone();
-
- // We need to ignore hidden attributes.
- Map<String, AttributeDescriptor> hiddenAttrDesc = getHiddenAttributeDescriptors();
-
- // Traverse the actual XML attribute list to find unknown attributes
- if (xmlAttrMap != null) {
- for (int i = 0; i < xmlAttrMap.getLength(); i++) {
- Node xmlAttr = xmlAttrMap.item(i);
- // Ignore attributes which have actual descriptors
- if (visited.contains(xmlAttr)) {
- continue;
- }
-
- String xmlFullName = xmlAttr.getNodeName();
-
- // Ignore attributes which are hidden (based on the prefix:localName key)
- if (hiddenAttrDesc.containsKey(xmlFullName)) {
- continue;
- }
-
- String xmlAttrLocalName = xmlAttr.getLocalName();
- String xmlNsUri = xmlAttr.getNamespaceURI();
-
- UiAttributeNode uiAttr = null;
- for (UiAttributeNode a : mUnknownUiAttributes) {
- String aLocalName = a.getDescriptor().getXmlLocalName();
- String aNsUri = a.getDescriptor().getNamespaceUri();
- if (aLocalName.equals(xmlAttrLocalName) &&
- (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) {
- // This attribute is still present in the unknown list
- uiAttr = a;
- // It has not been deleted
- deleted.remove(a);
- break;
- }
- }
- if (uiAttr == null) {
- uiAttr = addUnknownAttribute(xmlFullName, xmlAttrLocalName, xmlNsUri);
- }
-
- uiAttr.updateValue(xmlAttr);
- }
-
- // Remove from the internal list unknown attributes that have been deleted from the xml
- for (UiAttributeNode a : deleted) {
- mUnknownUiAttributes.remove(a);
- mCachedAllUiAttributes = null;
- }
- }
- }
-
- /**
- * Create a new temporary text attribute descriptor for the unknown attribute
- * and returns a new {@link UiAttributeNode} associated to this descriptor.
- * <p/>
- * The attribute is not marked as dirty, doing so is up to the caller.
- */
- private UiAttributeNode addUnknownAttribute(String xmlFullName,
- String xmlAttrLocalName, String xmlNsUri) {
- // Create a new unknown attribute of format string
- TextAttributeDescriptor desc = new TextAttributeDescriptor(
- xmlAttrLocalName, // xml name
- xmlNsUri, // ui name
- new AttributeInfo(xmlAttrLocalName, Format.STRING_SET)
- );
- UiAttributeNode uiAttr = desc.createUiNode(this);
- mUnknownUiAttributes.add(uiAttr);
- mCachedAllUiAttributes = null;
- return uiAttr;
- }
-
- /**
- * Invoke all registered {@link IUiUpdateListener} listening on this UI update for this node.
- */
- protected void invokeUiUpdateListeners(UiUpdateState state) {
- if (mUiUpdateListeners != null) {
- for (IUiUpdateListener listener : mUiUpdateListeners) {
- try {
- listener.uiElementNodeUpdated(this, state);
- } catch (Exception e) {
- // prevent a crashing listener from crashing the whole invocation chain
- AdtPlugin.log(e, "UIElement Listener failed: %s, state=%s", //$NON-NLS-1$
- getBreadcrumbTrailDescription(true),
- state.toString());
- }
- }
- }
- }
-
- // --- for derived implementations only ---
-
- @VisibleForTesting
- public void setXmlNode(Node xmlNode) {
- mXmlNode = xmlNode;
- }
-
- public void refreshUi() {
- invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED);
- }
-
-
- // ------------- Helpers
-
- /**
- * Helper method to commit a single attribute value to XML.
- * <p/>
- * This method updates the XML regardless of the current XML value.
- * Callers should check first if an update is needed.
- * If the new value is empty, the XML attribute will be actually removed.
- * <p/>
- * Note that the caller MUST ensure that modifying the underlying XML model is
- * safe and must take care of marking the model as dirty if necessary.
- *
- * @see AndroidXmlEditor#wrapEditXmlModel(Runnable)
- *
- * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode.
- * @param newValue The new value to set.
- * @return True if the XML attribute was modified or removed, false if nothing changed.
- */
- public boolean commitAttributeToXml(UiAttributeNode uiAttr, String newValue) {
- // Get (or create) the underlying XML element node that contains the attributes.
- Node element = prepareCommit();
- if (element != null && uiAttr != null) {
- String attrLocalName = uiAttr.getDescriptor().getXmlLocalName();
- String attrNsUri = uiAttr.getDescriptor().getNamespaceUri();
-
- NamedNodeMap attrMap = element.getAttributes();
- if (newValue == null || newValue.length() == 0) {
- // Remove attribute if it's empty
- if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) {
- attrMap.removeNamedItemNS(attrNsUri, attrLocalName);
- return true;
- }
- } else {
- // Add or replace an attribute
- Document doc = element.getOwnerDocument();
- if (doc != null) {
- Attr attr;
- if (attrNsUri != null && attrNsUri.length() > 0) {
- attr = (Attr) attrMap.getNamedItemNS(attrNsUri, attrLocalName);
- if (attr == null) {
- attr = doc.createAttributeNS(attrNsUri, attrLocalName);
- attr.setPrefix(XmlUtils.lookupNamespacePrefix(element, attrNsUri));
- attrMap.setNamedItemNS(attr);
- }
- } else {
- attr = (Attr) attrMap.getNamedItem(attrLocalName);
- if (attr == null) {
- attr = doc.createAttribute(attrLocalName);
- attrMap.setNamedItem(attr);
- }
- }
- attr.setValue(newValue);
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Helper method to commit all dirty attributes values to XML.
- * <p/>
- * This method is useful if {@link #setAttributeValue(String, String, String, boolean)} has
- * been called more than once and all the attributes marked as dirty must be committed to
- * the XML. It calls {@link #commitAttributeToXml(UiAttributeNode, String)} on each dirty
- * attribute.
- * <p/>
- * Note that the caller MUST ensure that modifying the underlying XML model is
- * safe and must take care of marking the model as dirty if necessary.
- *
- * @see AndroidXmlEditor#wrapEditXmlModel(Runnable)
- *
- * @return True if one or more values were actually modified or removed,
- * false if nothing changed.
- */
- @SuppressWarnings("null") // Eclipse is confused by the logic and gets it wrong
- public boolean commitDirtyAttributesToXml() {
- boolean result = false;
- List<UiAttributeNode> dirtyAttributes = new ArrayList<UiAttributeNode>();
- for (UiAttributeNode uiAttr : getAllUiAttributes()) {
- if (uiAttr.isDirty()) {
- String value = uiAttr.getCurrentValue();
- if (value != null && value.length() > 0) {
- // Defer the new attributes: set these last and in order
- dirtyAttributes.add(uiAttr);
- } else {
- result |= commitAttributeToXml(uiAttr, value);
- uiAttr.setDirty(false);
- }
- }
- }
- if (dirtyAttributes.size() > 0) {
- result = true;
-
- Collections.sort(dirtyAttributes);
-
- // The Eclipse XML model will *always* append new attributes.
- // Therefore, if any of the dirty attributes are new, they will appear
- // after any existing, clean attributes on the element. To fix this,
- // we need to first remove any of these attributes, then insert them
- // back in the right order.
- Node element = prepareCommit();
- if (element == null) {
- return result;
- }
-
- if (AdtPrefs.getPrefs().getFormatGuiXml() && getEditor().supportsFormatOnGuiEdit()) {
- // If auto formatting, don't bother with attribute sorting here since the
- // order will be corrected as soon as the edit is committed anyway
- for (UiAttributeNode uiAttribute : dirtyAttributes) {
- commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
- uiAttribute.setDirty(false);
- }
-
- return result;
- }
-
- AttributeDescriptor descriptor = dirtyAttributes.get(0).getDescriptor();
- String firstName = descriptor.getXmlLocalName();
- String firstNamePrefix = null;
- String namespaceUri = descriptor.getNamespaceUri();
- if (namespaceUri != null) {
- firstNamePrefix = XmlUtils.lookupNamespacePrefix(element, namespaceUri);
- }
- NamedNodeMap attributes = ((Element) element).getAttributes();
- List<Attr> move = new ArrayList<Attr>();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- if (XmlAttributeSortOrder.compareAttributes(
- attribute.getPrefix(), attribute.getLocalName(),
- firstNamePrefix, firstName) > 0) {
- move.add(attribute);
- }
- }
-
- for (Attr attribute : move) {
- if (attribute.getNamespaceURI() != null) {
- attributes.removeNamedItemNS(attribute.getNamespaceURI(),
- attribute.getLocalName());
- } else {
- attributes.removeNamedItem(attribute.getName());
- }
- }
-
- // Merge back the removed DOM attribute nodes and the new UI attribute nodes.
- // In cases where the attribute DOM name and the UI attribute names equal,
- // skip the DOM nodes and just apply the UI attributes.
- int domAttributeIndex = 0;
- int domAttributeIndexMax = move.size();
- int uiAttributeIndex = 0;
- int uiAttributeIndexMax = dirtyAttributes.size();
-
- while (true) {
- Attr domAttribute;
- UiAttributeNode uiAttribute;
-
- int compare;
- if (uiAttributeIndex < uiAttributeIndexMax) {
- if (domAttributeIndex < domAttributeIndexMax) {
- domAttribute = move.get(domAttributeIndex);
- uiAttribute = dirtyAttributes.get(uiAttributeIndex);
-
- String domAttributeName = domAttribute.getLocalName();
- String uiAttributeName = uiAttribute.getDescriptor().getXmlLocalName();
- compare = XmlAttributeSortOrder.compareAttributes(domAttributeName,
- uiAttributeName);
- } else {
- compare = 1;
- uiAttribute = dirtyAttributes.get(uiAttributeIndex);
- domAttribute = null;
- }
- } else if (domAttributeIndex < domAttributeIndexMax) {
- compare = -1;
- domAttribute = move.get(domAttributeIndex);
- uiAttribute = null;
- } else {
- break;
- }
-
- if (compare < 0) {
- if (domAttribute.getNamespaceURI() != null) {
- attributes.setNamedItemNS(domAttribute);
- } else {
- attributes.setNamedItem(domAttribute);
- }
- domAttributeIndex++;
- } else {
- assert compare >= 0;
- if (compare == 0) {
- domAttributeIndex++;
- }
- commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
- uiAttribute.setDirty(false);
- uiAttributeIndex++;
- }
- }
- }
-
- return result;
- }
-
- /**
- * Utility method to internally set the value of a text attribute for the current
- * UiElementNode.
- * <p/>
- * This method is a helper. It silently ignores the errors such as the requested
- * attribute not being present in the element or attribute not being settable.
- * It accepts inherited attributes (such as layout).
- * <p/>
- * This does not commit to the XML model. It does mark the attribute node as dirty.
- * This is up to the caller.
- *
- * @see #commitAttributeToXml(UiAttributeNode, String)
- * @see #commitDirtyAttributesToXml()
- *
- * @param attrXmlName The XML <em>local</em> name of the attribute to modify
- * @param attrNsUri The namespace URI of the attribute.
- * Can be null if the attribute uses the global namespace.
- * @param value The new value for the attribute. If set to null, the attribute is removed.
- * @param override True if the value must be set even if one already exists.
- * @return The {@link UiAttributeNode} that has been modified or null.
- */
- public UiAttributeNode setAttributeValue(
- String attrXmlName,
- String attrNsUri,
- String value,
- boolean override) {
- if (value == null) {
- value = ""; //$NON-NLS-1$ -- this removes an attribute
- }
-
- getEditor().scheduleNodeReformat(this, true);
-
- // Try with all internal attributes
- UiAttributeNode uiAttr = setInternalAttrValue(
- getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
- if (uiAttr != null) {
- return uiAttr;
- }
-
- if (uiAttr == null) {
- // Failed to find the attribute. For non-android attributes that is mostly expected,
- // in which case we just create a new custom one. As a side effect, we'll find the
- // attribute descriptor via getAllUiAttributes().
- addUnknownAttribute(attrXmlName, attrXmlName, attrNsUri);
-
- // We've created the attribute, but not actually set the value on it, so let's do it.
- // Try with the updated internal attributes.
- // Implementation detail: we could just do a setCurrentValue + setDirty on the
- // uiAttr returned by addUnknownAttribute(); however going through setInternalAttrValue
- // means we won't duplicate the logic, at the expense of doing one more lookup.
- uiAttr = setInternalAttrValue(
- getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
- }
-
- return uiAttr;
- }
-
- private UiAttributeNode setInternalAttrValue(
- Collection<UiAttributeNode> attributes,
- String attrXmlName,
- String attrNsUri,
- String value,
- boolean override) {
-
- // For namespace less attributes (like the "layout" attribute of an <include> tag
- // we may be passed "" as the namespace (during an attribute copy), and it
- // should really be null instead.
- if (attrNsUri != null && attrNsUri.length() == 0) {
- attrNsUri = null;
- }
-
- for (UiAttributeNode uiAttr : attributes) {
- AttributeDescriptor uiDesc = uiAttr.getDescriptor();
-
- if (uiDesc.getXmlLocalName().equals(attrXmlName)) {
- // Both NS URI must be either null or equal.
- if ((attrNsUri == null && uiDesc.getNamespaceUri() == null) ||
- (attrNsUri != null && attrNsUri.equals(uiDesc.getNamespaceUri()))) {
-
- // Not all attributes are editable, ignore those which are not.
- if (uiAttr instanceof IUiSettableAttributeNode) {
- String current = uiAttr.getCurrentValue();
- // Only update (and mark as dirty) if the attribute did not have any
- // value or if the value was different.
- if (override || current == null || !current.equals(value)) {
- ((IUiSettableAttributeNode) uiAttr).setCurrentValue(value);
- // mark the attribute as dirty since their internal content
- // as been modified, but not the underlying XML model
- uiAttr.setDirty(true);
- return uiAttr;
- }
- }
-
- // We found the attribute but it's not settable. Since attributes are
- // not duplicated, just abandon here.
- break;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Utility method to retrieve the internal value of an attribute.
- * <p/>
- * Note that this retrieves the *field* value if the attribute has some UI, and
- * not the actual XML value. They may differ if the attribute is dirty.
- *
- * @param attrXmlName The XML name of the attribute to modify
- * @return The current internal value for the attribute or null in case of error.
- */
- public String getAttributeValue(String attrXmlName) {
- HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
-
- for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
- AttributeDescriptor uiDesc = entry.getKey();
- if (uiDesc.getXmlLocalName().equals(attrXmlName)) {
- UiAttributeNode uiAttr = entry.getValue();
- return uiAttr.getCurrentValue();
- }
- }
- return null;
- }
-
- // ------ IPropertySource methods
-
- @Override
- public Object getEditableValue() {
- return null;
- }
-
- /*
- * (non-Javadoc)
- * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors()
- *
- * Returns the property descriptor for this node. Since the descriptors are not linked to the
- * data, the AttributeDescriptor are used directly.
- */
- @Override
- public IPropertyDescriptor[] getPropertyDescriptors() {
- List<IPropertyDescriptor> propDescs = new ArrayList<IPropertyDescriptor>();
-
- // get the standard descriptors
- HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
- Set<AttributeDescriptor> keys = attributeMap.keySet();
-
-
- // we only want the descriptor that do implement the IPropertyDescriptor interface.
- for (AttributeDescriptor key : keys) {
- if (key instanceof IPropertyDescriptor) {
- propDescs.add((IPropertyDescriptor)key);
- }
- }
-
- // now get the descriptor from the unknown attributes
- for (UiAttributeNode unknownNode : mUnknownUiAttributes) {
- if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) {
- propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor());
- }
- }
-
- // TODO cache this maybe, as it's not going to change (except for unknown descriptors)
- return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]);
- }
-
- /*
- * (non-Javadoc)
- * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang.Object)
- *
- * Returns the value of a given property. The id is the result of IPropertyDescriptor.getId(),
- * which return the AttributeDescriptor itself.
- */
- @Override
- public Object getPropertyValue(Object id) {
- HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
-
- UiAttributeNode attribute = attributeMap.get(id);
-
- if (attribute == null) {
- // look for the id in the unknown attributes.
- for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
- if (id == unknownAttr.getDescriptor()) {
- return unknownAttr;
- }
- }
- }
-
- return attribute;
- }
-
- /*
- * (non-Javadoc)
- * @see org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang.Object)
- *
- * Returns whether the property is set. In our case this is if the string is non empty.
- */
- @Override
- public boolean isPropertySet(Object id) {
- HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
-
- UiAttributeNode attribute = attributeMap.get(id);
-
- if (attribute != null) {
- return attribute.getCurrentValue().length() > 0;
- }
-
- // look for the id in the unknown attributes.
- for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
- if (id == unknownAttr.getDescriptor()) {
- return unknownAttr.getCurrentValue().length() > 0;
- }
- }
-
- return false;
- }
-
- /*
- * (non-Javadoc)
- * @see org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java.lang.Object)
- *
- * Reset the property to its default value. For now we simply empty it.
- */
- @Override
- public void resetPropertyValue(Object id) {
- HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
-
- UiAttributeNode attribute = attributeMap.get(id);
- if (attribute != null) {
- // TODO: reset the value of the attribute
-
- return;
- }
-
- // look for the id in the unknown attributes.
- for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
- if (id == unknownAttr.getDescriptor()) {
- // TODO: reset the value of the attribute
-
- return;
- }
- }
- }
-
- /*
- * (non-Javadoc)
- * @see org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java.lang.Object, java.lang.Object)
- *
- * Set the property value. id is the result of IPropertyDescriptor.getId(), which is the
- * AttributeDescriptor itself. Value should be a String.
- */
- @Override
- public void setPropertyValue(Object id, Object value) {
- HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
-
- UiAttributeNode attribute = attributeMap.get(id);
-
- if (attribute == null) {
- // look for the id in the unknown attributes.
- for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
- if (id == unknownAttr.getDescriptor()) {
- attribute = unknownAttr;
- break;
- }
- }
- }
-
- if (attribute != null) {
-
- // get the current value and compare it to the new value
- String oldValue = attribute.getCurrentValue();
- final String newValue = (String)value;
-
- if (oldValue.equals(newValue)) {
- return;
- }
-
- final UiAttributeNode fAttribute = attribute;
- AndroidXmlEditor editor = getEditor();
- editor.wrapEditXmlModel(new Runnable() {
- @Override
- public void run() {
- commitAttributeToXml(fAttribute, newValue);
- }
- });
- }
- }
-
- /**
- * Returns true if this node is an ancestor (parent, grandparent, and so on)
- * of the given node. Note that a node is not considered an ancestor of
- * itself.
- *
- * @param node the node to test
- * @return true if this node is an ancestor of the given node
- */
- public boolean isAncestorOf(UiElementNode node) {
- node = node.getUiParent();
- while (node != null) {
- if (node == this) {
- return true;
- }
- node = node.getUiParent();
- }
- return false;
- }
-
- /**
- * Finds the nearest common parent of the two given nodes (which could be one of the
- * two nodes as well)
- *
- * @param node1 the first node to test
- * @param node2 the second node to test
- * @return the nearest common parent of the two given nodes
- */
- public static UiElementNode getCommonAncestor(UiElementNode node1, UiElementNode node2) {
- while (node2 != null) {
- UiElementNode current = node1;
- while (current != null && current != node2) {
- current = current.getUiParent();
- }
- if (current == node2) {
- return current;
- }
- node2 = node2.getUiParent();
- }
-
- return null;
- }
-
- // ---- Global node create/delete Listeners ----
-
- /** List of listeners to be notified of newly created nodes, or null */
- private static List<NodeCreationListener> sListeners;
-
- /** Notify listeners that a new node has been created */
- private void fireNodeCreated(UiElementNode newChild, int index) {
- // Nothing to do if there aren't any listeners. We don't need to worry about
- // the case where one thread is firing node changes while another is adding a listener
- // (in that case it's still okay for this node firing not to be heard) so perform
- // the check outside of synchronization.
- if (sListeners == null) {
- return;
- }
- synchronized (UiElementNode.class) {
- if (sListeners != null) {
- UiElementNode parent = newChild.getUiParent();
- for (NodeCreationListener listener : sListeners) {
- listener.nodeCreated(parent, newChild, index);
- }
- }
- }
- }
-
- /** Notify listeners that a new node has been deleted */
- private void fireNodeDeleted(UiElementNode oldChild, int index) {
- if (sListeners == null) {
- return;
- }
- synchronized (UiElementNode.class) {
- if (sListeners != null) {
- UiElementNode parent = oldChild.getUiParent();
- for (NodeCreationListener listener : sListeners) {
- listener.nodeDeleted(parent, oldChild, index);
- }
- }
- }
- }
-
- /**
- * Adds a {@link NodeCreationListener} to be notified when new nodes are created
- *
- * @param listener the listener to be notified
- */
- public static void addNodeCreationListener(NodeCreationListener listener) {
- synchronized (UiElementNode.class) {
- if (sListeners == null) {
- sListeners = new ArrayList<NodeCreationListener>(1);
- }
- sListeners.add(listener);
- }
- }
-
- /**
- * Removes a {@link NodeCreationListener} from the set of listeners such that it is
- * no longer notified when nodes are created.
- *
- * @param listener the listener to be removed from the notification list
- */
- public static void removeNodeCreationListener(NodeCreationListener listener) {
- synchronized (UiElementNode.class) {
- sListeners.remove(listener);
- if (sListeners.size() == 0) {
- sListeners = null;
- }
- }
- }
-
- /** Interface implemented by listeners to be notified of newly created nodes */
- public interface NodeCreationListener {
- /**
- * Called when a new child node is created and added to the given parent
- *
- * @param parent the parent of the created node
- * @param child the newly node
- * @param index the index among the siblings of the child <b>after</b>
- * insertion
- */
- void nodeCreated(UiElementNode parent, UiElementNode child, int index);
-
- /**
- * Called when a child node is removed from the given parent
- *
- * @param parent the parent of the removed node
- * @param child the removed node
- * @param previousIndex the index among the siblings of the child
- * <b>before</b> removal
- */
- void nodeDeleted(UiElementNode parent, UiElementNode child, int previousIndex);
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiFlagAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiFlagAttributeNode.java
deleted file mode 100644
index 13fcdb6b2..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiFlagAttributeNode.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.FlagAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-
-import org.eclipse.jface.dialogs.Dialog;
-import org.eclipse.jface.resource.FontDescriptor;
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.ui.dialogs.SelectionStatusDialog;
-import org.eclipse.ui.forms.IManagedForm;
-import org.eclipse.ui.forms.widgets.FormToolkit;
-import org.eclipse.ui.forms.widgets.TableWrapData;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Represents an XML attribute that is defined by a set of flag values,
- * i.e. enum names separated by pipe (|) characters.
- *
- * Note: in Android resources, a "flag" is a list of fixed values where one or
- * more values can be selected using an "or", e.g. "align='left|top'".
- * By contrast, an "enum" is a list of fixed values of which only one can be
- * selected at a given time, e.g. "gravity='right'".
- * <p/>
- * This class handles the "flag" case.
- * The "enum" case is done using {@link UiListAttributeNode}.
- */
-public class UiFlagAttributeNode extends UiTextAttributeNode {
-
- public UiFlagAttributeNode(FlagAttributeDescriptor attributeDescriptor,
- UiElementNode uiParent) {
- super(attributeDescriptor, uiParent);
- }
-
- /* (non-java doc)
- * Creates a label widget and an associated text field.
- * <p/>
- * As most other parts of the android manifest editor, this assumes the
- * parent uses a table layout with 2 columns.
- */
- @Override
- public void createUiControl(Composite parent, IManagedForm managedForm) {
- setManagedForm(managedForm);
- FormToolkit toolkit = managedForm.getToolkit();
- TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
-
- Label label = toolkit.createLabel(parent, desc.getUiName());
- label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
- SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip()));
-
- Composite composite = toolkit.createComposite(parent);
- composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
- GridLayout gl = new GridLayout(2, false);
- gl.marginHeight = gl.marginWidth = 0;
- composite.setLayout(gl);
- // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
- // for the text field below
- toolkit.paintBordersFor(composite);
-
- final Text text = toolkit.createText(composite, getCurrentValue());
- GridData gd = new GridData(GridData.FILL_HORIZONTAL);
- gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK
- text.setLayoutData(gd);
- final Button selectButton = toolkit.createButton(composite, "Select...", SWT.PUSH);
-
- setTextWidget(text);
-
- selectButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- super.widgetSelected(e);
-
- String currentText = getTextWidgetValue();
-
- String result = showDialog(selectButton.getShell(), currentText);
-
- if (result != null) {
- setTextWidgetValue(result);
- }
- }
- });
- }
-
- /**
- * Get the flag names, either from the initial names set in the attribute
- * or by querying the framework resource parser.
- *
- * {@inheritDoc}
- */
- @Override
- public String[] getPossibleValues(String prefix) {
- String attr_name = getDescriptor().getXmlLocalName();
- String element_name = getUiParent().getDescriptor().getXmlName();
-
- String[] values = null;
-
- if (getDescriptor() instanceof FlagAttributeDescriptor &&
- ((FlagAttributeDescriptor) getDescriptor()).getNames() != null) {
- // Get enum values from the descriptor
- values = ((FlagAttributeDescriptor) getDescriptor()).getNames();
- }
-
- if (values == null) {
- // or from the AndroidTargetData
- UiElementNode uiNode = getUiParent();
- AndroidXmlEditor editor = uiNode.getEditor();
- AndroidTargetData data = editor.getTargetData();
- if (data != null) {
- values = data.getAttributeValues(element_name, attr_name);
- }
- }
-
- return values;
- }
-
- /**
- * Shows a dialog letting the user choose a set of enum, and returns a string
- * containing the result.
- */
- public String showDialog(Shell shell, String currentValue) {
- FlagSelectionDialog dlg = new FlagSelectionDialog(
- shell, currentValue.trim().split("\\s*\\|\\s*")); //$NON-NLS-1$
- dlg.open();
- Object[] result = dlg.getResult();
- if (result != null) {
- StringBuilder buf = new StringBuilder();
- for (Object name : result) {
- if (name instanceof String) {
- if (buf.length() > 0) {
- buf.append('|');
- }
- buf.append(name);
- }
- }
-
- return buf.toString();
- }
-
- return null;
-
- }
-
- /**
- * Displays a list of flag names with checkboxes.
- */
- private class FlagSelectionDialog extends SelectionStatusDialog {
-
- private Set<String> mCurrentSet;
- private Table mTable;
-
- public FlagSelectionDialog(Shell parentShell, String[] currentNames) {
- super(parentShell);
-
- mCurrentSet = new HashSet<String>();
- for (String name : currentNames) {
- if (name.length() > 0) {
- mCurrentSet.add(name);
- }
- }
-
- int shellStyle = getShellStyle();
- setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE);
- }
-
- @Override
- protected void computeResult() {
- if (mTable != null) {
- ArrayList<String> results = new ArrayList<String>();
-
- for (TableItem item : mTable.getItems()) {
- if (item.getChecked()) {
- results.add((String)item.getData());
- }
- }
-
- setResult(results);
- }
- }
-
- @Override
- protected Control createDialogArea(Composite parent) {
- Composite composite= new Composite(parent, SWT.NONE);
- composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- composite.setLayout(new GridLayout(1, true));
- composite.setFont(parent.getFont());
-
- Label label = new Label(composite, SWT.NONE);
- label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- label.setText(String.format("Select the flag values for attribute %1$s:",
- ((FlagAttributeDescriptor) getDescriptor()).getUiName()));
-
- mTable = new Table(composite, SWT.CHECK | SWT.BORDER);
- GridData data = new GridData();
- // The 60,18 hints are the ones used by AbstractElementListSelectionDialog
- data.widthHint = convertWidthInCharsToPixels(60);
- data.heightHint = convertHeightInCharsToPixels(18);
- data.grabExcessVerticalSpace = true;
- data.grabExcessHorizontalSpace = true;
- data.horizontalAlignment = GridData.FILL;
- data.verticalAlignment = GridData.FILL;
- mTable.setLayoutData(data);
-
- mTable.setHeaderVisible(false);
- final TableColumn column = new TableColumn(mTable, SWT.NONE);
-
- // List all the expected flag names and check those which are currently used
- String[] names = getPossibleValues(null);
- if (names != null) {
- for (String name : names) {
- TableItem item = new TableItem(mTable, SWT.NONE);
- item.setText(name);
- item.setData(name);
-
- boolean hasName = mCurrentSet.contains(name);
- item.setChecked(hasName);
- if (hasName) {
- mCurrentSet.remove(name);
- }
- }
- }
-
- // If there are unknown flag names currently used, display them at the end if the
- // table already checked.
- if (!mCurrentSet.isEmpty()) {
- FontDescriptor fontDesc = JFaceResources.getDialogFontDescriptor();
- fontDesc = fontDesc.withStyle(SWT.ITALIC);
- Font font = fontDesc.createFont(JFaceResources.getDialogFont().getDevice());
-
- for (String name : mCurrentSet) {
- TableItem item = new TableItem(mTable, SWT.NONE);
- item.setText(String.format("%1$s (unknown flag)", name));
- item.setData(name);
- item.setChecked(true);
- item.setFont(font);
- }
- }
-
- // Add a listener that will resize the column to the full width of the table
- // so that only one column appears in the table even if the dialog is resized.
- ControlAdapter listener = new ControlAdapter() {
- @Override
- public void controlResized(ControlEvent e) {
- Rectangle r = mTable.getClientArea();
- column.setWidth(r.width);
- }
- };
-
- mTable.addControlListener(listener);
- listener.controlResized(null /* event not used */);
-
- // Add a selection listener that will check/uncheck items when they are double-clicked
- mTable.addSelectionListener(new SelectionAdapter() {
- /** Default selection means double-click on "most" platforms */
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- if (e.item instanceof TableItem) {
- TableItem i = (TableItem) e.item;
- i.setChecked(!i.getChecked());
- }
- super.widgetDefaultSelected(e);
- }
- });
-
- Dialog.applyDialogFont(composite);
- setHelpAvailable(false);
-
- return composite;
- }
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java
deleted file mode 100644
index 0fd317c1c..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.SdkConstants;
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ListAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.ui.forms.IManagedForm;
-import org.eclipse.ui.forms.widgets.FormToolkit;
-import org.eclipse.ui.forms.widgets.TableWrapData;
-
-/**
- * Represents an XML attribute which has possible built-in values, and can be modified by
- * an editable Combo box.
- * <p/>
- * See {@link UiTextAttributeNode} for more information.
- */
-public class UiListAttributeNode extends UiAbstractTextAttributeNode {
-
- protected Combo mCombo;
-
- public UiListAttributeNode(ListAttributeDescriptor attributeDescriptor,
- UiElementNode uiParent) {
- super(attributeDescriptor, uiParent);
- }
-
- /* (non-java doc)
- * Creates a label widget and an associated text field.
- * <p/>
- * As most other parts of the android manifest editor, this assumes the
- * parent uses a table layout with 2 columns.
- */
- @Override
- public final void createUiControl(final Composite parent, IManagedForm managedForm) {
- FormToolkit toolkit = managedForm.getToolkit();
- TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
-
- Label label = toolkit.createLabel(parent, desc.getUiName());
- label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
- SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip()));
-
- int style = SWT.DROP_DOWN;
- mCombo = new Combo(parent, style);
- TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE);
- twd.maxWidth = 100;
- mCombo.setLayoutData(twd);
-
- fillCombo();
-
- setTextWidgetValue(getCurrentValue());
-
- mCombo.addModifyListener(new ModifyListener() {
- /**
- * Sent when the text is modified, whether by the user via manual
- * input or programmatic input via setText().
- */
- @Override
- public void modifyText(ModifyEvent e) {
- onComboChange();
- }
- });
-
- mCombo.addSelectionListener(new SelectionAdapter() {
- /** Sent when the text is changed from a list selection. */
- @Override
- public void widgetSelected(SelectionEvent e) {
- onComboChange();
- }
- });
-
- // Remove self-reference when the widget is disposed
- mCombo.addDisposeListener(new DisposeListener() {
- @Override
- public void widgetDisposed(DisposeEvent e) {
- mCombo = null;
- }
- });
- }
-
- protected void fillCombo() {
- String[] values = getPossibleValues(null);
-
- if (values == null) {
- AdtPlugin.log(IStatus.ERROR,
- "FrameworkResourceManager did not provide values yet for %1$s",
- getDescriptor().getXmlLocalName());
- } else {
- for (String value : values) {
- mCombo.add(value);
- }
- }
- }
-
- /**
- * Get the list values, either from the initial values set in the attribute
- * or by querying the framework resource parser.
- *
- * {@inheritDoc}
- */
- @Override
- public String[] getPossibleValues(String prefix) {
- AttributeDescriptor descriptor = getDescriptor();
- UiElementNode uiParent = getUiParent();
-
- String attr_name = descriptor.getXmlLocalName();
- String element_name = uiParent.getDescriptor().getXmlName();
-
- // FrameworkResourceManager expects a specific prefix for the attribute.
- String nsPrefix = "";
- if (SdkConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) {
- nsPrefix = SdkConstants.ANDROID_NS_NAME + ':';
- } else if (SdkConstants.XMLNS_URI.equals(descriptor.getNamespaceUri())) {
- nsPrefix = SdkConstants.XMLNS_PREFIX;
- }
- attr_name = nsPrefix + attr_name;
-
- String[] values = null;
-
- if (descriptor instanceof ListAttributeDescriptor &&
- ((ListAttributeDescriptor) descriptor).getValues() != null) {
- // Get enum values from the descriptor
- values = ((ListAttributeDescriptor) descriptor).getValues();
- }
-
- if (values == null) {
- // or from the AndroidTargetData
- UiElementNode uiNode = getUiParent();
- AndroidXmlEditor editor = uiNode.getEditor();
- AndroidTargetData data = editor.getTargetData();
- if (data != null) {
- // get the great-grand-parent descriptor.
-
- // the parent should always exist.
- UiElementNode grandParentNode = uiParent.getUiParent();
-
- String greatGrandParentNodeName = null;
- if (grandParentNode != null) {
- UiElementNode greatGrandParentNode = grandParentNode.getUiParent();
- if (greatGrandParentNode != null) {
- greatGrandParentNodeName =
- greatGrandParentNode.getDescriptor().getXmlName();
- }
- }
-
- values = data.getAttributeValues(element_name, attr_name, greatGrandParentNodeName);
- }
- }
-
- return values;
- }
-
- @Override
- public String getTextWidgetValue() {
- if (mCombo != null) {
- return mCombo.getText();
- }
-
- return null;
- }
-
- @Override
- public final boolean isValid() {
- return mCombo != null;
- }
-
- @Override
- public void setTextWidgetValue(String value) {
- if (mCombo != null) {
- mCombo.setText(value);
- }
- }
-
- /**
- * Handles Combo change, either from text edit or from selection change.
- * <p/>
- * Simply mark the attribute as dirty if it really changed.
- * The container SectionPart will collect these flag and manage them.
- */
- private void onComboChange() {
- if (!isInInternalTextModification() &&
- !isDirty() &&
- mCombo != null &&
- getCurrentValue() != null &&
- !mCombo.getText().equals(getCurrentValue())) {
- setDirty(true);
- }
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
deleted file mode 100644
index eb51d3f86..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import static com.android.SdkConstants.ANDROID_PKG;
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.api.IAttributeInfo;
-import com.android.ide.common.api.IAttributeInfo.Format;
-import com.android.ide.common.resources.ResourceItem;
-import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog;
-import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
-import com.android.resources.ResourceType;
-
-import org.eclipse.core.resources.IProject;
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.ui.forms.IManagedForm;
-import org.eclipse.ui.forms.widgets.FormToolkit;
-import org.eclipse.ui.forms.widgets.TableWrapData;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Represents an XML attribute for a resource that can be modified using a simple text field or
- * a dialog to choose an existing resource.
- * <p/>
- * It can be configured to represent any kind of resource, by providing the desired
- * {@link ResourceType} in the constructor.
- * <p/>
- * See {@link UiTextAttributeNode} for more information.
- */
-public class UiResourceAttributeNode extends UiTextAttributeNode {
- private ResourceType mType;
-
- /**
- * Creates a new {@linkplain UiResourceAttributeNode}
- *
- * @param type the associated resource type
- * @param attributeDescriptor the attribute descriptor for this attribute
- * @param uiParent the parent ui node, if any
- */
- public UiResourceAttributeNode(ResourceType type,
- AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
- super(attributeDescriptor, uiParent);
-
- mType = type;
- }
-
- /* (non-java doc)
- * Creates a label widget and an associated text field.
- * <p/>
- * As most other parts of the android manifest editor, this assumes the
- * parent uses a table layout with 2 columns.
- */
- @Override
- public void createUiControl(final Composite parent, IManagedForm managedForm) {
- setManagedForm(managedForm);
- FormToolkit toolkit = managedForm.getToolkit();
- TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
-
- Label label = toolkit.createLabel(parent, desc.getUiName());
- label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
- SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip()));
-
- Composite composite = toolkit.createComposite(parent);
- composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
- GridLayout gl = new GridLayout(2, false);
- gl.marginHeight = gl.marginWidth = 0;
- composite.setLayout(gl);
- // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
- // for the text field below
- toolkit.paintBordersFor(composite);
-
- final Text text = toolkit.createText(composite, getCurrentValue());
- GridData gd = new GridData(GridData.FILL_HORIZONTAL);
- gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK
- text.setLayoutData(gd);
- Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
-
- setTextWidget(text);
-
- // TODO Add a validator using onAddModifyListener
-
- browseButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- String result = showDialog(parent.getShell(), text.getText().trim());
- if (result != null) {
- text.setText(result);
- }
- }
- });
- }
-
- /**
- * Shows a dialog letting the user choose a set of enum, and returns a
- * string containing the result.
- *
- * @param shell the parent shell
- * @param currentValue an initial value, if any
- * @return the chosen string, or null
- */
- @Nullable
- public String showDialog(@NonNull Shell shell, @Nullable String currentValue) {
- // we need to get the project of the file being edited.
- UiElementNode uiNode = getUiParent();
- AndroidXmlEditor editor = uiNode.getEditor();
- IProject project = editor.getProject();
- if (project != null) {
- // get the resource repository for this project and the system resources.
- ResourceRepository projectRepository =
- ResourceManager.getInstance().getProjectResources(project);
-
- if (mType != null) {
- // get the Target Data to get the system resources
- AndroidTargetData data = editor.getTargetData();
- ResourceChooser dlg = ResourceChooser.create(project, mType, data, shell)
- .setCurrentResource(currentValue);
- if (dlg.open() == Window.OK) {
- return dlg.getCurrentResource();
- }
- } else {
- ReferenceChooserDialog dlg = new ReferenceChooserDialog(
- project,
- projectRepository,
- shell);
-
- dlg.setCurrentResource(currentValue);
-
- if (dlg.open() == Window.OK) {
- return dlg.getCurrentResource();
- }
- }
- }
-
- return null;
- }
-
- /**
- * Gets all the values one could use to auto-complete a "resource" value in an XML
- * content assist.
- * <p/>
- * Typically the user is editing the value of an attribute in a resource XML, e.g.
- * <pre> "&lt;Button android:test="@string/my_[caret]_string..." </pre>
- * <p/>
- *
- * "prefix" is the value that the user has typed so far (or more exactly whatever is on the
- * left side of the insertion point). In the example above it would be "@style/my_".
- * <p/>
- *
- * To avoid a huge long list of values, the completion works on two levels:
- * <ul>
- * <li> If a resource type as been typed so far (e.g. "@style/"), then limit the values to
- * the possible completions that match this type.
- * <li> If no resource type as been typed so far, then return the various types that could be
- * completed. So if the project has only strings and layouts resources, for example,
- * the returned list will only include "@string/" and "@layout/".
- * </ul>
- *
- * Finally if anywhere in the string we find the special token "android:", we use the
- * current framework system resources rather than the project resources.
- * This works for both "@android:style/foo" and "@style/android:foo" conventions even though
- * the reconstructed name will always be of the former form.
- *
- * Note that "android:" here is a keyword specific to Android resources and should not be
- * mixed with an XML namespace for an XML attribute name.
- */
- @Override
- public String[] getPossibleValues(String prefix) {
- return computeResourceStringMatches(getUiParent().getEditor(), getDescriptor(), prefix);
- }
-
- /**
- * Computes the set of resource string matches for a given resource prefix in a given editor
- *
- * @param editor the editor context
- * @param descriptor the attribute descriptor, if any
- * @param prefix the prefix, if any
- * @return an array of resource string matches
- */
- @Nullable
- public static String[] computeResourceStringMatches(
- @NonNull AndroidXmlEditor editor,
- @Nullable AttributeDescriptor descriptor,
- @Nullable String prefix) {
-
- if (prefix == null || !prefix.regionMatches(1, ANDROID_PKG, 0, ANDROID_PKG.length())) {
- IProject project = editor.getProject();
- if (project != null) {
- // get the resource repository for this project and the system resources.
- ResourceManager resourceManager = ResourceManager.getInstance();
- ResourceRepository repository = resourceManager.getProjectResources(project);
-
- List<IProject> libraries = null;
- ProjectState projectState = Sdk.getProjectState(project);
- if (projectState != null) {
- libraries = projectState.getFullLibraryProjects();
- }
-
- String[] projectMatches = computeResourceStringMatches(descriptor, prefix,
- repository, false);
-
- if (libraries == null || libraries.isEmpty()) {
- return projectMatches;
- }
-
- // Also compute matches for each of the libraries, and combine them
- Set<String> matches = new HashSet<String>(200);
- for (String s : projectMatches) {
- matches.add(s);
- }
-
- for (IProject library : libraries) {
- repository = resourceManager.getProjectResources(library);
- projectMatches = computeResourceStringMatches(descriptor, prefix,
- repository, false);
- for (String s : projectMatches) {
- matches.add(s);
- }
- }
-
- String[] sorted = matches.toArray(new String[matches.size()]);
- Arrays.sort(sorted);
- return sorted;
- }
- } else {
- // If there's a prefix with "android:" in it, use the system resources
- // Non-public framework resources are filtered out later.
- AndroidTargetData data = editor.getTargetData();
- if (data != null) {
- ResourceRepository repository = data.getFrameworkResources();
- return computeResourceStringMatches(descriptor, prefix, repository, true);
- }
- }
-
- return null;
- }
-
- /**
- * Computes the set of resource string matches for a given prefix and a
- * given resource repository
- *
- * @param attributeDescriptor the attribute descriptor, if any
- * @param prefix the prefix, if any
- * @param repository the repository to seaerch in
- * @param isSystem if true, the repository contains framework repository,
- * otherwise it contains project repositories
- * @return an array of resource string matches
- */
- @NonNull
- public static String[] computeResourceStringMatches(
- @Nullable AttributeDescriptor attributeDescriptor,
- @Nullable String prefix,
- @NonNull ResourceRepository repository,
- boolean isSystem) {
- // Get list of potential resource types, either specific to this project
- // or the generic list.
- Collection<ResourceType> resTypes = (repository != null) ?
- repository.getAvailableResourceTypes() :
- EnumSet.allOf(ResourceType.class);
-
- // Get the type name from the prefix, if any. It's any word before the / if there's one
- String typeName = null;
- if (prefix != null) {
- Matcher m = Pattern.compile(".*?([a-z]+)/.*").matcher(prefix); //$NON-NLS-1$
- if (m.matches()) {
- typeName = m.group(1);
- }
- }
-
- // Now collect results
- List<String> results = new ArrayList<String>();
-
- if (typeName == null) {
- // This prefix does not have a / in it, so the resource string is either empty
- // or does not have the resource type in it. Simply offer the list of potential
- // resource types.
- if (prefix != null && prefix.startsWith(PREFIX_THEME_REF)) {
- results.add(ANDROID_THEME_PREFIX + ResourceType.ATTR.getName() + '/');
- if (resTypes.contains(ResourceType.ATTR)
- || resTypes.contains(ResourceType.STYLE)) {
- results.add(PREFIX_THEME_REF + ResourceType.ATTR.getName() + '/');
- if (prefix != null && prefix.startsWith(ANDROID_THEME_PREFIX)) {
- // including attr isn't required
- for (ResourceItem item : repository.getResourceItemsOfType(
- ResourceType.ATTR)) {
- results.add(ANDROID_THEME_PREFIX + item.getName());
- }
- }
- }
- return results.toArray(new String[results.size()]);
- }
-
- for (ResourceType resType : resTypes) {
- if (isSystem) {
- results.add(ANDROID_PREFIX + resType.getName() + '/');
- } else {
- results.add('@' + resType.getName() + '/');
- }
- if (resType == ResourceType.ID) {
- // Also offer the + version to create an id from scratch
- results.add("@+" + resType.getName() + '/'); //$NON-NLS-1$
- }
- }
-
- // Also add in @android: prefix to completion such that if user has typed
- // "@an" we offer to complete it.
- if (prefix == null ||
- ANDROID_PKG.regionMatches(0, prefix, 1, prefix.length() - 1)) {
- results.add(ANDROID_PREFIX);
- }
- } else if (repository != null) {
- // We have a style name and a repository. Find all resources that match this
- // type and recreate suggestions out of them.
-
- String initial = prefix != null && prefix.startsWith(PREFIX_THEME_REF)
- ? PREFIX_THEME_REF : PREFIX_RESOURCE_REF;
- ResourceType resType = ResourceType.getEnum(typeName);
- if (resType != null) {
- StringBuilder sb = new StringBuilder();
- sb.append(initial);
- if (prefix != null && prefix.indexOf('+') >= 0) {
- sb.append('+');
- }
-
- if (isSystem) {
- sb.append(ANDROID_PKG).append(':');
- }
-
- sb.append(typeName).append('/');
- String base = sb.toString();
-
- for (ResourceItem item : repository.getResourceItemsOfType(resType)) {
- results.add(base + item.getName());
- }
-
- if (!isSystem && resType == ResourceType.ATTR) {
- for (ResourceItem item : repository.getResourceItemsOfType(
- ResourceType.STYLE)) {
- results.add(base + item.getName());
- }
- }
- }
- }
-
- if (attributeDescriptor != null) {
- sortAttributeChoices(attributeDescriptor, results);
- } else {
- Collections.sort(results);
- }
-
- return results.toArray(new String[results.size()]);
- }
-
- /**
- * Attempts to sort the attribute values to bubble up the most likely choices to
- * the top.
- * <p>
- * For example, if you are editing a style attribute, it's likely that among the
- * resource values you would rather see @style or @android than @string.
- * @param descriptor the descriptor that the resource values are being completed for,
- * used to prioritize some of the resource types
- * @param choices the set of string resource values
- */
- public static void sortAttributeChoices(AttributeDescriptor descriptor,
- List<String> choices) {
- final IAttributeInfo attributeInfo = descriptor.getAttributeInfo();
- Collections.sort(choices, new Comparator<String>() {
- @Override
- public int compare(String s1, String s2) {
- int compare = score(attributeInfo, s1) - score(attributeInfo, s2);
- if (compare == 0) {
- // Sort alphabetically as a fallback
- compare = s1.compareToIgnoreCase(s2);
- }
- return compare;
- }
- });
- }
-
- /** Compute a suitable sorting score for the given */
- private static final int score(IAttributeInfo attributeInfo, String value) {
- if (value.equals(ANDROID_PREFIX)) {
- return -1;
- }
-
- for (Format format : attributeInfo.getFormats()) {
- String type = null;
- switch (format) {
- case BOOLEAN:
- type = "bool"; //$NON-NLS-1$
- break;
- case COLOR:
- type = "color"; //$NON-NLS-1$
- break;
- case DIMENSION:
- type = "dimen"; //$NON-NLS-1$
- break;
- case INTEGER:
- type = "integer"; //$NON-NLS-1$
- break;
- case STRING:
- type = "string"; //$NON-NLS-1$
- break;
- // default: REFERENCE, FLAG, ENUM, etc - don't have type info about individual
- // elements to help make a decision
- }
-
- if (type != null) {
- if (value.startsWith(PREFIX_RESOURCE_REF)) {
- if (value.startsWith(PREFIX_RESOURCE_REF + type + '/')) {
- return -2;
- }
-
- if (value.startsWith(ANDROID_PREFIX + type + '/')) {
- return -2;
- }
- }
- if (value.startsWith(PREFIX_THEME_REF)) {
- if (value.startsWith(PREFIX_THEME_REF + type + '/')) {
- return -2;
- }
-
- if (value.startsWith(ANDROID_THEME_PREFIX + type + '/')) {
- return -2;
- }
- }
- }
- }
-
- // Handle a few more cases not covered by the Format metadata check
- String type = null;
-
- String attribute = attributeInfo.getName();
- if (attribute.equals(ATTR_ID)) {
- type = "id"; //$NON-NLS-1$
- } else if (attribute.equals(ATTR_STYLE)) {
- type = "style"; //$NON-NLS-1$
- } else if (attribute.equals(ATTR_LAYOUT)) {
- type = "layout"; //$NON-NLS-1$
- } else if (attribute.equals("drawable")) { //$NON-NLS-1$
- type = "drawable"; //$NON-NLS-1$
- } else if (attribute.equals("entries")) { //$NON-NLS-1$
- // Spinner
- type = "array"; //$NON-NLS-1$
- }
-
- if (type != null) {
- if (value.startsWith(PREFIX_RESOURCE_REF)) {
- if (value.startsWith(PREFIX_RESOURCE_REF + type + '/')) {
- return -2;
- }
-
- if (value.startsWith(ANDROID_PREFIX + type + '/')) {
- return -2;
- }
- }
- if (value.startsWith(PREFIX_THEME_REF)) {
- if (value.startsWith(PREFIX_THEME_REF + type + '/')) {
- return -2;
- }
-
- if (value.startsWith(ANDROID_THEME_PREFIX + type + '/')) {
- return -2;
- }
- }
- }
-
- return 0;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiSeparatorAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiSeparatorAttributeNode.java
deleted file mode 100644
index 3d2006299..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiSeparatorAttributeNode.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.ui.forms.IManagedForm;
-import org.eclipse.ui.forms.widgets.FormToolkit;
-import org.eclipse.ui.forms.widgets.TableWrapData;
-import org.eclipse.ui.forms.widgets.TableWrapLayout;
-import org.w3c.dom.Node;
-
-/**
- * {@link UiSeparatorAttributeNode} does not represent any real attribute.
- * <p/>
- * It is used to separate groups of attributes visually.
- */
-public class UiSeparatorAttributeNode extends UiAttributeNode {
-
- /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} */
- public UiSeparatorAttributeNode(SeparatorAttributeDescriptor attrDesc,
- UiElementNode uiParent) {
- super(attrDesc, uiParent);
- }
-
- /** Returns the current value of the node. */
- @Override
- public String getCurrentValue() {
- // There is no value here.
- return null;
- }
-
- /**
- * Sets whether the attribute is dirty and also notifies the editor some part's dirty
- * flag as changed.
- * <p/>
- * Subclasses should set the to true as a result of user interaction with the widgets in
- * the section and then should set to false when the commit() method completed.
- */
- @Override
- public void setDirty(boolean isDirty) {
- // This is never dirty.
- }
-
- /**
- * Called once by the parent user interface to creates the necessary
- * user interface to edit this attribute.
- * <p/>
- * This method can be called more than once in the life cycle of an UI node,
- * typically when the UI is part of a master-detail tree, as pages are swapped.
- *
- * @param parent The composite where to create the user interface.
- * @param managedForm The managed form owning this part.
- */
- @Override
- public void createUiControl(Composite parent, IManagedForm managedForm) {
- FormToolkit toolkit = managedForm.getToolkit();
- Composite row = toolkit.createComposite(parent);
-
- TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB);
- if (parent.getLayout() instanceof TableWrapLayout) {
- twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns;
- }
- row.setLayoutData(twd);
- row.setLayout(new GridLayout(3, false /* equal width */));
-
- Label sep = toolkit.createSeparator(row, SWT.HORIZONTAL);
- GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false);
- gd.widthHint = 16;
- sep.setLayoutData(gd);
-
- Label label = toolkit.createLabel(row, getDescriptor().getXmlLocalName());
- label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
-
- sep = toolkit.createSeparator(row, SWT.HORIZONTAL);
- sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
- }
-
- /**
- * No completion values for this UI attribute.
- *
- * {@inheritDoc}
- */
- @Override
- public String[] getPossibleValues(String prefix) {
- return null;
- }
-
- /**
- * Called when the XML is being loaded or has changed to
- * update the value held by this user interface attribute node.
- * <p/>
- * The XML Node <em>may</em> be null, which denotes that the attribute is not
- * specified in the XML model. In general, this means the "default" value of the
- * attribute should be used.
- * <p/>
- * The caller doesn't really know if attributes have changed,
- * so it will call this to refresh the attribute anyway. It's up to the
- * UI implementation to minimize refreshes.
- *
- * @param xml_attribute_node
- */
- @Override
- public void updateValue(Node xml_attribute_node) {
- // No value to update.
- }
-
- /**
- * Called by the user interface when the editor is saved or its state changed
- * and the modified attributes must be committed (i.e. written) to the XML model.
- * <p/>
- * Important behaviors:
- * <ul>
- * <li>The caller *must* have called IStructuredModel.aboutToChangeModel before.
- * The implemented methods must assume it is safe to modify the XML model.
- * <li>On success, the implementation *must* call setDirty(false).
- * <li>On failure, the implementation can fail with an exception, which
- * is trapped and logged by the caller, or do nothing, whichever is more
- * appropriate.
- * </ul>
- */
- @Override
- public void commit() {
- // No value to commit.
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextAttributeNode.java
deleted file mode 100644
index 504ac3122..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextAttributeNode.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
-
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.ui.forms.IManagedForm;
-import org.eclipse.ui.forms.widgets.TableWrapData;
-
-/**
- * Represents an XML attribute in that can be modified using a simple text field
- * in the XML editor's user interface.
- * <p/>
- * The XML attribute has no default value. When unset, the text field is blank.
- * When updating the XML, if the field is empty, the attribute will be removed
- * from the XML element.
- * <p/>
- * See {@link UiAttributeNode} for more information.
- */
-public class UiTextAttributeNode extends UiAbstractTextAttributeNode {
-
- /** Text field */
- private Text mText;
- /** The managed form, set only once createUiControl has been called. */
- private IManagedForm mManagedForm;
-
- public UiTextAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
- super(attributeDescriptor, uiParent);
- }
-
- /* (non-java doc)
- * Creates a label widget and an associated text field.
- * <p/>
- * As most other parts of the android manifest editor, this assumes the
- * parent uses a table layout with 2 columns.
- */
- @Override
- public void createUiControl(Composite parent, IManagedForm managedForm) {
- setManagedForm(managedForm);
- TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
- Text text = SectionHelper.createLabelAndText(parent, managedForm.getToolkit(),
- desc.getUiName(), getCurrentValue(),
- DescriptorsUtils.formatTooltip(desc.getTooltip()));
-
- setTextWidget(text);
- }
-
- /**
- * No completion values for this UI attribute.
- *
- * {@inheritDoc}
- */
- @Override
- public String[] getPossibleValues(String prefix) {
- return null;
- }
-
- /**
- * Sets the internal managed form.
- * This is usually set by createUiControl.
- */
- protected void setManagedForm(IManagedForm managedForm) {
- mManagedForm = managedForm;
- }
-
- /**
- * @return The managed form, set only once createUiControl has been called.
- */
- protected IManagedForm getManagedForm() {
- return mManagedForm;
- }
-
- /* (non-java doc)
- * Returns if the attribute node is valid, and its UI has been created.
- */
- @Override
- public boolean isValid() {
- return mText != null;
- }
-
- @Override
- public String getTextWidgetValue() {
- if (mText != null) {
- return mText.getText();
- }
-
- return null;
- }
-
- @Override
- public void setTextWidgetValue(String value) {
- if (mText != null) {
- mText.setText(value);
- }
- }
-
- /**
- * Sets the Text widget object, and prepares it to handle modification and synchronization
- * with the XML node.
- * @param textWidget
- */
- protected final void setTextWidget(Text textWidget) {
- mText = textWidget;
-
- if (textWidget != null) {
- // Sets the with hint for the text field. Derived classes can always override it.
- // This helps the grid layout to resize correctly on smaller screen sizes.
- Object data = textWidget.getLayoutData();
- if (data == null) {
- } else if (data instanceof GridData) {
- ((GridData)data).widthHint = AndroidXmlEditor.TEXT_WIDTH_HINT;
- } else if (data instanceof TableWrapData) {
- ((TableWrapData)data).maxWidth = 100;
- }
-
- mText.addModifyListener(new ModifyListener() {
- /**
- * Sent when the text is modified, whether by the user via manual
- * input or programmatic input via setText().
- * <p/>
- * Simply mark the attribute as dirty if it really changed.
- * The container SectionPart will collect these flag and manage them.
- */
- @Override
- public void modifyText(ModifyEvent e) {
- if (!isInInternalTextModification() &&
- !isDirty() &&
- mText != null &&
- getCurrentValue() != null &&
- !mText.getText().equals(getCurrentValue())) {
- setDirty(true);
- }
- }
- });
-
- // Remove self-reference when the widget is disposed
- mText.addDisposeListener(new DisposeListener() {
- @Override
- public void widgetDisposed(DisposeEvent e) {
- mText = null;
- }
- });
- }
-
- onAddValidators(mText);
- }
-
- /**
- * Called after the text widget as been created.
- * <p/>
- * Derived classes typically want to:
- * <li> Create a new {@link ModifyListener} and attach it to the given {@link Text} widget.
- * <li> In the modify listener, call getManagedForm().getMessageManager().addMessage()
- * and getManagedForm().getMessageManager().removeMessage() as necessary.
- * <li> Call removeMessage in a new text.addDisposeListener.
- * <li> Call the validator once to setup the initial messages as needed.
- * <p/>
- * The base implementation does nothing.
- *
- * @param text The {@link Text} widget to validate.
- */
- protected void onAddValidators(Text text) {
- }
-
- /**
- * Returns the text widget.
- */
- protected final Text getTextWidget() {
- return mText;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextValueNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextValueNode.java
deleted file mode 100644
index 33fa9fc99..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiTextValueNode.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.uimodel;
-
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextValueDescriptor;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
-
-/**
- * Represents an XML element value in that can be modified using a simple text field
- * in the XML editor's user interface.
- */
-public class UiTextValueNode extends UiTextAttributeNode {
-
- public UiTextValueNode(TextValueDescriptor attributeDescriptor, UiElementNode uiParent) {
- super(attributeDescriptor, uiParent);
- }
-
- /**
- * Updates the current text field's value when the XML has changed.
- * <p/>
- * The caller doesn't really know if value of the element has changed,
- * so it will call this to refresh the value anyway. The value
- * is only set if it has changed.
- * <p/>
- * This also resets the "dirty" flag.
- */
- @Override
- public void updateValue(Node xml_attribute_node) {
- setCurrentValue(DEFAULT_VALUE);
-
- // The argument xml_attribute_node is not used here. It should always be
- // null since this is not an attribute. What we want is the "text value" of
- // the parent element, which is actually the first text node of the element.
-
- UiElementNode parent = getUiParent();
- if (parent != null) {
- Node xml_node = parent.getXmlNode();
- if (xml_node != null) {
- for (Node xml_child = xml_node.getFirstChild();
- xml_child != null;
- xml_child = xml_child.getNextSibling()) {
- if (xml_child.getNodeType() == Node.TEXT_NODE) {
- setCurrentValue(xml_child.getNodeValue());
- break;
- }
- }
- }
- }
-
- if (isValid() && !getTextWidgetValue().equals(getCurrentValue())) {
- try {
- setInInternalTextModification(true);
- setTextWidgetValue(getCurrentValue());
- setDirty(false);
- } finally {
- setInInternalTextModification(false);
- }
- }
- }
-
- /* (non-java doc)
- * Called by the user interface when the editor is saved or its state changed
- * and the modified "attributes" must be committed (i.e. written) to the XML model.
- */
- @Override
- public void commit() {
- UiElementNode parent = getUiParent();
- if (parent != null && isValid() && isDirty()) {
- // Get (or create) the underlying XML element node that contains the value.
- Node element = parent.prepareCommit();
- if (element != null) {
- String value = getTextWidgetValue();
-
- // Try to find an existing text child to update.
- boolean updated = false;
-
- for (Node xml_child = element.getFirstChild();
- xml_child != null;
- xml_child = xml_child.getNextSibling()) {
- if (xml_child.getNodeType() == Node.TEXT_NODE) {
- xml_child.setNodeValue(value);
- updated = true;
- break;
- }
- }
-
- // If we didn't find a text child to update, we need to create one.
- if (!updated) {
- Document doc = element.getOwnerDocument();
- if (doc != null) {
- Text text = doc.createTextNode(value);
- element.appendChild(text);
- }
- }
-
- setCurrentValue(value);
- }
- }
- setDirty(false);
- }
-}