2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package com.android.ide.eclipse.adt.internal.editors.ui.tree;
20 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
21 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
22 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory;
23 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
26 import org.eclipse.jface.dialogs.MessageDialog;
27 import org.eclipse.jface.viewers.ILabelProvider;
28 import org.eclipse.swt.widgets.Shell;
29 import org.w3c.dom.Document;
30 import org.w3c.dom.Node;
32 import java.util.List;
35 * Performs basic actions on an XML tree: add node, remove node, move up/down.
37 public abstract class UiActions implements ICommitXml {
42 //---------------------
43 // Actual implementations must override these to provide specific hooks
45 /** Returns the UiDocumentNode for the current model. */
46 abstract protected UiElementNode getRootNode();
48 /** Commits pending data before the XML model is modified. */
49 abstract public void commitPendingXmlChanges();
52 * Utility method to select an outline item based on its model node
54 * @param uiNode The node to select. Can be null (in which case nothing should happen)
56 abstract protected void selectUiNode(UiElementNode uiNode);
58 //---------------------
61 * Called when the "Add..." button next to the tree view is selected.
63 * This simplified version of doAdd does not support descriptor filters and creates
64 * a new {@link UiModelTreeLabelProvider} for each call.
66 public void doAdd(UiElementNode uiNode, Shell shell) {
67 doAdd(uiNode, null /* descriptorFilters */, shell, new UiModelTreeLabelProvider());
71 * Called when the "Add..." button next to the tree view is selected.
73 * Displays a selection dialog that lets the user select which kind of node
74 * to create, depending on the current selection.
76 public void doAdd(UiElementNode uiNode,
77 ElementDescriptor[] descriptorFilters,
78 Shell shell, ILabelProvider labelProvider) {
79 // If the root node is a document with already a root, use it as the root node
80 UiElementNode rootNode = getRootNode();
81 if (rootNode instanceof UiDocumentNode && rootNode.getUiChildren().size() > 0) {
82 rootNode = rootNode.getUiChildren().get(0);
85 NewItemSelectionDialog dlg = new NewItemSelectionDialog(
91 Object[] results = dlg.getResult();
92 if (results != null && results.length > 0) {
93 addElement(dlg.getChosenRootNode(), null, (ElementDescriptor) results[0],
94 true /*updateLayout*/);
99 * Adds a new XML element based on the {@link ElementDescriptor} to the given parent
100 * {@link UiElementNode}, and then select it.
102 * If the parent is a document root which already contains a root element, the inner
103 * root element is used as the actual parent. This ensure you can't create a broken
104 * XML file with more than one root element.
106 * If a sibling is given and that sibling has the same parent, the new node is added
107 * right after that sibling. Otherwise the new node is added at the end of the parent
110 * @param uiParent An existing UI node or null to add to the tree root
111 * @param uiSibling An existing UI node before which to insert the new node. Can be null.
112 * @param descriptor The descriptor of the element to add
113 * @param updateLayout True if layout attributes should be set
114 * @return The new {@link UiElementNode} or null.
116 public UiElementNode addElement(UiElementNode uiParent,
117 UiElementNode uiSibling,
118 ElementDescriptor descriptor,
119 boolean updateLayout) {
120 if (uiParent instanceof UiDocumentNode && uiParent.getUiChildren().size() > 0) {
121 uiParent = uiParent.getUiChildren().get(0);
123 if (uiSibling != null && uiSibling.getUiParent() != uiParent) {
127 UiElementNode uiNew = addNewTreeElement(uiParent, uiSibling, descriptor, updateLayout);
134 * Called when the "Remove" button is selected.
136 * If the tree has a selection, remove it.
137 * This simply deletes the XML node attached to the UI node: when the XML model fires the
138 * update event, the tree will get refreshed.
140 public void doRemove(final List<UiElementNode> nodes, Shell shell) {
142 if (nodes == null || nodes.size() == 0) {
146 final int len = nodes.size();
148 StringBuilder sb = new StringBuilder();
149 for (UiElementNode node : nodes) {
150 sb.append("\n- "); //$NON-NLS-1$
151 sb.append(node.getBreadcrumbTrailDescription(false /* include_root */));
154 if (MessageDialog.openQuestion(shell,
155 len > 1 ? "Remove elements from Android XML" // title
156 : "Remove element from Android XML",
157 String.format("Do you really want to remove %1$s?", sb.toString()))) {
158 commitPendingXmlChanges();
159 getRootNode().getEditor().wrapEditXmlModel(new Runnable() {
161 UiElementNode previous = null;
162 UiElementNode parent = null;
164 for (int i = len - 1; i >= 0; i--) {
165 UiElementNode node = nodes.get(i);
166 previous = node.getUiPreviousSibling();
167 parent = node.getUiParent();
170 node.deleteXmlNode();
173 // try to select the last previous sibling or the last parent
174 if (previous != null) {
175 selectUiNode(previous);
176 } else if (parent != null) {
177 selectUiNode(parent);
185 * Called when the "Up" button is selected.
187 * If the tree has a selection, move it up, either in the child list or as the last child
188 * of the previous parent.
191 final List<UiElementNode> uiNodes,
192 final ElementDescriptor[] descriptorFilters) {
193 if (uiNodes == null || uiNodes.size() < 1) {
197 final Node[] selectXmlNode = { null };
198 final UiElementNode[] uiLastNode = { null };
199 final UiElementNode[] uiSearchRoot = { null };
201 commitPendingXmlChanges();
202 getRootNode().getEditor().wrapEditXmlModel(new Runnable() {
204 for (int i = 0; i < uiNodes.size(); i++) {
205 UiElementNode uiNode = uiLastNode[0] = uiNodes.get(i);
216 assert uiLastNode[0] != null; // tell Eclipse this can't be null below
218 if (selectXmlNode[0] == null) {
219 // The XML node has not been moved, we can just select the same UI node
220 selectUiNode(uiLastNode[0]);
222 // The XML node has moved. At this point the UI model has been reloaded
223 // and the XML node has been affected to a new UI node. Find that new UI
224 // node and select it.
225 if (uiSearchRoot[0] == null) {
226 uiSearchRoot[0] = uiLastNode[0].getUiRoot();
228 if (uiSearchRoot[0] != null) {
229 selectUiNode(uiSearchRoot[0].findXmlNode(selectXmlNode[0]));
235 * Checks whether the "up" action can be performed on all items.
237 * @return True if the up action can be carried on *all* items.
239 public boolean canDoUp(
240 List<UiElementNode> uiNodes,
241 ElementDescriptor[] descriptorFilters) {
242 if (uiNodes == null || uiNodes.size() < 1) {
246 final Node[] selectXmlNode = { null };
247 final UiElementNode[] uiSearchRoot = { null };
249 commitPendingXmlChanges();
251 for (int i = 0; i < uiNodes.size(); i++) {
257 true /*testOnly*/)) {
265 private boolean doUpInternal(
266 UiElementNode uiNode,
267 ElementDescriptor[] descriptorFilters,
268 Node[] outSelectXmlNode,
269 UiElementNode[] outUiSearchRoot,
271 // the node will move either up to its parent or grand-parent
272 outUiSearchRoot[0] = uiNode.getUiParent();
273 if (outUiSearchRoot[0] != null && outUiSearchRoot[0].getUiParent() != null) {
274 outUiSearchRoot[0] = outUiSearchRoot[0].getUiParent();
276 Node xmlNode = uiNode.getXmlNode();
277 ElementDescriptor nodeDesc = uiNode.getDescriptor();
278 if (xmlNode == null || nodeDesc == null) {
281 UiElementNode uiParentNode = uiNode.getUiParent();
282 Node xmlParent = uiParentNode == null ? null : uiParentNode.getXmlNode();
283 if (xmlParent == null) {
287 UiElementNode uiPrev = uiNode.getUiPreviousSibling();
289 // Only accept a sibling that has an XML attached and
290 // is part of the allowed descriptor filters.
291 while (uiPrev != null &&
292 (uiPrev.getXmlNode() == null || !matchDescFilter(descriptorFilters, uiPrev))) {
293 uiPrev = uiPrev.getUiPreviousSibling();
296 if (uiPrev != null && uiPrev.getXmlNode() != null) {
297 // This node is not the first one of the parent.
298 Node xmlPrev = uiPrev.getXmlNode();
299 if (uiPrev.getDescriptor().acceptChild(nodeDesc)) {
300 // If the previous sibling can accept this child, then it
301 // is inserted at the end of the children list.
305 xmlPrev.appendChild(xmlParent.removeChild(xmlNode));
306 outSelectXmlNode[0] = xmlNode;
308 // This node is not the first one of the parent, so it can be
309 // removed and then inserted before its previous sibling.
313 xmlParent.insertBefore(
314 xmlParent.removeChild(xmlNode),
316 outSelectXmlNode[0] = xmlNode;
318 } else if (uiParentNode != null && !(xmlParent instanceof Document)) {
319 UiElementNode uiGrandParent = uiParentNode.getUiParent();
320 Node xmlGrandParent = uiGrandParent == null ? null : uiGrandParent.getXmlNode();
321 ElementDescriptor grandDesc =
322 uiGrandParent == null ? null : uiGrandParent.getDescriptor();
324 if (xmlGrandParent != null &&
325 !(xmlGrandParent instanceof Document) &&
327 grandDesc.acceptChild(nodeDesc)) {
328 // If the node is the first one of the child list of its
329 // parent, move it up in the hierarchy as previous sibling
330 // to the parent. This is only possible if the parent of the
331 // parent is not a document.
332 // The parent node must actually accept this kind of child.
337 xmlGrandParent.insertBefore(
338 xmlParent.removeChild(xmlNode),
340 outSelectXmlNode[0] = xmlNode;
347 private boolean matchDescFilter(
348 ElementDescriptor[] descriptorFilters,
349 UiElementNode uiNode) {
350 if (descriptorFilters == null || descriptorFilters.length < 1) {
354 ElementDescriptor desc = uiNode.getDescriptor();
356 for (ElementDescriptor filter : descriptorFilters) {
357 if (filter.equals(desc)) {
365 * Called when the "Down" button is selected.
367 * If the tree has a selection, move it down, either in the same child list or as the
368 * first child of the next parent.
371 final List<UiElementNode> nodes,
372 final ElementDescriptor[] descriptorFilters) {
373 if (nodes == null || nodes.size() < 1) {
377 final Node[] selectXmlNode = { null };
378 final UiElementNode[] uiLastNode = { null };
379 final UiElementNode[] uiSearchRoot = { null };
381 commitPendingXmlChanges();
382 getRootNode().getEditor().wrapEditXmlModel(new Runnable() {
384 for (int i = nodes.size() - 1; i >= 0; i--) {
385 final UiElementNode node = uiLastNode[0] = nodes.get(i);
396 assert uiLastNode[0] != null; // tell Eclipse this can't be null below
398 if (selectXmlNode[0] == null) {
399 // The XML node has not been moved, we can just select the same UI node
400 selectUiNode(uiLastNode[0]);
402 // The XML node has moved. At this point the UI model has been reloaded
403 // and the XML node has been affected to a new UI node. Find that new UI
404 // node and select it.
405 if (uiSearchRoot[0] == null) {
406 uiSearchRoot[0] = uiLastNode[0].getUiRoot();
408 if (uiSearchRoot[0] != null) {
409 selectUiNode(uiSearchRoot[0].findXmlNode(selectXmlNode[0]));
415 * Checks whether the "down" action can be performed on all items.
417 * @return True if the down action can be carried on *all* items.
419 public boolean canDoDown(
420 List<UiElementNode> uiNodes,
421 ElementDescriptor[] descriptorFilters) {
422 if (uiNodes == null || uiNodes.size() < 1) {
426 final Node[] selectXmlNode = { null };
427 final UiElementNode[] uiSearchRoot = { null };
429 commitPendingXmlChanges();
431 for (int i = 0; i < uiNodes.size(); i++) {
437 true /*testOnly*/)) {
445 private boolean doDownInternal(
446 UiElementNode uiNode,
447 ElementDescriptor[] descriptorFilters,
448 Node[] outSelectXmlNode,
449 UiElementNode[] outUiSearchRoot,
451 // the node will move either down to its parent or grand-parent
452 outUiSearchRoot[0] = uiNode.getUiParent();
453 if (outUiSearchRoot[0] != null && outUiSearchRoot[0].getUiParent() != null) {
454 outUiSearchRoot[0] = outUiSearchRoot[0].getUiParent();
457 Node xmlNode = uiNode.getXmlNode();
458 ElementDescriptor nodeDesc = uiNode.getDescriptor();
459 if (xmlNode == null || nodeDesc == null) {
462 UiElementNode uiParentNode = uiNode.getUiParent();
463 Node xmlParent = uiParentNode == null ? null : uiParentNode.getXmlNode();
464 if (xmlParent == null) {
468 UiElementNode uiNext = uiNode.getUiNextSibling();
470 // Only accept a sibling that has an XML attached and
471 // is part of the allowed descriptor filters.
472 while (uiNext != null &&
473 (uiNext.getXmlNode() == null || !matchDescFilter(descriptorFilters, uiNext))) {
474 uiNext = uiNext.getUiNextSibling();
477 if (uiNext != null && uiNext.getXmlNode() != null) {
478 // This node is not the last one of the parent.
479 Node xmlNext = uiNext.getXmlNode();
480 // If the next sibling is a node that can have children, though,
481 // then the node is inserted as the first child.
482 if (uiNext.getDescriptor().acceptChild(nodeDesc)) {
486 // Note: insertBefore works as append if the ref node is
487 // null, i.e. when the node doesn't have children yet.
488 xmlNext.insertBefore(
489 xmlParent.removeChild(xmlNode),
490 xmlNext.getFirstChild());
491 outSelectXmlNode[0] = xmlNode;
493 // This node is not the last one of the parent, so it can be
494 // removed and then inserted after its next sibling.
499 // Insert "before after next" ;-)
500 xmlParent.insertBefore(
501 xmlParent.removeChild(xmlNode),
502 xmlNext.getNextSibling());
503 outSelectXmlNode[0] = xmlNode;
505 } else if (uiParentNode != null && !(xmlParent instanceof Document)) {
506 UiElementNode uiGrandParent = uiParentNode.getUiParent();
507 Node xmlGrandParent = uiGrandParent == null ? null : uiGrandParent.getXmlNode();
508 ElementDescriptor grandDesc =
509 uiGrandParent == null ? null : uiGrandParent.getDescriptor();
511 if (xmlGrandParent != null &&
512 !(xmlGrandParent instanceof Document) &&
514 grandDesc.acceptChild(nodeDesc)) {
515 // This node is the last node of its parent.
516 // If neither the parent nor the grandparent is a document,
517 // then the node can be inserted right after the parent.
518 // The parent node must actually accept this kind of child.
522 xmlGrandParent.insertBefore(
523 xmlParent.removeChild(xmlNode),
524 xmlParent.getNextSibling());
525 outSelectXmlNode[0] = xmlNode;
532 //---------------------
535 * Adds a new element of the given descriptor's type to the given UI parent node.
537 * This actually creates the corresponding XML node in the XML model, which in turn
538 * will refresh the current tree view.
540 * @param uiParent An existing UI node or null to add to the tree root
541 * @param uiSibling An existing UI node to insert right before. Can be null.
542 * @param descriptor The descriptor of the element to add
543 * @param updateLayout True if layout attributes should be set
544 * @return The {@link UiElementNode} that has been added to the UI tree.
546 private UiElementNode addNewTreeElement(UiElementNode uiParent,
547 UiElementNode uiSibling,
548 ElementDescriptor descriptor,
549 final boolean updateLayout) {
550 commitPendingXmlChanges();
552 List<UiElementNode> uiChildren = uiParent.getUiChildren();
553 int n = uiChildren.size();
555 // The default is to append at the end of the list.
558 if (uiSibling != null) {
559 // Try to find the requested sibling.
560 index = uiChildren.indexOf(uiSibling);
562 // This sibling didn't exist. Should not happen but compensate
563 // by simply adding to the end of the list.
569 if (uiSibling == null) {
570 // If we don't require any specific position, make sure to insert before the
571 // first mandatory_last descriptor's position, if any.
573 for (int i = 0; i < n; i++) {
574 UiElementNode uiChild = uiChildren.get(i);
575 if (uiChild.getDescriptor().getMandatory() == Mandatory.MANDATORY_LAST) {
582 final UiElementNode uiNew = uiParent.insertNewUiChild(index, descriptor);
583 UiElementNode rootNode = getRootNode();
585 rootNode.getEditor().wrapEditXmlModel(new Runnable() {
587 DescriptorsUtils.setDefaultLayoutAttributes(uiNew, updateLayout);
588 uiNew.createXmlNode();