2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.ide.eclipse.adt.internal.editors.manifest.pages;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
21 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
22 import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
23 import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
25 import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
26 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
28 import org.eclipse.swt.SWT;
29 import org.eclipse.swt.events.SelectionAdapter;
30 import org.eclipse.swt.events.SelectionEvent;
31 import org.eclipse.swt.widgets.Button;
32 import org.eclipse.swt.widgets.Composite;
33 import org.eclipse.ui.forms.IManagedForm;
34 import org.eclipse.ui.forms.widgets.FormText;
35 import org.eclipse.ui.forms.widgets.FormToolkit;
36 import org.eclipse.ui.forms.widgets.Section;
37 import org.eclipse.ui.forms.widgets.TableWrapData;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Node;
40 import org.w3c.dom.Text;
43 * Appllication Toogle section part for application page.
45 final class ApplicationToggle extends UiElementPart {
47 /** Checkbox indicating whether an application node is present */
48 private Button mCheckbox;
49 /** Listen to changes to the UI node for <application> and updates the checkbox */
50 private AppNodeUpdateListener mAppNodeUpdateListener;
51 /** Internal flag to know where we're programmatically modifying the checkbox and we want to
52 * avoid triggering the checkbox's callback. */
53 public boolean mInternalModification;
54 private FormText mTooltipFormText;
56 public ApplicationToggle(Composite body, FormToolkit toolkit, ManifestEditor editor,
57 UiElementNode applicationUiNode) {
58 super(body, toolkit, editor, applicationUiNode,
60 null, /* description */
61 Section.TWISTIE | Section.EXPANDED);
65 public void dispose() {
67 if (getUiElementNode() != null && mAppNodeUpdateListener != null) {
68 getUiElementNode().removeUpdateListener(mAppNodeUpdateListener);
69 mAppNodeUpdateListener = null;
74 * Changes and refreshes the Application UI node handle by the this part.
77 public void setUiElementNode(UiElementNode uiElementNode) {
78 super.setUiElementNode(uiElementNode);
82 // Set the state of the checkbox
83 mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
84 UiUpdateState.CHILDREN_CHANGED);
88 * Create the controls to edit the attributes for the given ElementDescriptor.
90 * This MUST not be called by the constructor. Instead it must be called from
91 * <code>initialize</code> (i.e. right after the form part is added to the managed form.)
93 * @param managedForm The owner managed form
96 protected void createFormControls(IManagedForm managedForm) {
97 FormToolkit toolkit = managedForm.getToolkit();
98 Composite table = createTableLayout(toolkit, 1 /* numColumns */);
100 mTooltipFormText = createFormText(table, toolkit, true, "<form></form>",
101 false /* setupLayoutData */);
104 mCheckbox = toolkit.createButton(table,
105 "Define an <application> tag in the AndroidManifest.xml",
107 mCheckbox.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP));
108 mCheckbox.setSelection(false);
109 mCheckbox.addSelectionListener(new CheckboxSelectionListener());
111 mAppNodeUpdateListener = new AppNodeUpdateListener();
112 getUiElementNode().addUpdateListener(mAppNodeUpdateListener);
114 // Initialize the state of the checkbox
115 mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
116 UiUpdateState.CHILDREN_CHANGED);
118 // Tell the section that the layout has changed.
123 * Updates the application tooltip in the form text.
124 * If there is no tooltip, the form text is hidden.
126 private void updateTooltip() {
127 boolean isVisible = false;
129 String tooltip = getUiElementNode().getDescriptor().getTooltip();
130 if (tooltip != null) {
131 tooltip = DescriptorsUtils.formatFormText(tooltip,
132 getUiElementNode().getDescriptor(),
133 Sdk.getCurrent().getDocumentationBaseUrl());
135 mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */);
136 mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, AdtPlugin.getAndroidLogo());
137 mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener());
141 mTooltipFormText.setVisible(isVisible);
145 * This listener synchronizes the XML application node when the checkbox
146 * is changed by the user.
148 private class CheckboxSelectionListener extends SelectionAdapter {
149 private Node mUndoXmlNode;
150 private Node mUndoXmlParent;
151 private Node mUndoXmlNextNode;
152 private Node mUndoXmlNextElement;
153 private Document mUndoXmlDocument;
156 public void widgetSelected(SelectionEvent e) {
157 super.widgetSelected(e);
158 if (!mInternalModification && getUiElementNode() != null) {
159 getUiElementNode().getEditor().wrapUndoRecording(
160 mCheckbox.getSelection()
161 ? "Create or restore Application node"
162 : "Remove Application node",
165 getUiElementNode().getEditor().editXmlModel(new Runnable() {
167 if (mCheckbox.getSelection()) {
168 // The user wants an <application> node.
169 // Either restore a previous one
170 // or create a full new one.
171 boolean create = true;
172 if (mUndoXmlNode != null) {
173 create = !restoreApplicationNode();
176 getUiElementNode().createXmlNode();
179 // Users no longer wants the <application> node.
180 removeApplicationNode();
190 * Restore a previously "saved" application node.
192 * @return True if the node could be restored, false otherwise.
194 private boolean restoreApplicationNode() {
195 if (mUndoXmlDocument == null || mUndoXmlNode == null) {
199 // Validate node references...
200 mUndoXmlParent = validateNode(mUndoXmlDocument, mUndoXmlParent);
201 mUndoXmlNextNode = validateNode(mUndoXmlDocument, mUndoXmlNextNode);
202 mUndoXmlNextElement = validateNode(mUndoXmlDocument, mUndoXmlNextElement);
204 if (mUndoXmlParent == null){
205 // If the parent node doesn't exist, try to find a new manifest node.
206 // If it doesn't exist, create it.
207 mUndoXmlParent = getUiElementNode().getUiParent().prepareCommit();
208 mUndoXmlNextNode = null;
209 mUndoXmlNextElement = null;
212 boolean success = false;
213 if (mUndoXmlParent != null) {
214 // If the parent is still around, reuse the same node.
216 // Ideally we want to insert the node before what used to be its next sibling.
217 // If that's not possible, we try to insert it before its next sibling element.
218 // If that's not possible either, it will be inserted at the end of the parent's.
219 Node next = mUndoXmlNextNode;
221 next = mUndoXmlNextElement;
223 mUndoXmlParent.insertBefore(mUndoXmlNode, next);
225 Text sep = mUndoXmlDocument.createTextNode("\n"); //$NON-NLS-1$
226 mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag
231 // Remove internal references to avoid using them twice
232 mUndoXmlParent = null;
233 mUndoXmlNextNode = null;
234 mUndoXmlNextElement = null;
236 mUndoXmlDocument = null;
241 * Validates that the given xml_node is still either the root node or one of its
242 * direct descendants.
244 * @param root_node The root of the node hierarchy to examine.
245 * @param xml_node The XML node to find.
246 * @return Returns xml_node if it is, otherwise returns null.
248 private Node validateNode(Node root_node, Node xml_node) {
249 if (root_node == xml_node) {
252 for (Node node = root_node.getFirstChild(); node != null;
253 node = node.getNextSibling()) {
254 if (root_node == xml_node || validateNode(node, xml_node) != null) {
263 * Removes the <application> node from the hierarchy.
264 * Before doing that, we try to remember where it was so that we can put it back
267 private void removeApplicationNode() {
268 // Make sure the node actually exists...
269 Node xml_node = getUiElementNode().getXmlNode();
270 if (xml_node == null) {
274 // Save its parent, next sibling and next element
275 mUndoXmlDocument = xml_node.getOwnerDocument();
276 mUndoXmlParent = xml_node.getParentNode();
277 mUndoXmlNextNode = xml_node.getNextSibling();
278 mUndoXmlNextElement = mUndoXmlNextNode;
279 while (mUndoXmlNextElement != null &&
280 mUndoXmlNextElement.getNodeType() != Node.ELEMENT_NODE) {
281 mUndoXmlNextElement = mUndoXmlNextElement.getNextSibling();
284 // Actually remove the node from the hierarchy and keep it here.
285 // The returned node looses its parents/siblings pointers.
286 mUndoXmlNode = getUiElementNode().deleteXmlNode();
291 * This listener synchronizes the UI (i.e. the checkbox) with the
292 * actual presence of the application XML node.
294 private class AppNodeUpdateListener implements IUiUpdateListener {
295 public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
296 // The UiElementNode for the application XML node always exists, even
297 // if there is no corresponding XML node in the XML file.
299 // To update the checkbox to reflect the actual state, we just need
300 // to check if the XML node is null.
302 mInternalModification = true;
303 boolean exists = ui_node.getXmlNode() != null;
304 if (mCheckbox.getSelection() != exists) {
305 mCheckbox.setSelection(exists);
308 mInternalModification = false;