aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/UiElementPart.java
blob: db9fa069f6ce268b9e606aa359869452da1c9882 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.editors.ui;

import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSectionPart;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;

/**
 * Generic page's section part that displays all attributes of a given {@link UiElementNode}.
 * <p/>
 * This part is designed to be displayed in a page that has a table column layout.
 * It is linked to a specific {@link UiElementNode} and automatically displays all of its
 * attributes, manages its dirty state and commits the attributes when necessary.
 * <p/>
 * No derivation is needed unless the UI or workflow needs to be extended.
 */
public class UiElementPart extends ManifestSectionPart {

    /** A reference to the container editor */
    private ManifestEditor mEditor;
    /** The {@link UiElementNode} manipulated by this SectionPart. It can be null. */
    private UiElementNode mUiElementNode;
    /** Table that contains all the attributes */
    private Composite mTable;

    public UiElementPart(Composite body, FormToolkit toolkit, ManifestEditor editor,
            UiElementNode uiElementNode, String sectionTitle, String sectionDescription,
            int extra_style) {
        super(body, toolkit, extra_style, sectionDescription != null);
        mEditor = editor;
        mUiElementNode = uiElementNode;
        setupSection(sectionTitle, sectionDescription);

        if (uiElementNode == null) {
            // This is serious and should never happen. Instead of crashing, simply abort.
            // There will be no UI, which will prevent further damage.
            AdtPlugin.log(IStatus.ERROR, "Missing node to edit!"); //$NON-NLS-1$
            return;
        }
    }

    /**
     * Returns the Editor associated with this part.
     */
    public ManifestEditor getEditor() {
        return mEditor;
    }

    /**
     * Returns the {@link UiElementNode} associated with this part.
     */
    public UiElementNode getUiElementNode() {
        return mUiElementNode;
    }

    /**
     * Changes the element node handled by this part.
     *
     * @param uiElementNode The new element node for the part.
     */
    public void setUiElementNode(UiElementNode uiElementNode) {
        mUiElementNode = uiElementNode;
    }

    /**
     * Initializes the form part.
     * <p/>
     * This is called by the owning managed form as soon as the part is added to the form,
     * which happens right after the part is actually created.
     */
    @Override
    public void initialize(IManagedForm form) {
        super.initialize(form);
        createFormControls(form);
    }

    /**
     * Setup the section that contains this part.
     * <p/>
     * This is called by the constructor to set the section's title and description
     * with parameters given in the constructor.
     * <br/>
     * Derived class override this if needed, however in most cases the default
     * implementation should be enough.
     *
     * @param sectionTitle The section part's title
     * @param sectionDescription The section part's description
     */
    protected void setupSection(String sectionTitle, String sectionDescription) {
        Section section = getSection();
        section.setText(sectionTitle);
        section.setDescription(sectionDescription);
    }

    /**
     * Create the controls to edit the attributes for the given ElementDescriptor.
     * <p/>
     * This MUST not be called by the constructor. Instead it must be called from
     * <code>initialize</code> (i.e. right after the form part is added to the managed form.)
     * <p/>
     * Derived classes can override this if necessary.
     *
     * @param managedForm The owner managed form
     */
    protected void createFormControls(IManagedForm managedForm) {
        setTable(createTableLayout(managedForm.getToolkit(), 2 /* numColumns */));

        createUiAttributes(managedForm);
    }

    /**
     * Sets the table where the attribute UI needs to be created.
     */
    protected void setTable(Composite table) {
        mTable = table;
    }

    /**
     * Returns the table where the attribute UI needs to be created.
     */
    protected Composite getTable() {
        return mTable;
    }

    /**
     * Add all the attribute UI widgets into the underlying table layout.
     *
     * @param managedForm The owner managed form
     */
    protected void createUiAttributes(IManagedForm managedForm) {
        Composite table = getTable();
        if (table == null || managedForm == null) {
            return;
        }

        // Remove any old UI controls
        for (Control c : table.getChildren()) {
            c.dispose();
        }

        fillTable(table, managedForm);

        // Tell the section that the layout has changed.
        layoutChanged();
    }

    /**
     * Actually fills the table.
     * This is called by {@link #createUiAttributes(IManagedForm)} to populate the new
     * table. The default implementation is to use
     * {@link #insertUiAttributes(UiElementNode, Composite, IManagedForm)} to actually
     * place the attributes of the default {@link UiElementNode} in the table.
     * <p/>
     * Derived classes can override this to add controls in the table before or after.
     *
     * @param table The table to fill. It must have 2 columns.
     * @param managedForm The managed form for new controls.
     */
    protected void fillTable(Composite table, IManagedForm managedForm) {
        int inserted = insertUiAttributes(mUiElementNode, table, managedForm);

        if (inserted == 0) {
            createLabel(table, managedForm.getToolkit(),
                    "No attributes to display, waiting for SDK to finish loading...",
                    null /* tooltip */ );
        }
    }

    /**
     * Insert the UI attributes of the given {@link UiElementNode} in the given table.
     *
     * @param uiNode The {@link UiElementNode} that contains the attributes to display.
     *               Must not be null.
     * @param table The table to fill. It must have 2 columns.
     * @param managedForm The managed form for new controls.
     * @return The number of UI attributes inserted. It is >= 0.
     */
    protected int insertUiAttributes(UiElementNode uiNode, Composite table, IManagedForm managedForm) {
        if (uiNode == null || table == null || managedForm == null) {
            return 0;
        }

        // To iterate over all attributes, we use the {@link ElementDescriptor} instead
        // of the {@link UiElementNode} because the attributes' order is guaranteed in the
        // descriptor but not in the node itself.
        AttributeDescriptor[] attr_desc_list = uiNode.getAttributeDescriptors();
        for (AttributeDescriptor attr_desc : attr_desc_list) {
            if (attr_desc instanceof XmlnsAttributeDescriptor) {
                // Do not show hidden attributes
                continue;
            }

            UiAttributeNode ui_attr = uiNode.findUiAttribute(attr_desc);
            if (ui_attr != null) {
                ui_attr.createUiControl(table, managedForm);
            } else {
                // The XML has an extra attribute which wasn't declared in
                // AndroidManifestDescriptors. This is not a problem, we just ignore it.
                AdtPlugin.log(IStatus.WARNING,
                        "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$
                        attr_desc.getXmlLocalName(),
                        uiNode.getDescriptor().getXmlName());
            }
        }
        return attr_desc_list.length;
    }

    /**
     * Tests whether the part is dirty i.e. its widgets have state that is
     * newer than the data in the model.
     * <p/>
     * This is done by iterating over all attributes and updating the super's
     * internal dirty flag. Stop once at least one attribute is dirty.
     *
     * @return <code>true</code> if the part is dirty, <code>false</code>
     *         otherwise.
     */
    @Override
    public boolean isDirty() {
        if (mUiElementNode != null && !super.isDirty()) {
            for (UiAttributeNode ui_attr : mUiElementNode.getAllUiAttributes()) {
                if (ui_attr.isDirty()) {
                    markDirty();
                    break;
                }
            }
        }
        return super.isDirty();
    }

    /**
     * If part is displaying information loaded from a model, this method
     * instructs it to commit the new (modified) data back into the model.
     *
     * @param onSave
     *            indicates if commit is called during 'save' operation or for
     *            some other reason (for example, if form is contained in a
     *            wizard or a multi-page editor and the user is about to leave
     *            the page).
     */
    @Override
    public void commit(boolean onSave) {
        if (mUiElementNode != null) {
            mEditor.wrapEditXmlModel(new Runnable() {
                @Override
                public void run() {
                    for (UiAttributeNode ui_attr : mUiElementNode.getAllUiAttributes()) {
                        ui_attr.commit();
                    }
                }
            });
        }

        // We need to call super's commit after we synchronized the nodes to make sure we
        // reset the dirty flag after all the side effects from committing have occurred.
        super.commit(onSave);
    }
}