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