diff options
author | Raphael Moll <ralf@android.com> | 2010-11-22 18:46:40 -0800 |
---|---|---|
committer | Raphael Moll <ralf@android.com> | 2010-11-23 14:57:50 -0800 |
commit | 3dfd8112f7137821ef9191bfdd82a7f30ae52dc6 (patch) | |
tree | ddefddbd003ececbe91675d7b4da658f7e8e371b /eclipse/plugins/com.android.ide.eclipse.adt/src/com | |
parent | 33620a2caa9a8d757eb5aa6b197577408a197410 (diff) | |
download | sdk-3dfd8112f7137821ef9191bfdd82a7f30ae52dc6.zip sdk-3dfd8112f7137821ef9191bfdd82a7f30ae52dc6.tar.gz sdk-3dfd8112f7137821ef9191bfdd82a7f30ae52dc6.tar.bz2 |
ADT: ensure <application> is last in manifest.
ADT descriptors have no notion of XML ordering.
We modify the concept of "mandatory" descriptor to
have normal mandatory descriptors versus "mandatory last" ones.
("mandatory" elements are virtual reserved slots that are
always present in the UiModel even if they have no real
counterpart in the XML model. These elements ensure they can
be manipulated in the UI even before their XML is created.)
Then we try our best to ensure the "last" ones remain at the
end of the UiNode model. There are 2 cases:
- in UiElementNode, when constructing the UINode model when
parsing an existing XML model, we try to reorder the nodes
at the end if possible.
- in UiActions, when adding a new element to the UiModel, we
try to place it before the first "mandatory last" element.
SDK Bug 3197310
Change-Id: I6a7d9502a95ebe92ff82e07f3f3249a0d25c2154
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com')
10 files changed, 228 insertions, 127 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java index 1509d94..6953278 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java @@ -21,7 +21,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; /** * {@link DocumentDescriptor} describes the properties expected for an XML document node. - * + * * Compared to ElementDescriptor, {@link DocumentDescriptor} does not have XML name nor UI name, * tooltip, SDK url and attributes list. * <p/> @@ -39,12 +39,12 @@ public class DocumentDescriptor extends ElementDescriptor { * <p/> * The XML name is never shown in the UI directly. It is however used when an icon * needs to be found for the node. - * + * * @param xml_name The XML element node name. Case sensitive. * @param children The list of allowed children. Can be null or empty. */ public DocumentDescriptor(String xml_name, ElementDescriptor[] children) { - super(xml_name, children, true /* mandatory */); + super(xml_name, children, Mandatory.MANDATORY); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java index 9c1cacc..b17f5df 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java @@ -40,9 +40,9 @@ import java.util.Set; */ public class ElementDescriptor implements Comparable<ElementDescriptor> { /** The XML element node name. Case sensitive. */ - private String mXmlName; + private final String mXmlName; /** The XML element name for the user interface, typically capitalized. */ - private String mUiName; + private final String mUiName; /** The list of allowed attributes. */ private AttributeDescriptor[] mAttributes; /** The list of allowed children */ @@ -52,7 +52,13 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { /** An optional SKD URL. Can be empty. */ private String mSdkUrl; /** Whether this UI node must always exist (even for empty models). */ - private boolean mMandatory; + private final Mandatory mMandatory; + + public enum Mandatory { + NOT_MANDATORY, + MANDATORY, + MANDATORY_LAST + } /** * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, @@ -72,7 +78,7 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, AttributeDescriptor[] attributes, ElementDescriptor[] children, - boolean mandatory) { + Mandatory mandatory) { mMandatory = mandatory; mXmlName = xml_name; mUiName = ui_name; @@ -83,6 +89,34 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { } /** + * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, + * tooltip, SDK url, attributes list, children list and mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children, + boolean mandatory) { + mMandatory = mandatory ? Mandatory.MANDATORY : Mandatory.NOT_MANDATORY; + mXmlName = xml_name; + mUiName = ui_name; + mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; + mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null; + setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{}); + mChildren = children != null ? children : new ElementDescriptor[]{}; + } + + /** * Constructs a new {@link ElementDescriptor} based on its XML name and children list. * The UI name is build by capitalizing the XML name. * The UI nodes will be non-mandatory. @@ -94,7 +128,7 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { * UI node MUST have an XML node attached and it will cease to exist when the XML node * ceases to exist. */ - public ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory) { + public ElementDescriptor(String xml_name, ElementDescriptor[] children, Mandatory mandatory) { this(xml_name, prettyName(xml_name), null, null, null, children, mandatory); } @@ -122,10 +156,21 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { } /** Returns whether this node must always exist (even for empty models) */ - public boolean isMandatory() { + public Mandatory getMandatory() { return mMandatory; } + @Override + public String toString() { + return String.format("%s [%s, attr %d, children %d%s]", //$NON-NLS-1$ + this.getClass().getSimpleName(), + mXmlName, + mAttributes != null ? mAttributes.length : 0, + mChildren != null ? mChildren.length : 0, + mMandatory != Mandatory.NOT_MANDATORY ? ", " + mMandatory.toString() : "" //$NON-NLS-1$ //$NON-NLS-2$ + ); + } + /** * Returns the XML element node local name (case sensitive) */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java index be81f54..ca07b7a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java @@ -84,48 +84,6 @@ public final class ViewElementDescriptor extends ElementDescriptor { } /** - * Constructs a new {@link ElementDescriptor} based on its XML name, the canonical - * name of the class it represents, and its children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param fullClassName The fully qualified class name the {@link ViewElementDescriptor} is - * representing. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). A mandatory - * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory - * UI node MUST have an XML node attached and it will cease to exist when the XML node - * ceases to exist. - * - * @deprecated Never used. We should clean it up someday. - */ - public ViewElementDescriptor(String xml_name, String fullClassName, - ElementDescriptor[] children, - boolean mandatory) { - super(xml_name, children, mandatory); - mFullClassName = fullClassName; - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name and children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param fullClassName The fully qualified class name the {@link ViewElementDescriptor} is - * representing. - * @param children The list of allowed children. Can be null or empty. - * - * @deprecated Never used. We should clean it up someday. - */ - public ViewElementDescriptor(String xml_name, String fullClassName, - ElementDescriptor[] children) { - super(xml_name, children); - mFullClassName = fullClassName; - } - - /** * Constructs a new {@link ElementDescriptor} based on its XML name and on the canonical * name of the class it represents. * The UI name is build by capitalizing the XML name. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java index 6e0936b..fb742a7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java @@ -197,6 +197,7 @@ public final class ManifestEditor extends AndroidXmlEditor { return null; } + @SuppressWarnings("restriction") private void onDescriptorsChanged() { IStructuredModel model = getModelForRead(); if (model != null) { @@ -350,29 +351,30 @@ public final class ManifestEditor extends AndroidXmlEditor { mUiManifestNode = manifestElement.createUiNode(); mUiManifestNode.setEditor(this); - // Similarly, always create the /manifest/application and /manifest/uses-sdk nodes - ElementDescriptor appElement = manifestDescriptor.getApplicationElement(); + // Similarly, always create the /manifest/uses-sdk followed by /manifest/application + // (order of the elements now matters) + ElementDescriptor element = manifestDescriptor.getUsesSdkElement(); boolean present = false; for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { - if (ui_node.getDescriptor() == appElement) { + if (ui_node.getDescriptor() == element) { present = true; break; } } if (!present) { - mUiManifestNode.appendNewUiChild(appElement); + mUiManifestNode.appendNewUiChild(element); } - appElement = manifestDescriptor.getUsesSdkElement(); + element = manifestDescriptor.getApplicationElement(); present = false; for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { - if (ui_node.getDescriptor() == appElement) { + if (ui_node.getDescriptor() == element) { present = true; break; } } if (!present) { - mUiManifestNode.appendNewUiChild(appElement); + mUiManifestNode.appendNewUiChild(element); } onDescriptorsChanged(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java index 7c69e0b..7bc3692 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java @@ -30,6 +30,7 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.ITextAttributeCr import com.android.ide.eclipse.adt.internal.editors.descriptors.ListAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory; import com.android.sdklib.SdkConstants; import org.eclipse.core.runtime.IStatus; @@ -86,12 +87,12 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { private final TextAttributeDescriptor PACKAGE_ATTR_DESC; public AndroidManifestDescriptors() { - APPLICATION_ELEMENT = createElement("application", null, true); //$NON-NLS-1$ + no child & mandatory + APPLICATION_ELEMENT = createElement("application", null, Mandatory.MANDATORY_LAST); //$NON-NLS-1$ + no child & mandatory INTRUMENTATION_ELEMENT = createElement("instrumentation"); //$NON-NLS-1$ PERMISSION_ELEMENT = createElement("permission"); //$NON-NLS-1$ USES_PERMISSION_ELEMENT = createElement("uses-permission"); //$NON-NLS-1$ - USES_SDK_ELEMENT = createElement("uses-sdk", null, true); //$NON-NLS-1$ + no child & mandatory + USES_SDK_ELEMENT = createElement("uses-sdk", null, Mandatory.MANDATORY); //$NON-NLS-1$ + no child & mandatory PERMISSION_GROUP_ELEMENT = createElement("permission-group"); //$NON-NLS-1$ PERMISSION_TREE_ELEMENT = createElement("permission-tree"); //$NON-NLS-1$ @@ -107,7 +108,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { PERMISSION_TREE_ELEMENT, USES_SDK_ELEMENT, }, - true /* mandatory */); + Mandatory.MANDATORY); // The "package" attribute is treated differently as it doesn't have the standard // Android XML namespace. @@ -302,7 +303,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { private ElementDescriptor createElement( String xmlName, ElementDescriptor[] childrenElements, - boolean mandatory) { + Mandatory mandatory) { // Creates an element with no attribute overrides. String styleName = guessStyleName(xmlName); String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName; @@ -321,7 +322,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { */ private ElementDescriptor createElement(String xmlName) { // Creates an element with no child and not mandatory - return createElement(xmlName, null, false); + return createElement(xmlName, null, Mandatory.NOT_MANDATORY); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java index 7c02850..deb815e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java @@ -25,14 +25,35 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; * {@link ManifestElementDescriptor} describes an XML element node, with its * element name, its possible attributes, its possible child elements but also * its display name and tooltip. - * + * * This {@link ElementDescriptor} is specialized to create {@link UiManifestElementNode} UI nodes. */ public class ManifestElementDescriptor extends ElementDescriptor { /** * Constructs a new {@link ManifestElementDescriptor}. - * + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). + */ + public ManifestElementDescriptor(String xml_name, + String ui_name, + String tooltip, + String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children, + Mandatory mandatory) { + super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); + } + + /** + * Constructs a new {@link ManifestElementDescriptor}. + * * @param xml_name The XML element node name. Case sensitive. * @param ui_name The XML element name for the user interface, typically capitalized. * @param tooltip An optional tooltip. Can be null or empty. @@ -41,7 +62,10 @@ public class ManifestElementDescriptor extends ElementDescriptor { * @param children The list of allowed children. Can be null or empty. * @param mandatory Whether this node must always exist (even for empty models). */ - public ManifestElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + public ManifestElementDescriptor(String xml_name, + String ui_name, + String tooltip, + String sdk_url, AttributeDescriptor[] attributes, ElementDescriptor[] children, boolean mandatory) { @@ -50,7 +74,7 @@ public class ManifestElementDescriptor extends ElementDescriptor { /** * Constructs a new {@link ManifestElementDescriptor}. - * + * * @param xml_name The XML element node name. Case sensitive. * @param ui_name The XML element name for the user interface, typically capitalized. * @param tooltip An optional tooltip. Can be null or empty. @@ -58,7 +82,10 @@ public class ManifestElementDescriptor extends ElementDescriptor { * @param attributes The list of allowed attributes. Can be null or empty. * @param children The list of allowed children. Can be null or empty. */ - public ManifestElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + public ManifestElementDescriptor(String xml_name, + String ui_name, + String tooltip, + String sdk_url, AttributeDescriptor[] attributes, ElementDescriptor[] children) { super(xml_name, ui_name, tooltip, sdk_url, attributes, children, false); @@ -68,7 +95,7 @@ public class ManifestElementDescriptor extends ElementDescriptor { * This is a shortcut for * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, children). * This constructor is mostly used for unit tests. - * + * * @param xml_name The XML element node name. Case sensitive. */ public ManifestElementDescriptor(String xml_name, ElementDescriptor[] children) { @@ -79,7 +106,7 @@ public class ManifestElementDescriptor extends ElementDescriptor { * This is a shortcut for * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, null). * This constructor is mostly used for unit tests. - * + * * @param xml_name The XML element node name. Case sensitive. */ public ManifestElementDescriptor(String xml_name) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java index 24b8014..64af2ae 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java @@ -46,7 +46,7 @@ public final class OverviewPage extends FormPage { /** Page id used for switching tabs programmatically */ final static String PAGE_ID = "overview_page"; //$NON-NLS-1$ - + /** Container editor */ ManifestEditor mEditor; /** Overview part (attributes for manifest) */ @@ -55,7 +55,7 @@ public final class OverviewPage extends FormPage { private OverviewLinksPart mOverviewLinkPart; private UiTreeBlock mTreeBlock; - + public OverviewPage(ManifestEditor editor) { super(editor, PAGE_ID, "Manifest"); // tab's label, user visible, keep it short mEditor = editor; @@ -63,7 +63,7 @@ public final class OverviewPage extends FormPage { /** * Creates the content in the form hosted in this page. - * + * * @param managedForm the form hosted in this page. */ @Override @@ -72,23 +72,23 @@ public final class OverviewPage extends FormPage { ScrolledForm form = managedForm.getForm(); form.setText("Android Manifest"); form.setImage(AdtPlugin.getAndroidLogo()); - + Composite body = form.getBody(); FormToolkit toolkit = managedForm.getToolkit(); - + // Usually we would set a ColumnLayout on body here. However the presence of the // UiTreeBlock forces a GridLayout with one column so we comply with it. mOverviewPart = new OverviewInfoPart(body, toolkit, mEditor); mOverviewPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); managedForm.addPart(mOverviewPart); - + newManifestExtrasPart(managedForm); - + OverviewExportPart exportPart = new OverviewExportPart(this, body, toolkit, mEditor); exportPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); managedForm.addPart(exportPart); - + mOverviewLinkPart = new OverviewLinksPart(body, toolkit, mEditor); mOverviewLinkPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); managedForm.addPart(mOverviewLinkPart); @@ -105,13 +105,13 @@ public final class OverviewPage extends FormPage { } /** - * Changes and refreshes the Application UI node handle by the sub parts. + * Changes and refreshes the Application UI node handled by the sub parts. */ public void refreshUiApplicationNode() { if (mOverviewPart != null) { mOverviewPart.onSdkChanged(); } - + if (mOverviewLinkPart != null) { mOverviewLinkPart.onSdkChanged(); } @@ -123,7 +123,7 @@ public final class OverviewPage extends FormPage { true /* refresh */); } } - + private ElementDescriptor[] computeManifestExtraFilters() { UiElementNode manifest = mEditor.getUiRootNode(); AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java index 2e257c9..4ddb1e9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java @@ -20,6 +20,7 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 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.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.sdklib.xml.AndroidXPathFactory; @@ -37,7 +38,7 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; /** - * Multi-page form editor for /res/menu XML files. + * Multi-page form editor for /res/menu XML files. */ public class MenuEditor extends AndroidXmlEditor { @@ -68,7 +69,7 @@ public class MenuEditor extends AndroidXmlEditor { * Returns whether the "save as" operation is supported by this editor. * <p/> * Save-As is a valid operation for the ManifestEditor since it acts on a - * single source file. + * single source file. * * @see IEditorPart */ @@ -87,7 +88,7 @@ public class MenuEditor extends AndroidXmlEditor { } catch (PartInitException e) { AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ } - + } /* (non-java doc) @@ -102,10 +103,10 @@ public class MenuEditor extends AndroidXmlEditor { setPartName(String.format("%1$s", file.getName())); } } - + /** * Processes the new XML Model, which XML root node is given. - * + * * @param xml_doc The XML document, if available, or null if none exists. */ @Override @@ -121,24 +122,24 @@ public class MenuEditor extends AndroidXmlEditor { Node node = (Node) xpath.evaluate("/" + root_desc.getXmlName(), //$NON-NLS-1$ xml_doc, XPathConstants.NODE); - if (node == null && root_desc.isMandatory()) { + if (node == null && root_desc.getMandatory() != Mandatory.NOT_MANDATORY) { // Create the root element if it doesn't exist yet (for empty new documents) node = mUiRootNode.createXmlNode(); } - // Refresh the manifest UI node and all its descendants + // Refresh the manifest UI node and all its descendants mUiRootNode.loadFromXmlNode(node); - + // TODO ? startMonitoringMarkers(); } catch (XPathExpressionException e) { AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ root_desc.getXmlName()); } } - + super.xmlModelChanged(xml_doc); } - + /** * Creates the initial UI Root Node, including the known mandatory elements. * @param force if true, a new UiRootNode is recreated even if it already exists. @@ -151,7 +152,7 @@ public class MenuEditor extends AndroidXmlEditor { if (mUiRootNode != null) { doc = mUiRootNode.getXmlDocument(); } - + // get the target data from the opened file (and its project) AndroidTargetData data = getTargetData(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java index 60ec130..c1e2d0b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.ui.tree; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; 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.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; @@ -49,7 +50,7 @@ public abstract class UiActions implements ICommitXml { /** * Utility method to select an outline item based on its model node - * + * * @param uiNode The node to select. Can be null (in which case nothing should happen) */ abstract protected void selectUiNode(UiElementNode uiNode); @@ -65,10 +66,10 @@ public abstract class UiActions implements ICommitXml { public void doAdd(UiElementNode uiNode, Shell shell) { doAdd(uiNode, null /* descriptorFilters */, shell, new UiModelTreeLabelProvider()); } - + /** * Called when the "Add..." button next to the tree view is selected. - * + * * Displays a selection dialog that lets the user select which kind of node * to create, depending on the current selection. */ @@ -105,7 +106,7 @@ public abstract class UiActions implements ICommitXml { * If a sibling is given and that sibling has the same parent, the new node is added * right after that sibling. Otherwise the new node is added at the end of the parent * child list. - * + * * @param uiParent An existing UI node or null to add to the tree root * @param uiSibling An existing UI node before which to insert the new node. Can be null. * @param descriptor The descriptor of the element to add @@ -125,31 +126,31 @@ public abstract class UiActions implements ICommitXml { UiElementNode uiNew = addNewTreeElement(uiParent, uiSibling, descriptor, updateLayout); selectUiNode(uiNew); - + return uiNew; } /** * Called when the "Remove" button is selected. - * + * * If the tree has a selection, remove it. * This simply deletes the XML node attached to the UI node: when the XML model fires the * update event, the tree will get refreshed. */ public void doRemove(final List<UiElementNode> nodes, Shell shell) { - + if (nodes == null || nodes.size() == 0) { return; } - + final int len = nodes.size(); - + StringBuilder sb = new StringBuilder(); for (UiElementNode node : nodes) { sb.append("\n- "); //$NON-NLS-1$ sb.append(node.getBreadcrumbTrailDescription(false /* include_root */)); } - + if (MessageDialog.openQuestion(shell, len > 1 ? "Remove elements from Android XML" // title : "Remove element from Android XML", @@ -164,11 +165,11 @@ public abstract class UiActions implements ICommitXml { UiElementNode node = nodes.get(i); previous = node.getUiPreviousSibling(); parent = node.getUiParent(); - + // delete node node.deleteXmlNode(); } - + // try to select the last previous sibling or the last parent if (previous != null) { selectUiNode(previous); @@ -190,20 +191,20 @@ public abstract class UiActions implements ICommitXml { if (nodes == null || nodes.size() < 1) { return; } - + final Node[] select_xml_node = { null }; UiElementNode last_node = null; UiElementNode search_root = null; - + for (int i = 0; i < nodes.size(); i++) { final UiElementNode node = last_node = nodes.get(i); - + // the node will move either up to its parent or grand-parent search_root = node.getUiParent(); if (search_root != null && search_root.getUiParent() != null) { search_root = search_root.getUiParent(); } - + commitPendingXmlChanges(); getRootNode().getEditor().wrapEditXmlModel(new Runnable() { public void run() { @@ -263,7 +264,7 @@ public abstract class UiActions implements ICommitXml { /** * Called when the "Down" button is selected. - * + * * If the tree has a selection, move it down, either in the same child list or as the * first child of the next parent. */ @@ -271,7 +272,7 @@ public abstract class UiActions implements ICommitXml { if (nodes == null || nodes.size() < 1) { return; } - + final Node[] select_xml_node = { null }; UiElementNode last_node = null; UiElementNode search_root = null; @@ -283,7 +284,7 @@ public abstract class UiActions implements ICommitXml { if (search_root != null && search_root.getUiParent() != null) { search_root = search_root.getUiParent(); } - + commitPendingXmlChanges(); getRootNode().getEditor().wrapEditXmlModel(new Runnable() { public void run() { @@ -344,33 +345,55 @@ public abstract class UiActions implements ICommitXml { } //--------------------- - + /** * Adds a new element of the given descriptor's type to the given UI parent node. - * + * * This actually creates the corresponding XML node in the XML model, which in turn * will refresh the current tree view. - * + * * @param uiParent An existing UI node or null to add to the tree root - * @param uiSibling An existing UI node to insert right before. Can be null. + * @param uiSibling An existing UI node to insert right before. Can be null. * @param descriptor The descriptor of the element to add * @param updateLayout True if layout attributes should be set * @return The {@link UiElementNode} that has been added to the UI tree. */ private UiElementNode addNewTreeElement(UiElementNode uiParent, - final UiElementNode uiSibling, + UiElementNode uiSibling, ElementDescriptor descriptor, final boolean updateLayout) { commitPendingXmlChanges(); - - int index = 0; - for (UiElementNode uiChild : uiParent.getUiChildren()) { - if (uiChild == uiSibling) { - break; + + List<UiElementNode> uiChildren = uiParent.getUiChildren(); + int n = uiChildren.size(); + + // The default is to append at the end of the list. + int index = n; + + if (uiSibling != null) { + // Try to find the requested sibling. + index = uiChildren.indexOf(uiSibling); + if (index < 0) { + // This sibling didn't exist. Should not happen but compensate + // by simply adding to the end of the list. + uiSibling = null; + index = n; } - index++; } - + + if (uiSibling == null) { + // If we don't require any specific position, make sure to insert before the + // first mandatory_last descriptor's position, if any. + + for (int i = 0; i < n; i++) { + UiElementNode uiChild = uiChildren.get(i); + if (uiChild.getDescriptor().getMandatory() == Mandatory.MANDATORY_LAST) { + index = i; + break; + } + } + } + final UiElementNode uiNew = uiParent.insertNewUiChild(index, descriptor); UiElementNode rootNode = getRootNode(); 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 index b57901f..92ad589 100644 --- 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 @@ -26,6 +26,7 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.IUnknownDescript 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.descriptors.ElementDescriptor.Mandatory; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; @@ -130,6 +131,16 @@ public class UiElementNode implements IPropertySource { 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. @@ -765,7 +776,7 @@ public class UiElementNode implements IPropertySource { * @return The XML node matching this {@link UiElementNode} or null. */ public Node prepareCommit() { - if (getDescriptor().isMandatory()) { + 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 @@ -1017,6 +1028,7 @@ public class UiElementNode implements IPropertySource { */ protected boolean updateElementList(Node xmlNode) { boolean structureChanged = false; + boolean hasMandatoryLast = false; int uiIndex = 0; Node xmlChild = xmlNode.getFirstChild(); while (xmlChild != null) { @@ -1063,14 +1075,16 @@ public class UiElementNode implements IPropertySource { for (int j = uiIndex; j < n; j++) { uiChild = mUiChildren.get(j); if (uiChild.getXmlNode() == null && - uiChild.getDescriptor().isMandatory() && + 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. + // Assign the XML node to this empty mandatory element. uiChild.mXmlNode = xmlChild; structureChanged = true; uiNode = uiChild; @@ -1099,6 +1113,10 @@ public class UiElementNode implements IPropertySource { // 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(); @@ -1109,6 +1127,28 @@ public class UiElementNode implements IPropertySource { 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; } @@ -1129,7 +1169,7 @@ public class UiElementNode implements IPropertySource { ElementDescriptor desc = uiNode.getDescriptor(); try { - if (uiNode.getDescriptor().isMandatory()) { + 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. @@ -1191,6 +1231,10 @@ public class UiElementNode implements IPropertySource { * 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. */ |