Home | History | Annotate | Download | only in pages
      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.IUiUpdateListener.UiUpdateState;
     25 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     26 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     27 import com.android.utils.SdkUtils;
     28 
     29 import org.eclipse.swt.SWT;
     30 import org.eclipse.swt.events.SelectionAdapter;
     31 import org.eclipse.swt.events.SelectionEvent;
     32 import org.eclipse.swt.widgets.Button;
     33 import org.eclipse.swt.widgets.Composite;
     34 import org.eclipse.ui.forms.IManagedForm;
     35 import org.eclipse.ui.forms.widgets.FormText;
     36 import org.eclipse.ui.forms.widgets.FormToolkit;
     37 import org.eclipse.ui.forms.widgets.Section;
     38 import org.eclipse.ui.forms.widgets.TableWrapData;
     39 import org.w3c.dom.Document;
     40 import org.w3c.dom.Node;
     41 import org.w3c.dom.Text;
     42 
     43 /**
     44  * Appllication Toogle section part for application page.
     45  */
     46 final class ApplicationToggle extends UiElementPart {
     47 
     48     /** Checkbox indicating whether an application node is present */
     49     private Button mCheckbox;
     50     /** Listen to changes to the UI node for <application> and updates the checkbox */
     51     private AppNodeUpdateListener mAppNodeUpdateListener;
     52     /** Internal flag to know where we're programmatically modifying the checkbox and we want to
     53      *  avoid triggering the checkbox's callback. */
     54     public boolean mInternalModification;
     55     private FormText mTooltipFormText;
     56 
     57     public ApplicationToggle(Composite body, FormToolkit toolkit, ManifestEditor editor,
     58             UiElementNode applicationUiNode) {
     59         super(body, toolkit, editor, applicationUiNode,
     60                 "Application Toggle",
     61                 null, /* description */
     62                 Section.TWISTIE | Section.EXPANDED);
     63     }
     64 
     65     @Override
     66     public void dispose() {
     67         super.dispose();
     68         if (getUiElementNode() != null && mAppNodeUpdateListener != null) {
     69             getUiElementNode().removeUpdateListener(mAppNodeUpdateListener);
     70             mAppNodeUpdateListener = null;
     71         }
     72     }
     73 
     74     /**
     75      * Changes and refreshes the Application UI node handle by the this part.
     76      */
     77     @Override
     78     public void setUiElementNode(UiElementNode uiElementNode) {
     79         super.setUiElementNode(uiElementNode);
     80 
     81         updateTooltip();
     82 
     83         // Set the state of the checkbox
     84         mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
     85                 UiUpdateState.CHILDREN_CHANGED);
     86     }
     87 
     88     /**
     89      * Create the controls to edit the attributes for the given ElementDescriptor.
     90      * <p/>
     91      * This MUST not be called by the constructor. Instead it must be called from
     92      * <code>initialize</code> (i.e. right after the form part is added to the managed form.)
     93      *
     94      * @param managedForm The owner managed form
     95      */
     96     @Override
     97     protected void createFormControls(IManagedForm managedForm) {
     98         FormToolkit toolkit = managedForm.getToolkit();
     99         Composite table = createTableLayout(toolkit, 1 /* numColumns */);
    100 
    101         mTooltipFormText = createFormText(table, toolkit, true, "<form></form>",
    102                 false /* setupLayoutData */);
    103         updateTooltip();
    104 
    105         mCheckbox = toolkit.createButton(table,
    106                 "Define an <application> tag in the AndroidManifest.xml",
    107                 SWT.CHECK);
    108         mCheckbox.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP));
    109         mCheckbox.setSelection(false);
    110         mCheckbox.addSelectionListener(new CheckboxSelectionListener());
    111 
    112         mAppNodeUpdateListener = new AppNodeUpdateListener();
    113         getUiElementNode().addUpdateListener(mAppNodeUpdateListener);
    114 
    115         // Initialize the state of the checkbox
    116         mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
    117                 UiUpdateState.CHILDREN_CHANGED);
    118 
    119         // Tell the section that the layout has changed.
    120         layoutChanged();
    121     }
    122 
    123     /**
    124      * Updates the application tooltip in the form text.
    125      * If there is no tooltip, the form text is hidden.
    126      */
    127     private void updateTooltip() {
    128         boolean isVisible = false;
    129 
    130         String tooltip = getUiElementNode().getDescriptor().getTooltip();
    131         if (tooltip != null) {
    132             tooltip = DescriptorsUtils.formatFormText(tooltip,
    133                     getUiElementNode().getDescriptor(),
    134                     Sdk.getCurrent().getDocumentationBaseUrl());
    135 
    136             mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */);
    137             mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, AdtPlugin.getAndroidLogo());
    138             mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener());
    139             isVisible = true;
    140         }
    141 
    142         mTooltipFormText.setVisible(isVisible);
    143     }
    144 
    145     /**
    146      * This listener synchronizes the XML application node when the checkbox
    147      * is changed by the user.
    148      */
    149     private class CheckboxSelectionListener extends SelectionAdapter {
    150         private Node mUndoXmlNode;
    151         private Node mUndoXmlParent;
    152         private Node mUndoXmlNextNode;
    153         private Node mUndoXmlNextElement;
    154         private Document mUndoXmlDocument;
    155 
    156         @Override
    157         public void widgetSelected(SelectionEvent e) {
    158             super.widgetSelected(e);
    159             if (!mInternalModification && getUiElementNode() != null) {
    160                 getUiElementNode().getEditor().wrapUndoEditXmlModel(
    161                         mCheckbox.getSelection()
    162                             ? "Create or restore Application node"
    163                             : "Remove Application node",
    164                         new Runnable() {
    165                             @Override
    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          * Restore a previously "saved" application node.
    189          *
    190          * @return True if the node could be restored, false otherwise.
    191          */
    192         private boolean restoreApplicationNode() {
    193             if (mUndoXmlDocument == null || mUndoXmlNode == null) {
    194                 return false;
    195             }
    196 
    197             // Validate node references...
    198             mUndoXmlParent = validateNode(mUndoXmlDocument, mUndoXmlParent);
    199             mUndoXmlNextNode = validateNode(mUndoXmlDocument, mUndoXmlNextNode);
    200             mUndoXmlNextElement = validateNode(mUndoXmlDocument, mUndoXmlNextElement);
    201 
    202             if (mUndoXmlParent == null){
    203                 // If the parent node doesn't exist, try to find a new manifest node.
    204                 // If it doesn't exist, create it.
    205                 mUndoXmlParent = getUiElementNode().getUiParent().prepareCommit();
    206                 mUndoXmlNextNode = null;
    207                 mUndoXmlNextElement = null;
    208             }
    209 
    210             boolean success = false;
    211             if (mUndoXmlParent != null) {
    212                 // If the parent is still around, reuse the same node.
    213 
    214                 // Ideally we want to insert the node before what used to be its next sibling.
    215                 // If that's not possible, we try to insert it before its next sibling element.
    216                 // If that's not possible either, it will be inserted at the end of the parent's.
    217                 Node next = mUndoXmlNextNode;
    218                 if (next == null) {
    219                     next = mUndoXmlNextElement;
    220                 }
    221                 mUndoXmlParent.insertBefore(mUndoXmlNode, next);
    222                 if (next == null) {
    223                     Text sep = mUndoXmlDocument.createTextNode(SdkUtils.getLineSeparator());
    224                     mUndoXmlParent.insertBefore(sep, null);  // insert separator before end tag
    225                 }
    226                 success = true;
    227             }
    228 
    229             // Remove internal references to avoid using them twice
    230             mUndoXmlParent = null;
    231             mUndoXmlNextNode = null;
    232             mUndoXmlNextElement = null;
    233             mUndoXmlNode = null;
    234             mUndoXmlDocument = null;
    235             return success;
    236         }
    237 
    238         /**
    239          * Validates that the given xml_node is still either the root node or one of its
    240          * direct descendants.
    241          *
    242          * @param root_node The root of the node hierarchy to examine.
    243          * @param xml_node The XML node to find.
    244          * @return Returns xml_node if it is, otherwise returns null.
    245          */
    246         private Node validateNode(Node root_node, Node xml_node) {
    247             if (root_node == xml_node) {
    248                 return xml_node;
    249             } else {
    250                 for (Node node = root_node.getFirstChild(); node != null;
    251                         node = node.getNextSibling()) {
    252                     if (root_node == xml_node || validateNode(node, xml_node) != null) {
    253                         return xml_node;
    254                     }
    255                 }
    256             }
    257             return null;
    258         }
    259 
    260         /**
    261          * Removes the <application> node from the hierarchy.
    262          * Before doing that, we try to remember where it was so that we can put it back
    263          * in the same place.
    264          */
    265         private void removeApplicationNode() {
    266             // Make sure the node actually exists...
    267             Node xml_node = getUiElementNode().getXmlNode();
    268             if (xml_node == null) {
    269                 return;
    270             }
    271 
    272             // Save its parent, next sibling and next element
    273             mUndoXmlDocument = xml_node.getOwnerDocument();
    274             mUndoXmlParent = xml_node.getParentNode();
    275             mUndoXmlNextNode = xml_node.getNextSibling();
    276             mUndoXmlNextElement = mUndoXmlNextNode;
    277             while (mUndoXmlNextElement != null &&
    278                     mUndoXmlNextElement.getNodeType() != Node.ELEMENT_NODE) {
    279                 mUndoXmlNextElement = mUndoXmlNextElement.getNextSibling();
    280             }
    281 
    282             // Actually remove the node from the hierarchy and keep it here.
    283             // The returned node looses its parents/siblings pointers.
    284             mUndoXmlNode = getUiElementNode().deleteXmlNode();
    285         }
    286     }
    287 
    288     /**
    289      * This listener synchronizes the UI (i.e. the checkbox) with the
    290      * actual presence of the application XML node.
    291      */
    292     private class AppNodeUpdateListener implements IUiUpdateListener {
    293         @Override
    294         public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
    295             // The UiElementNode for the application XML node always exists, even
    296             // if there is no corresponding XML node in the XML file.
    297             //
    298             // To update the checkbox to reflect the actual state, we just need
    299             // to check if the XML node is null.
    300             try {
    301                 mInternalModification = true;
    302                 boolean exists = ui_node.getXmlNode() != null;
    303                 if (mCheckbox.getSelection() != exists) {
    304                     mCheckbox.setSelection(exists);
    305                 }
    306             } finally {
    307                 mInternalModification = false;
    308             }
    309 
    310         }
    311     }
    312 }
    313