OSDN Git Service

android-2.1_r1 snapshot
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / manifest / pages / ApplicationToggle.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
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
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
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 package com.android.ide.eclipse.adt.internal.editors.manifest.pages;
18
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;
27
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;
41
42 /**
43  * Appllication Toogle section part for application page.
44  */
45 final class ApplicationToggle extends UiElementPart {
46     
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;
55
56     public ApplicationToggle(Composite body, FormToolkit toolkit, ManifestEditor editor,
57             UiElementNode applicationUiNode) {
58         super(body, toolkit, editor, applicationUiNode,
59                 "Application Toggle",
60                 null, /* description */
61                 Section.TWISTIE | Section.EXPANDED);
62     }
63     
64     @Override
65     public void dispose() {
66         super.dispose();
67         if (getUiElementNode() != null && mAppNodeUpdateListener != null) {
68             getUiElementNode().removeUpdateListener(mAppNodeUpdateListener);
69             mAppNodeUpdateListener = null;
70         }
71     }
72     
73     /**
74      * Changes and refreshes the Application UI node handle by the this part.
75      */
76     @Override
77     public void setUiElementNode(UiElementNode uiElementNode) {
78         super.setUiElementNode(uiElementNode);
79
80         updateTooltip();
81
82         // Set the state of the checkbox
83         mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
84                 UiUpdateState.CHILDREN_CHANGED);
85     }
86
87     /**
88      * Create the controls to edit the attributes for the given ElementDescriptor.
89      * <p/>
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.)
92      * 
93      * @param managedForm The owner managed form
94      */
95     @Override
96     protected void createFormControls(IManagedForm managedForm) {
97         FormToolkit toolkit = managedForm.getToolkit();
98         Composite table = createTableLayout(toolkit, 1 /* numColumns */);
99
100         mTooltipFormText = createFormText(table, toolkit, true, "<form></form>",
101                 false /* setupLayoutData */);
102         updateTooltip();
103
104         mCheckbox = toolkit.createButton(table,
105                 "Define an <application> tag in the AndroidManifest.xml",
106                 SWT.CHECK);
107         mCheckbox.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP));
108         mCheckbox.setSelection(false);
109         mCheckbox.addSelectionListener(new CheckboxSelectionListener());
110
111         mAppNodeUpdateListener = new AppNodeUpdateListener();
112         getUiElementNode().addUpdateListener(mAppNodeUpdateListener);
113
114         // Initialize the state of the checkbox
115         mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
116                 UiUpdateState.CHILDREN_CHANGED);
117         
118         // Tell the section that the layout has changed.
119         layoutChanged();
120     }
121
122     /**
123      * Updates the application tooltip in the form text.
124      * If there is no tooltip, the form text is hidden. 
125      */
126     private void updateTooltip() {
127         boolean isVisible = false;
128
129         String tooltip = getUiElementNode().getDescriptor().getTooltip();
130         if (tooltip != null) {
131             tooltip = DescriptorsUtils.formatFormText(tooltip,
132                     getUiElementNode().getDescriptor(),
133                     Sdk.getCurrent().getDocumentationBaseUrl());
134     
135             mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */);
136             mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, AdtPlugin.getAndroidLogo());
137             mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener());
138             isVisible = true;
139         }
140         
141         mTooltipFormText.setVisible(isVisible);
142     }
143
144     /**
145      * This listener synchronizes the XML application node when the checkbox
146      * is changed by the user.
147      */
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;
154
155         @Override
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",
163                         new Runnable() {
164                             public void run() {
165                                 getUiElementNode().getEditor().editXmlModel(new Runnable() {
166                                     public void run() {
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();
174                                             }
175                                             if (create) {
176                                                 getUiElementNode().createXmlNode();
177                                             }
178                                         } else {
179                                             // Users no longer wants the <application> node.
180                                             removeApplicationNode();
181                                         }
182                                     }
183                                 });
184                             }
185                 });
186             }
187         }
188
189         /**
190          * Restore a previously "saved" application node.
191          * 
192          * @return True if the node could be restored, false otherwise.
193          */
194         private boolean restoreApplicationNode() {
195             if (mUndoXmlDocument == null || mUndoXmlNode == null) {
196                 return false;
197             }
198
199             // Validate node references...
200             mUndoXmlParent = validateNode(mUndoXmlDocument, mUndoXmlParent);
201             mUndoXmlNextNode = validateNode(mUndoXmlDocument, mUndoXmlNextNode);
202             mUndoXmlNextElement = validateNode(mUndoXmlDocument, mUndoXmlNextElement);
203
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;
210             }
211
212             boolean success = false;
213             if (mUndoXmlParent != null) {
214                 // If the parent is still around, reuse the same node.
215
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;
220                 if (next == null) {
221                     next = mUndoXmlNextElement;
222                 }
223                 mUndoXmlParent.insertBefore(mUndoXmlNode, next);
224                 if (next == null) {
225                     Text sep = mUndoXmlDocument.createTextNode("\n");  //$NON-NLS-1$
226                     mUndoXmlParent.insertBefore(sep, null);  // insert separator before end tag
227                 }
228                 success = true;
229             } 
230
231             // Remove internal references to avoid using them twice
232             mUndoXmlParent = null;
233             mUndoXmlNextNode = null;
234             mUndoXmlNextElement = null;
235             mUndoXmlNode = null;
236             mUndoXmlDocument = null;
237             return success;
238         }
239
240         /**
241          * Validates that the given xml_node is still either the root node or one of its
242          * direct descendants. 
243          * 
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.
247          */
248         private Node validateNode(Node root_node, Node xml_node) {
249             if (root_node == xml_node) {
250                 return xml_node;
251             } else {
252                 for (Node node = root_node.getFirstChild(); node != null;
253                         node = node.getNextSibling()) {
254                     if (root_node == xml_node || validateNode(node, xml_node) != null) {
255                         return xml_node;
256                     }
257                 }
258             }
259             return null;
260         }
261
262         /**
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
265          * in the same place.
266          */
267         private void removeApplicationNode() {
268             // Make sure the node actually exists...
269             Node xml_node = getUiElementNode().getXmlNode();
270             if (xml_node == null) {
271                 return;
272             }
273
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();
282             }
283
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();
287         }
288     }
289
290     /**
291      * This listener synchronizes the UI (i.e. the checkbox) with the
292      * actual presence of the application XML node.
293      */
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.
298             //
299             // To update the checkbox to reflect the actual state, we just need
300             // to check if the XML node is null.
301             try {
302                 mInternalModification = true;
303                 boolean exists = ui_node.getXmlNode() != null;
304                 if (mCheckbox.getSelection() != exists) {
305                     mCheckbox.setSelection(exists);
306                 }
307             } finally {
308                 mInternalModification = false;
309             }
310             
311         }
312     }
313 }