aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java598
1 files changed, 598 insertions, 0 deletions
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
new file mode 100644
index 000000000..92ccf2e7d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java
@@ -0,0 +1,598 @@
+/*
+ * 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.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;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.swt.widgets.Shell;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.util.List;
+
+/**
+ * Performs basic actions on an XML tree: add node, remove node, move up/down.
+ */
+public abstract class UiActions implements ICommitXml {
+
+ public UiActions() {
+ }
+
+ //---------------------
+ // Actual implementations must override these to provide specific hooks
+
+ /** Returns the UiDocumentNode for the current model. */
+ abstract protected UiElementNode getRootNode();
+
+ /** Commits pending data before the XML model is modified. */
+ @Override
+ abstract public void commitPendingXmlChanges();
+
+ /**
+ * 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);
+
+ //---------------------
+
+ /**
+ * Called when the "Add..." button next to the tree view is selected.
+ * <p/>
+ * This simplified version of doAdd does not support descriptor filters and creates
+ * a new {@link UiModelTreeLabelProvider} for each call.
+ */
+ 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.
+ */
+ public void doAdd(UiElementNode uiNode,
+ ElementDescriptor[] descriptorFilters,
+ Shell shell, ILabelProvider labelProvider) {
+ // If the root node is a document with already a root, use it as the root node
+ UiElementNode rootNode = getRootNode();
+ if (rootNode instanceof UiDocumentNode && rootNode.getUiChildren().size() > 0) {
+ rootNode = rootNode.getUiChildren().get(0);
+ }
+
+ NewItemSelectionDialog dlg = new NewItemSelectionDialog(
+ shell,
+ labelProvider,
+ descriptorFilters,
+ uiNode, rootNode);
+ dlg.open();
+ Object[] results = dlg.getResult();
+ if (results != null && results.length > 0) {
+ addElement(dlg.getChosenRootNode(), null, (ElementDescriptor) results[0],
+ true /*updateLayout*/);
+ }
+ }
+
+ /**
+ * Adds a new XML element based on the {@link ElementDescriptor} to the given parent
+ * {@link UiElementNode}, and then select it.
+ * <p/>
+ * If the parent is a document root which already contains a root element, the inner
+ * root element is used as the actual parent. This ensure you can't create a broken
+ * XML file with more than one root element.
+ * <p/>
+ * 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
+ * @param updateLayout True if layout attributes should be set
+ * @return The new {@link UiElementNode} or null.
+ */
+ public UiElementNode addElement(UiElementNode uiParent,
+ UiElementNode uiSibling,
+ ElementDescriptor descriptor,
+ boolean updateLayout) {
+ if (uiParent instanceof UiDocumentNode && uiParent.getUiChildren().size() > 0) {
+ uiParent = uiParent.getUiChildren().get(0);
+ }
+ if (uiSibling != null && uiSibling.getUiParent() != uiParent) {
+ uiSibling = null;
+ }
+
+ 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",
+ String.format("Do you really want to remove %1$s?", sb.toString()))) {
+ commitPendingXmlChanges();
+ getRootNode().getEditor().wrapEditXmlModel(new Runnable() {
+ @Override
+ public void run() {
+ UiElementNode previous = null;
+ UiElementNode parent = null;
+
+ for (int i = len - 1; i >= 0; i--) {
+ 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);
+ } else if (parent != null) {
+ selectUiNode(parent);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Called when the "Up" button is selected.
+ * <p/>
+ * If the tree has a selection, move it up, either in the child list or as the last child
+ * of the previous parent.
+ */
+ public void doUp(
+ final List<UiElementNode> uiNodes,
+ final ElementDescriptor[] descriptorFilters) {
+ if (uiNodes == null || uiNodes.size() < 1) {
+ return;
+ }
+
+ final Node[] selectXmlNode = { null };
+ final UiElementNode[] uiLastNode = { null };
+ final UiElementNode[] uiSearchRoot = { null };
+
+ commitPendingXmlChanges();
+ getRootNode().getEditor().wrapEditXmlModel(new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < uiNodes.size(); i++) {
+ UiElementNode uiNode = uiLastNode[0] = uiNodes.get(i);
+ doUpInternal(
+ uiNode,
+ descriptorFilters,
+ selectXmlNode,
+ uiSearchRoot,
+ false /*testOnly*/);
+ }
+ }
+ });
+
+ assert uiLastNode[0] != null; // tell Eclipse this can't be null below
+
+ if (selectXmlNode[0] == null) {
+ // The XML node has not been moved, we can just select the same UI node
+ selectUiNode(uiLastNode[0]);
+ } else {
+ // The XML node has moved. At this point the UI model has been reloaded
+ // and the XML node has been affected to a new UI node. Find that new UI
+ // node and select it.
+ if (uiSearchRoot[0] == null) {
+ uiSearchRoot[0] = uiLastNode[0].getUiRoot();
+ }
+ if (uiSearchRoot[0] != null) {
+ selectUiNode(uiSearchRoot[0].findXmlNode(selectXmlNode[0]));
+ }
+ }
+ }
+
+ /**
+ * Checks whether the "up" action can be performed on all items.
+ *
+ * @return True if the up action can be carried on *all* items.
+ */
+ public boolean canDoUp(
+ List<UiElementNode> uiNodes,
+ ElementDescriptor[] descriptorFilters) {
+ if (uiNodes == null || uiNodes.size() < 1) {
+ return false;
+ }
+
+ final Node[] selectXmlNode = { null };
+ final UiElementNode[] uiSearchRoot = { null };
+
+ commitPendingXmlChanges();
+
+ for (int i = 0; i < uiNodes.size(); i++) {
+ if (!doUpInternal(
+ uiNodes.get(i),
+ descriptorFilters,
+ selectXmlNode,
+ uiSearchRoot,
+ true /*testOnly*/)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean doUpInternal(
+ UiElementNode uiNode,
+ ElementDescriptor[] descriptorFilters,
+ Node[] outSelectXmlNode,
+ UiElementNode[] outUiSearchRoot,
+ boolean testOnly) {
+ // the node will move either up to its parent or grand-parent
+ outUiSearchRoot[0] = uiNode.getUiParent();
+ if (outUiSearchRoot[0] != null && outUiSearchRoot[0].getUiParent() != null) {
+ outUiSearchRoot[0] = outUiSearchRoot[0].getUiParent();
+ }
+ Node xmlNode = uiNode.getXmlNode();
+ ElementDescriptor nodeDesc = uiNode.getDescriptor();
+ if (xmlNode == null || nodeDesc == null) {
+ return false;
+ }
+ UiElementNode uiParentNode = uiNode.getUiParent();
+ Node xmlParent = uiParentNode == null ? null : uiParentNode.getXmlNode();
+ if (xmlParent == null) {
+ return false;
+ }
+
+ UiElementNode uiPrev = uiNode.getUiPreviousSibling();
+
+ // Only accept a sibling that has an XML attached and
+ // is part of the allowed descriptor filters.
+ while (uiPrev != null &&
+ (uiPrev.getXmlNode() == null || !matchDescFilter(descriptorFilters, uiPrev))) {
+ uiPrev = uiPrev.getUiPreviousSibling();
+ }
+
+ if (uiPrev != null && uiPrev.getXmlNode() != null) {
+ // This node is not the first one of the parent.
+ Node xmlPrev = uiPrev.getXmlNode();
+ if (uiPrev.getDescriptor().acceptChild(nodeDesc)) {
+ // If the previous sibling can accept this child, then it
+ // is inserted at the end of the children list.
+ if (testOnly) {
+ return true;
+ }
+ xmlPrev.appendChild(xmlParent.removeChild(xmlNode));
+ outSelectXmlNode[0] = xmlNode;
+ } else {
+ // This node is not the first one of the parent, so it can be
+ // removed and then inserted before its previous sibling.
+ if (testOnly) {
+ return true;
+ }
+ xmlParent.insertBefore(
+ xmlParent.removeChild(xmlNode),
+ xmlPrev);
+ outSelectXmlNode[0] = xmlNode;
+ }
+ } else if (uiParentNode != null && !(xmlParent instanceof Document)) {
+ UiElementNode uiGrandParent = uiParentNode.getUiParent();
+ Node xmlGrandParent = uiGrandParent == null ? null : uiGrandParent.getXmlNode();
+ ElementDescriptor grandDesc =
+ uiGrandParent == null ? null : uiGrandParent.getDescriptor();
+
+ if (xmlGrandParent != null &&
+ !(xmlGrandParent instanceof Document) &&
+ grandDesc != null &&
+ grandDesc.acceptChild(nodeDesc)) {
+ // If the node is the first one of the child list of its
+ // parent, move it up in the hierarchy as previous sibling
+ // to the parent. This is only possible if the parent of the
+ // parent is not a document.
+ // The parent node must actually accept this kind of child.
+
+ if (testOnly) {
+ return true;
+ }
+ xmlGrandParent.insertBefore(
+ xmlParent.removeChild(xmlNode),
+ xmlParent);
+ outSelectXmlNode[0] = xmlNode;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean matchDescFilter(
+ ElementDescriptor[] descriptorFilters,
+ UiElementNode uiNode) {
+ if (descriptorFilters == null || descriptorFilters.length < 1) {
+ return true;
+ }
+
+ ElementDescriptor desc = uiNode.getDescriptor();
+
+ for (ElementDescriptor filter : descriptorFilters) {
+ if (filter.equals(desc)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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.
+ */
+ public void doDown(
+ final List<UiElementNode> nodes,
+ final ElementDescriptor[] descriptorFilters) {
+ if (nodes == null || nodes.size() < 1) {
+ return;
+ }
+
+ final Node[] selectXmlNode = { null };
+ final UiElementNode[] uiLastNode = { null };
+ final UiElementNode[] uiSearchRoot = { null };
+
+ commitPendingXmlChanges();
+ getRootNode().getEditor().wrapEditXmlModel(new Runnable() {
+ @Override
+ public void run() {
+ for (int i = nodes.size() - 1; i >= 0; i--) {
+ final UiElementNode node = uiLastNode[0] = nodes.get(i);
+ doDownInternal(
+ node,
+ descriptorFilters,
+ selectXmlNode,
+ uiSearchRoot,
+ false /*testOnly*/);
+ }
+ }
+ });
+
+ assert uiLastNode[0] != null; // tell Eclipse this can't be null below
+
+ if (selectXmlNode[0] == null) {
+ // The XML node has not been moved, we can just select the same UI node
+ selectUiNode(uiLastNode[0]);
+ } else {
+ // The XML node has moved. At this point the UI model has been reloaded
+ // and the XML node has been affected to a new UI node. Find that new UI
+ // node and select it.
+ if (uiSearchRoot[0] == null) {
+ uiSearchRoot[0] = uiLastNode[0].getUiRoot();
+ }
+ if (uiSearchRoot[0] != null) {
+ selectUiNode(uiSearchRoot[0].findXmlNode(selectXmlNode[0]));
+ }
+ }
+ }
+
+ /**
+ * Checks whether the "down" action can be performed on all items.
+ *
+ * @return True if the down action can be carried on *all* items.
+ */
+ public boolean canDoDown(
+ List<UiElementNode> uiNodes,
+ ElementDescriptor[] descriptorFilters) {
+ if (uiNodes == null || uiNodes.size() < 1) {
+ return false;
+ }
+
+ final Node[] selectXmlNode = { null };
+ final UiElementNode[] uiSearchRoot = { null };
+
+ commitPendingXmlChanges();
+
+ for (int i = 0; i < uiNodes.size(); i++) {
+ if (!doDownInternal(
+ uiNodes.get(i),
+ descriptorFilters,
+ selectXmlNode,
+ uiSearchRoot,
+ true /*testOnly*/)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean doDownInternal(
+ UiElementNode uiNode,
+ ElementDescriptor[] descriptorFilters,
+ Node[] outSelectXmlNode,
+ UiElementNode[] outUiSearchRoot,
+ boolean testOnly) {
+ // the node will move either down to its parent or grand-parent
+ outUiSearchRoot[0] = uiNode.getUiParent();
+ if (outUiSearchRoot[0] != null && outUiSearchRoot[0].getUiParent() != null) {
+ outUiSearchRoot[0] = outUiSearchRoot[0].getUiParent();
+ }
+
+ Node xmlNode = uiNode.getXmlNode();
+ ElementDescriptor nodeDesc = uiNode.getDescriptor();
+ if (xmlNode == null || nodeDesc == null) {
+ return false;
+ }
+ UiElementNode uiParentNode = uiNode.getUiParent();
+ Node xmlParent = uiParentNode == null ? null : uiParentNode.getXmlNode();
+ if (xmlParent == null) {
+ return false;
+ }
+
+ UiElementNode uiNext = uiNode.getUiNextSibling();
+
+ // Only accept a sibling that has an XML attached and
+ // is part of the allowed descriptor filters.
+ while (uiNext != null &&
+ (uiNext.getXmlNode() == null || !matchDescFilter(descriptorFilters, uiNext))) {
+ uiNext = uiNext.getUiNextSibling();
+ }
+
+ if (uiNext != null && uiNext.getXmlNode() != null) {
+ // This node is not the last one of the parent.
+ Node xmlNext = uiNext.getXmlNode();
+ // If the next sibling is a node that can have children, though,
+ // then the node is inserted as the first child.
+ if (uiNext.getDescriptor().acceptChild(nodeDesc)) {
+ if (testOnly) {
+ return true;
+ }
+ // Note: insertBefore works as append if the ref node is
+ // null, i.e. when the node doesn't have children yet.
+ xmlNext.insertBefore(
+ xmlParent.removeChild(xmlNode),
+ xmlNext.getFirstChild());
+ outSelectXmlNode[0] = xmlNode;
+ } else {
+ // This node is not the last one of the parent, so it can be
+ // removed and then inserted after its next sibling.
+
+ if (testOnly) {
+ return true;
+ }
+ // Insert "before after next" ;-)
+ xmlParent.insertBefore(
+ xmlParent.removeChild(xmlNode),
+ xmlNext.getNextSibling());
+ outSelectXmlNode[0] = xmlNode;
+ }
+ } else if (uiParentNode != null && !(xmlParent instanceof Document)) {
+ UiElementNode uiGrandParent = uiParentNode.getUiParent();
+ Node xmlGrandParent = uiGrandParent == null ? null : uiGrandParent.getXmlNode();
+ ElementDescriptor grandDesc =
+ uiGrandParent == null ? null : uiGrandParent.getDescriptor();
+
+ if (xmlGrandParent != null &&
+ !(xmlGrandParent instanceof Document) &&
+ grandDesc != null &&
+ grandDesc.acceptChild(nodeDesc)) {
+ // This node is the last node of its parent.
+ // If neither the parent nor the grandparent is a document,
+ // then the node can be inserted right after the parent.
+ // The parent node must actually accept this kind of child.
+ if (testOnly) {
+ return true;
+ }
+ xmlGrandParent.insertBefore(
+ xmlParent.removeChild(xmlNode),
+ xmlParent.getNextSibling());
+ outSelectXmlNode[0] = xmlNode;
+ }
+ }
+
+ return false;
+ }
+
+ //---------------------
+
+ /**
+ * 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 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,
+ UiElementNode uiSibling,
+ ElementDescriptor descriptor,
+ final boolean updateLayout) {
+ commitPendingXmlChanges();
+
+ 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;
+ }
+ }
+
+ 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();
+
+ rootNode.getEditor().wrapEditXmlModel(new Runnable() {
+ @Override
+ public void run() {
+ DescriptorsUtils.setDefaultLayoutAttributes(uiNew, updateLayout);
+ uiNew.createXmlNode();
+ }
+ });
+ return uiNew;
+ }
+}