• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 
18 package com.android.ide.eclipse.adt.internal.editors.ui.tree;
19 
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.uimodel.UiDocumentNode;
23 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
24 
25 import org.eclipse.jface.dialogs.MessageDialog;
26 import org.eclipse.jface.viewers.ILabelProvider;
27 import org.eclipse.swt.widgets.Shell;
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Node;
30 
31 import java.util.List;
32 
33 /**
34  * Performs basic actions on an XML tree: add node, remove node, move up/down.
35  */
36 public abstract class UiActions implements ICommitXml {
37 
UiActions()38     public UiActions() {
39     }
40 
41     //---------------------
42     // Actual implementations must override these to provide specific hooks
43 
44     /** Returns the UiDocumentNode for the current model. */
getRootNode()45     abstract protected UiElementNode getRootNode();
46 
47     /** Commits pending data before the XML model is modified. */
commitPendingXmlChanges()48     abstract public void commitPendingXmlChanges();
49 
50     /**
51      * Utility method to select an outline item based on its model node
52      *
53      * @param uiNode The node to select. Can be null (in which case nothing should happen)
54      */
selectUiNode(UiElementNode uiNode)55     abstract protected void selectUiNode(UiElementNode uiNode);
56 
57     //---------------------
58 
59     /**
60      * Called when the "Add..." button next to the tree view is selected.
61      * <p/>
62      * This simplified version of doAdd does not support descriptor filters and creates
63      * a new {@link UiModelTreeLabelProvider} for each call.
64      */
doAdd(UiElementNode uiNode, Shell shell)65     public void doAdd(UiElementNode uiNode, Shell shell) {
66         doAdd(uiNode, null /* descriptorFilters */, shell, new UiModelTreeLabelProvider());
67     }
68 
69     /**
70      * Called when the "Add..." button next to the tree view is selected.
71      *
72      * Displays a selection dialog that lets the user select which kind of node
73      * to create, depending on the current selection.
74      */
doAdd(UiElementNode uiNode, ElementDescriptor[] descriptorFilters, Shell shell, ILabelProvider labelProvider)75     public void doAdd(UiElementNode uiNode,
76             ElementDescriptor[] descriptorFilters,
77             Shell shell, ILabelProvider labelProvider) {
78         // If the root node is a document with already a root, use it as the root node
79         UiElementNode rootNode = getRootNode();
80         if (rootNode instanceof UiDocumentNode && rootNode.getUiChildren().size() > 0) {
81             rootNode = rootNode.getUiChildren().get(0);
82         }
83 
84         NewItemSelectionDialog dlg = new NewItemSelectionDialog(
85                 shell,
86                 labelProvider,
87                 descriptorFilters,
88                 uiNode, rootNode);
89         dlg.open();
90         Object[] results = dlg.getResult();
91         if (results != null && results.length > 0) {
92             addElement(dlg.getChosenRootNode(), null, (ElementDescriptor) results[0],
93                     true /*updateLayout*/);
94         }
95     }
96 
97     /**
98      * Adds a new XML element based on the {@link ElementDescriptor} to the given parent
99      * {@link UiElementNode}, and then select it.
100      * <p/>
101      * If the parent is a document root which already contains a root element, the inner
102      * root element is used as the actual parent. This ensure you can't create a broken
103      * XML file with more than one root element.
104      * <p/>
105      * If a sibling is given and that sibling has the same parent, the new node is added
106      * right after that sibling. Otherwise the new node is added at the end of the parent
107      * child list.
108      *
109      * @param uiParent An existing UI node or null to add to the tree root
110      * @param uiSibling An existing UI node before which to insert the new node. Can be null.
111      * @param descriptor The descriptor of the element to add
112      * @param updateLayout True if layout attributes should be set
113      * @return The new {@link UiElementNode} or null.
114      */
addElement(UiElementNode uiParent, UiElementNode uiSibling, ElementDescriptor descriptor, boolean updateLayout)115     public UiElementNode addElement(UiElementNode uiParent,
116             UiElementNode uiSibling,
117             ElementDescriptor descriptor,
118             boolean updateLayout) {
119         if (uiParent instanceof UiDocumentNode && uiParent.getUiChildren().size() > 0) {
120             uiParent = uiParent.getUiChildren().get(0);
121         }
122         if (uiSibling != null && uiSibling.getUiParent() != uiParent) {
123             uiSibling = null;
124         }
125 
126         UiElementNode uiNew = addNewTreeElement(uiParent, uiSibling, descriptor, updateLayout);
127         selectUiNode(uiNew);
128 
129         return uiNew;
130     }
131 
132     /**
133      * Called when the "Remove" button is selected.
134      *
135      * If the tree has a selection, remove it.
136      * This simply deletes the XML node attached to the UI node: when the XML model fires the
137      * update event, the tree will get refreshed.
138      */
doRemove(final List<UiElementNode> nodes, Shell shell)139     public void doRemove(final List<UiElementNode> nodes, Shell shell) {
140 
141         if (nodes == null || nodes.size() == 0) {
142             return;
143         }
144 
145         final int len = nodes.size();
146 
147         StringBuilder sb = new StringBuilder();
148         for (UiElementNode node : nodes) {
149             sb.append("\n- "); //$NON-NLS-1$
150             sb.append(node.getBreadcrumbTrailDescription(false /* include_root */));
151         }
152 
153         if (MessageDialog.openQuestion(shell,
154                 len > 1 ? "Remove elements from Android XML"  // title
155                         : "Remove element from Android XML",
156                 String.format("Do you really want to remove %1$s?", sb.toString()))) {
157             commitPendingXmlChanges();
158             getRootNode().getEditor().editXmlModel(new Runnable() {
159                 public void run() {
160                     UiElementNode previous = null;
161                     UiElementNode parent = null;
162 
163                     for (int i = len - 1; i >= 0; i--) {
164                         UiElementNode node = nodes.get(i);
165                         previous = node.getUiPreviousSibling();
166                         parent = node.getUiParent();
167 
168                         // delete node
169                         node.deleteXmlNode();
170                     }
171 
172                     // try to select the last previous sibling or the last parent
173                     if (previous != null) {
174                         selectUiNode(previous);
175                     } else if (parent != null) {
176                         selectUiNode(parent);
177                     }
178                 }
179             });
180         }
181     }
182 
183     /**
184      * Called when the "Up" button is selected.
185      * <p/>
186      * If the tree has a selection, move it up, either in the child list or as the last child
187      * of the previous parent.
188      */
doUp(final List<UiElementNode> nodes)189     public void doUp(final List<UiElementNode> nodes) {
190         if (nodes == null || nodes.size() < 1) {
191             return;
192         }
193 
194         final Node[] select_xml_node = { null };
195         UiElementNode last_node = null;
196         UiElementNode search_root = null;
197 
198         for (int i = 0; i < nodes.size(); i++) {
199             final UiElementNode node = last_node = nodes.get(i);
200 
201             // the node will move either up to its parent or grand-parent
202             search_root = node.getUiParent();
203             if (search_root != null && search_root.getUiParent() != null) {
204                 search_root = search_root.getUiParent();
205             }
206 
207             commitPendingXmlChanges();
208             getRootNode().getEditor().editXmlModel(new Runnable() {
209                 public void run() {
210                     Node xml_node = node.getXmlNode();
211                     if (xml_node != null) {
212                         Node xml_parent = xml_node.getParentNode();
213                         if (xml_parent != null) {
214                             UiElementNode ui_prev = node.getUiPreviousSibling();
215                             if (ui_prev != null && ui_prev.getXmlNode() != null) {
216                                 // This node is not the first one of the parent, so it can be
217                                 // removed and then inserted before its previous sibling.
218                                 // If the previous sibling can have children, though, then it
219                                 // is inserted at the end of the children list.
220                                 Node xml_prev = ui_prev.getXmlNode();
221                                 if (ui_prev.getDescriptor().hasChildren()) {
222                                     xml_prev.appendChild(xml_parent.removeChild(xml_node));
223                                     select_xml_node[0] = xml_node;
224                                 } else {
225                                     xml_parent.insertBefore(
226                                             xml_parent.removeChild(xml_node),
227                                             xml_prev);
228                                     select_xml_node[0] = xml_node;
229                                 }
230                             } else if (!(xml_parent instanceof Document) &&
231                                     xml_parent.getParentNode() != null &&
232                                     !(xml_parent.getParentNode() instanceof Document)) {
233                                 // If the node is the first one of the child list of its
234                                 // parent, move it up in the hierarchy as previous sibling
235                                 // to the parent. This is only possible if the parent of the
236                                 // parent is not a document.
237                                 Node grand_parent = xml_parent.getParentNode();
238                                 grand_parent.insertBefore(xml_parent.removeChild(xml_node),
239                                         xml_parent);
240                                 select_xml_node[0] = xml_node;
241                             }
242                         }
243                     }
244                 }
245             });
246         }
247 
248         if (select_xml_node[0] == null) {
249             // The XML node has not been moved, we can just select the same UI node
250             selectUiNode(last_node);
251         } else {
252             // The XML node has moved. At this point the UI model has been reloaded
253             // and the XML node has been affected to a new UI node. Find that new UI
254             // node and select it.
255             if (search_root == null) {
256                 search_root = last_node.getUiRoot();
257             }
258             if (search_root != null) {
259                 selectUiNode(search_root.findXmlNode(select_xml_node[0]));
260             }
261         }
262     }
263 
264     /**
265      * Called when the "Down" button is selected.
266      *
267      * If the tree has a selection, move it down, either in the same child list or as the
268      * first child of the next parent.
269      */
doDown(final List<UiElementNode> nodes)270     public void doDown(final List<UiElementNode> nodes) {
271         if (nodes == null || nodes.size() < 1) {
272             return;
273         }
274 
275         final Node[] select_xml_node = { null };
276         UiElementNode last_node = null;
277         UiElementNode search_root = null;
278 
279         for (int i = nodes.size() - 1; i >= 0; i--) {
280             final UiElementNode node = last_node = nodes.get(i);
281             // the node will move either down to its parent or grand-parent
282             search_root = node.getUiParent();
283             if (search_root != null && search_root.getUiParent() != null) {
284                 search_root = search_root.getUiParent();
285             }
286 
287             commitPendingXmlChanges();
288             getRootNode().getEditor().editXmlModel(new Runnable() {
289                 public void run() {
290                     Node xml_node = node.getXmlNode();
291                     if (xml_node != null) {
292                         Node xml_parent = xml_node.getParentNode();
293                         if (xml_parent != null) {
294                             UiElementNode uiNext = node.getUiNextSibling();
295                             if (uiNext != null && uiNext.getXmlNode() != null) {
296                                 // This node is not the last one of the parent, so it can be
297                                 // removed and then inserted after its next sibling.
298                                 // If the next sibling is a node that can have children, though,
299                                 // then the node is inserted as the first child.
300                                 Node xml_next = uiNext.getXmlNode();
301                                 if (uiNext.getDescriptor().hasChildren()) {
302                                     // Note: insertBefore works as append if the ref node is
303                                     // null, i.e. when the node doesn't have children yet.
304                                     xml_next.insertBefore(xml_parent.removeChild(xml_node),
305                                             xml_next.getFirstChild());
306                                     select_xml_node[0] = xml_node;
307                                 } else {
308                                     // Insert "before after next" ;-)
309                                     xml_parent.insertBefore(xml_parent.removeChild(xml_node),
310                                             xml_next.getNextSibling());
311                                     select_xml_node[0] = xml_node;
312                                 }
313                             } else if (!(xml_parent instanceof Document) &&
314                                     xml_parent.getParentNode() != null &&
315                                     !(xml_parent.getParentNode() instanceof Document)) {
316                                 // This node is the last node of its parent.
317                                 // If neither the parent nor the grandparent is a document,
318                                 // then the node can be insert right after the parent.
319                                 Node grand_parent = xml_parent.getParentNode();
320                                 grand_parent.insertBefore(xml_parent.removeChild(xml_node),
321                                         xml_parent.getNextSibling());
322                                 select_xml_node[0] = xml_node;
323                             }
324                         }
325                     }
326                 }
327             });
328         }
329 
330         if (select_xml_node[0] == null) {
331             // The XML node has not been moved, we can just select the same UI node
332             selectUiNode(last_node);
333         } else {
334             // The XML node has moved. At this point the UI model has been reloaded
335             // and the XML node has been affected to a new UI node. Find that new UI
336             // node and select it.
337             if (search_root == null) {
338                 search_root = last_node.getUiRoot();
339             }
340             if (search_root != null) {
341                 selectUiNode(search_root.findXmlNode(select_xml_node[0]));
342             }
343         }
344     }
345 
346     //---------------------
347 
348     /**
349      * Adds a new element of the given descriptor's type to the given UI parent node.
350      *
351      * This actually creates the corresponding XML node in the XML model, which in turn
352      * will refresh the current tree view.
353      *
354      * @param uiParent An existing UI node or null to add to the tree root
355      * @param uiSibling An existing UI node to insert right before. Can be null.
356      * @param descriptor The descriptor of the element to add
357      * @param updateLayout True if layout attributes should be set
358      * @return The {@link UiElementNode} that has been added to the UI tree.
359      */
addNewTreeElement(UiElementNode uiParent, final UiElementNode uiSibling, ElementDescriptor descriptor, final boolean updateLayout)360     private UiElementNode addNewTreeElement(UiElementNode uiParent,
361             final UiElementNode uiSibling,
362             ElementDescriptor descriptor,
363             final boolean updateLayout) {
364         commitPendingXmlChanges();
365 
366         int index = 0;
367         for (UiElementNode uiChild : uiParent.getUiChildren()) {
368             if (uiChild == uiSibling) {
369                 break;
370             }
371             index++;
372         }
373 
374         final UiElementNode uiNew = uiParent.insertNewUiChild(index, descriptor);
375         UiElementNode rootNode = getRootNode();
376 
377         rootNode.getEditor().editXmlModel(new Runnable() {
378             public void run() {
379                 DescriptorsUtils.setDefaultLayoutAttributes(uiNew, updateLayout);
380                 Node xmlNode = uiNew.createXmlNode();
381             }
382         });
383         return uiNew;
384     }
385 }
386