diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel')
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> "<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); - } -} |