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.ui;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
21 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
22 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
23 import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSectionPart;
24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
25 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
26 
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.swt.widgets.Composite;
29 import org.eclipse.swt.widgets.Control;
30 import org.eclipse.ui.forms.IManagedForm;
31 import org.eclipse.ui.forms.widgets.FormToolkit;
32 import org.eclipse.ui.forms.widgets.Section;
33 
34 /**
35  * Generic page's section part that displays all attributes of a given {@link UiElementNode}.
36  * <p/>
37  * This part is designed to be displayed in a page that has a table column layout.
38  * It is linked to a specific {@link UiElementNode} and automatically displays all of its
39  * attributes, manages its dirty state and commits the attributes when necessary.
40  * <p/>
41  * No derivation is needed unless the UI or workflow needs to be extended.
42  */
43 public class UiElementPart extends ManifestSectionPart {
44 
45     /** A reference to the container editor */
46     private ManifestEditor mEditor;
47     /** The {@link UiElementNode} manipulated by this SectionPart. It can be null. */
48     private UiElementNode mUiElementNode;
49     /** Table that contains all the attributes */
50     private Composite mTable;
51 
UiElementPart(Composite body, FormToolkit toolkit, ManifestEditor editor, UiElementNode uiElementNode, String sectionTitle, String sectionDescription, int extra_style)52     public UiElementPart(Composite body, FormToolkit toolkit, ManifestEditor editor,
53             UiElementNode uiElementNode, String sectionTitle, String sectionDescription,
54             int extra_style) {
55         super(body, toolkit, extra_style, sectionDescription != null);
56         mEditor = editor;
57         mUiElementNode = uiElementNode;
58         setupSection(sectionTitle, sectionDescription);
59 
60         if (uiElementNode == null) {
61             // This is serious and should never happen. Instead of crashing, simply abort.
62             // There will be no UI, which will prevent further damage.
63             AdtPlugin.log(IStatus.ERROR, "Missing node to edit!"); //$NON-NLS-1$
64             return;
65         }
66     }
67 
68     /**
69      * Returns the Editor associated with this part.
70      */
getEditor()71     public ManifestEditor getEditor() {
72         return mEditor;
73     }
74 
75     /**
76      * Returns the {@link UiElementNode} associated with this part.
77      */
getUiElementNode()78     public UiElementNode getUiElementNode() {
79         return mUiElementNode;
80     }
81 
82     /**
83      * Changes the element node handled by this part.
84      *
85      * @param uiElementNode The new element node for the part.
86      */
setUiElementNode(UiElementNode uiElementNode)87     public void setUiElementNode(UiElementNode uiElementNode) {
88         mUiElementNode = uiElementNode;
89     }
90 
91     /**
92      * Initializes the form part.
93      * <p/>
94      * This is called by the owning managed form as soon as the part is added to the form,
95      * which happens right after the part is actually created.
96      */
97     @Override
initialize(IManagedForm form)98     public void initialize(IManagedForm form) {
99         super.initialize(form);
100         createFormControls(form);
101     }
102 
103     /**
104      * Setup the section that contains this part.
105      * <p/>
106      * This is called by the constructor to set the section's title and description
107      * with parameters given in the constructor.
108      * <br/>
109      * Derived class override this if needed, however in most cases the default
110      * implementation should be enough.
111      *
112      * @param sectionTitle The section part's title
113      * @param sectionDescription The section part's description
114      */
setupSection(String sectionTitle, String sectionDescription)115     protected void setupSection(String sectionTitle, String sectionDescription) {
116         Section section = getSection();
117         section.setText(sectionTitle);
118         section.setDescription(sectionDescription);
119     }
120 
121     /**
122      * Create the controls to edit the attributes for the given ElementDescriptor.
123      * <p/>
124      * This MUST not be called by the constructor. Instead it must be called from
125      * <code>initialize</code> (i.e. right after the form part is added to the managed form.)
126      * <p/>
127      * Derived classes can override this if necessary.
128      *
129      * @param managedForm The owner managed form
130      */
createFormControls(IManagedForm managedForm)131     protected void createFormControls(IManagedForm managedForm) {
132         setTable(createTableLayout(managedForm.getToolkit(), 2 /* numColumns */));
133 
134         createUiAttributes(managedForm);
135     }
136 
137     /**
138      * Sets the table where the attribute UI needs to be created.
139      */
setTable(Composite table)140     protected void setTable(Composite table) {
141         mTable = table;
142     }
143 
144     /**
145      * Returns the table where the attribute UI needs to be created.
146      */
getTable()147     protected Composite getTable() {
148         return mTable;
149     }
150 
151     /**
152      * Add all the attribute UI widgets into the underlying table layout.
153      *
154      * @param managedForm The owner managed form
155      */
createUiAttributes(IManagedForm managedForm)156     protected void createUiAttributes(IManagedForm managedForm) {
157         Composite table = getTable();
158         if (table == null || managedForm == null) {
159             return;
160         }
161 
162         // Remove any old UI controls
163         for (Control c : table.getChildren()) {
164             c.dispose();
165         }
166 
167         fillTable(table, managedForm);
168 
169         // Tell the section that the layout has changed.
170         layoutChanged();
171     }
172 
173     /**
174      * Actually fills the table.
175      * This is called by {@link #createUiAttributes(IManagedForm)} to populate the new
176      * table. The default implementation is to use
177      * {@link #insertUiAttributes(UiElementNode, Composite, IManagedForm)} to actually
178      * place the attributes of the default {@link UiElementNode} in the table.
179      * <p/>
180      * Derived classes can override this to add controls in the table before or after.
181      *
182      * @param table The table to fill. It must have 2 columns.
183      * @param managedForm The managed form for new controls.
184      */
fillTable(Composite table, IManagedForm managedForm)185     protected void fillTable(Composite table, IManagedForm managedForm) {
186         int inserted = insertUiAttributes(mUiElementNode, table, managedForm);
187 
188         if (inserted == 0) {
189             createLabel(table, managedForm.getToolkit(),
190                     "No attributes to display, waiting for SDK to finish loading...",
191                     null /* tooltip */ );
192         }
193     }
194 
195     /**
196      * Insert the UI attributes of the given {@link UiElementNode} in the given table.
197      *
198      * @param uiNode The {@link UiElementNode} that contains the attributes to display.
199      *               Must not be null.
200      * @param table The table to fill. It must have 2 columns.
201      * @param managedForm The managed form for new controls.
202      * @return The number of UI attributes inserted. It is >= 0.
203      */
insertUiAttributes(UiElementNode uiNode, Composite table, IManagedForm managedForm)204     protected int insertUiAttributes(UiElementNode uiNode, Composite table, IManagedForm managedForm) {
205         if (uiNode == null || table == null || managedForm == null) {
206             return 0;
207         }
208 
209         // To iterate over all attributes, we use the {@link ElementDescriptor} instead
210         // of the {@link UiElementNode} because the attributes' order is guaranteed in the
211         // descriptor but not in the node itself.
212         AttributeDescriptor[] attr_desc_list = uiNode.getAttributeDescriptors();
213         for (AttributeDescriptor attr_desc : attr_desc_list) {
214             if (attr_desc instanceof XmlnsAttributeDescriptor) {
215                 // Do not show hidden attributes
216                 continue;
217             }
218 
219             UiAttributeNode ui_attr = uiNode.findUiAttribute(attr_desc);
220             if (ui_attr != null) {
221                 ui_attr.createUiControl(table, managedForm);
222             } else {
223                 // The XML has an extra attribute which wasn't declared in
224                 // AndroidManifestDescriptors. This is not a problem, we just ignore it.
225                 AdtPlugin.log(IStatus.WARNING,
226                         "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$
227                         attr_desc.getXmlLocalName(),
228                         uiNode.getDescriptor().getXmlName());
229             }
230         }
231         return attr_desc_list.length;
232     }
233 
234     /**
235      * Tests whether the part is dirty i.e. its widgets have state that is
236      * newer than the data in the model.
237      * <p/>
238      * This is done by iterating over all attributes and updating the super's
239      * internal dirty flag. Stop once at least one attribute is dirty.
240      *
241      * @return <code>true</code> if the part is dirty, <code>false</code>
242      *         otherwise.
243      */
244     @Override
isDirty()245     public boolean isDirty() {
246         if (mUiElementNode != null && !super.isDirty()) {
247             for (UiAttributeNode ui_attr : mUiElementNode.getAllUiAttributes()) {
248                 if (ui_attr.isDirty()) {
249                     markDirty();
250                     break;
251                 }
252             }
253         }
254         return super.isDirty();
255     }
256 
257     /**
258      * If part is displaying information loaded from a model, this method
259      * instructs it to commit the new (modified) data back into the model.
260      *
261      * @param onSave
262      *            indicates if commit is called during 'save' operation or for
263      *            some other reason (for example, if form is contained in a
264      *            wizard or a multi-page editor and the user is about to leave
265      *            the page).
266      */
267     @Override
commit(boolean onSave)268     public void commit(boolean onSave) {
269         if (mUiElementNode != null) {
270             mEditor.wrapEditXmlModel(new Runnable() {
271                 @Override
272                 public void run() {
273                     for (UiAttributeNode ui_attr : mUiElementNode.getAllUiAttributes()) {
274                         ui_attr.commit();
275                     }
276                 }
277             });
278         }
279 
280         // We need to call super's commit after we synchronized the nodes to make sure we
281         // reset the dirty flag after all the side effects from committing have occurred.
282         super.commit(onSave);
283     }
284 }
285