• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tree;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
21 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
22 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
23 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
24 
25 import org.eclipse.core.resources.IFile;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.jface.viewers.ILabelProvider;
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.swt.widgets.Control;
35 import org.eclipse.swt.widgets.Shell;
36 import org.eclipse.ui.IEditorInput;
37 import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
38 import org.eclipse.ui.dialogs.ISelectionStatusValidator;
39 import org.eclipse.ui.part.FileEditorInput;
40 
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.Map;
44 import java.util.TreeMap;
45 import java.util.Map.Entry;
46 
47 /**
48  * A selection dialog to select the type of the new element node to
49  * create, either in the application node or the selected sub node.
50  */
51 public class NewItemSelectionDialog extends AbstractElementListSelectionDialog {
52 
53     /** The UI node selected in the tree view before creating the new item selection dialog.
54      *  Can be null -- which means new items must be created in the root_node. */
55     private UiElementNode mSelectedUiNode;
56     /** The root node chosen by the user, either root_node or the one passed
57      *  to the constructor if not null */
58     private UiElementNode mChosenRootNode;
59     private UiElementNode mLocalRootNode;
60     /** The descriptor of the elements to be displayed as root in this tree view. All elements
61      *  of the same type in the root will be displayed. Can be null. */
62     private ElementDescriptor[] mDescriptorFilters;
63     /** The key for the {@link #setLastUsedXmlName(Object[])}. It corresponds to the full
64      * workspace path of the currently edited file, if this can be computed. This is computed
65      * by {@link #getLastUsedXmlName(UiElementNode)}, called from the constructor. */
66     private String mLastUsedKey;
67     /** A static map of known XML Names used for a given file. The map has full workspace
68      * paths as key and XML names as values. */
69     private static final Map<String, String> sLastUsedXmlName = new HashMap<String, String>();
70     /** The potential XML Name to initially select in the selection dialog. This is computed
71      * in the constructor and set by {@link #setInitialSelection(UiElementNode)}. */
72     private String mInitialXmlName;
73 
74     /**
75      * Creates the new item selection dialog.
76      *
77      * @param shell The parent shell for the list.
78      * @param labelProvider ILabelProvider for the list.
79      * @param descriptorFilters The element allows at the root of the tree. Can be null.
80      * @param ui_node The selected node, or null if none is selected.
81      * @param root_node The root of the Ui Tree, either the UiDocumentNode or a sub-node.
82      */
NewItemSelectionDialog(Shell shell, ILabelProvider labelProvider, ElementDescriptor[] descriptorFilters, UiElementNode ui_node, UiElementNode root_node)83     public NewItemSelectionDialog(Shell shell, ILabelProvider labelProvider,
84             ElementDescriptor[] descriptorFilters,
85             UiElementNode ui_node,
86             UiElementNode root_node) {
87         super(shell, labelProvider);
88         mDescriptorFilters = descriptorFilters;
89         mLocalRootNode = root_node;
90 
91         // Only accept the UI node if it is not the UI root node and it can have children.
92         // If the node cannot have children, select its parent as a potential target.
93         if (ui_node != null && ui_node != mLocalRootNode) {
94             if (ui_node.getDescriptor().hasChildren()) {
95                 mSelectedUiNode = ui_node;
96             } else {
97                 UiElementNode parent = ui_node.getUiParent();
98                 if (parent != null && parent != mLocalRootNode) {
99                     mSelectedUiNode = parent;
100                 }
101             }
102         }
103 
104         setHelpAvailable(false);
105         setMultipleSelection(false);
106 
107         setValidator(new ISelectionStatusValidator() {
108             public IStatus validate(Object[] selection) {
109                 if (selection.length == 1 && selection[0] instanceof ViewElementDescriptor) {
110                     return new Status(IStatus.OK, // severity
111                             AdtPlugin.PLUGIN_ID, //plugin id
112                             IStatus.OK, // code
113                             ((ViewElementDescriptor) selection[0]).getCanonicalClassName(), //msg
114                             null); // exception
115                 } else if (selection.length == 1 && selection[0] instanceof ElementDescriptor) {
116                     return new Status(IStatus.OK, // severity
117                             AdtPlugin.PLUGIN_ID, //plugin id
118                             IStatus.OK, // code
119                             "", //$NON-NLS-1$ // msg
120                             null); // exception
121                 } else {
122                     return new Status(IStatus.ERROR, // severity
123                             AdtPlugin.PLUGIN_ID, //plugin id
124                             IStatus.ERROR, // code
125                             "Invalid selection", // msg, translatable
126                             null); // exception
127                 }
128             }
129         });
130 
131         // Determine the initial selection using a couple heuristics.
132 
133         // First check if we can get the last used node type for this file.
134         // The heuristic is that generally one keeps adding the same kind of items to the
135         // same file, so reusing the last used item type makes most sense.
136         String xmlName = getLastUsedXmlName(root_node);
137         if (xmlName == null) {
138             // Another heuristic is to find the most used item and default to that.
139             xmlName = getMostUsedXmlName(root_node);
140         }
141         if (xmlName == null) {
142             // Finally the last heuristic is to see if there's an item with a name
143             // similar to the edited file name.
144             xmlName = getLeafFileName(root_node);
145         }
146         // Set the potential name. Selecting the right item is done later by setInitialSelection().
147         mInitialXmlName = xmlName;
148     }
149 
150     /**
151      * Returns a potential XML name based on the file name.
152      * The item name is marked with an asterisk to identify it as a partial match.
153      */
getLeafFileName(UiElementNode ui_node)154     private String getLeafFileName(UiElementNode ui_node) {
155         if (ui_node != null) {
156             AndroidEditor editor = ui_node.getEditor();
157             if (editor != null) {
158                 IEditorInput editorInput = editor.getEditorInput();
159                 if (editorInput instanceof FileEditorInput) {
160                     IFile f = ((FileEditorInput) editorInput).getFile();
161                     if (f != null) {
162                         String leafName = f.getFullPath().removeFileExtension().lastSegment();
163                         return "*" + leafName; //$NON-NLS-1$
164                     }
165                 }
166             }
167         }
168 
169         return null;
170     }
171 
172     /**
173      * Given a potential non-null root node, this method looks for the currently edited
174      * file path and uses it as a key to retrieve the last used item for this file by this
175      * selection dialog. Returns null if nothing can be found, otherwise returns the string
176      * name of the item.
177      */
getLastUsedXmlName(UiElementNode ui_node)178     private String getLastUsedXmlName(UiElementNode ui_node) {
179         if (ui_node != null) {
180             AndroidEditor editor = ui_node.getEditor();
181             if (editor != null) {
182                 IEditorInput editorInput = editor.getEditorInput();
183                 if (editorInput instanceof FileEditorInput) {
184                     IFile f = ((FileEditorInput) editorInput).getFile();
185                     if (f != null) {
186                         mLastUsedKey = f.getFullPath().toPortableString();
187 
188                         return sLastUsedXmlName.get(mLastUsedKey);
189                     }
190                 }
191             }
192         }
193 
194         return null;
195     }
196 
197     /**
198      * Sets the last used item for this selection dialog for this file.
199      * @param objects The currently selected items. Only the first one is used if it is an
200      *                {@link ElementDescriptor}.
201      */
setLastUsedXmlName(Object[] objects)202     private void setLastUsedXmlName(Object[] objects) {
203         if (mLastUsedKey != null &&
204                 objects != null &&
205                 objects.length > 0 &&
206                 objects[0] instanceof ElementDescriptor) {
207             ElementDescriptor desc = (ElementDescriptor) objects[0];
208             sLastUsedXmlName.put(mLastUsedKey, desc.getXmlName());
209         }
210     }
211 
212     /**
213      * Returns the most used sub-element name, if any, or null.
214      */
getMostUsedXmlName(UiElementNode ui_node)215     private String getMostUsedXmlName(UiElementNode ui_node) {
216         if (ui_node != null) {
217             TreeMap<String, Integer> counts = new TreeMap<String, Integer>();
218             int max = -1;
219 
220             for (UiElementNode child : ui_node.getUiChildren()) {
221                 String name = child.getDescriptor().getXmlName();
222                 Integer i = counts.get(name);
223                 int count = i == null ? 1 : i.intValue() + 1;
224                 counts.put(name, count);
225                 max = Math.max(max, count);
226             }
227 
228             if (max > 0) {
229                 // Find first key with this max and return it
230                 for (Entry<String, Integer> entry : counts.entrySet()) {
231                     if (entry.getValue().intValue() == max) {
232                         return entry.getKey();
233                     }
234                 }
235             }
236         }
237         return null;
238     }
239 
240     /**
241      * @return The root node selected by the user, either root node or the
242      *         one passed to the constructor if not null.
243      */
getChosenRootNode()244     public UiElementNode getChosenRootNode() {
245         return mChosenRootNode;
246     }
247 
248     /**
249      * Internal helper to compute the result. Returns the selection from
250      * the list view, if any.
251      */
252     @Override
computeResult()253     protected void computeResult() {
254         setResult(Arrays.asList(getSelectedElements()));
255         setLastUsedXmlName(getSelectedElements());
256     }
257 
258     /**
259      * Creates the dialog area.
260      *
261      * First add a radio area, which may be either 2 radio controls or
262      * just a message area if there's only one choice (the app root node).
263      *
264      * Then uses the default from the AbstractElementListSelectionDialog
265      * which is to add both a filter text and a filtered list. Adding both
266      * is necessary (since the base class accesses both internal directly
267      * fields without checking for null pointers.)
268      *
269      * Finally sets the initial selection list.
270      */
271     @Override
createDialogArea(Composite parent)272     protected Control createDialogArea(Composite parent) {
273         Composite contents = (Composite) super.createDialogArea(parent);
274 
275         createRadioControl(contents);
276         createFilterText(contents);
277         createFilteredList(contents);
278 
279         // Initialize the list state.
280         // This must be done after the filtered list as been created.
281         chooseNode(mChosenRootNode);
282 
283         // Set the initial selection
284         setInitialSelection(mChosenRootNode);
285         return contents;
286     }
287 
288     /**
289      * Tries to set the initial selection based on the {@link #mInitialXmlName} computed
290      * in the constructor. The selection is only set if there's an element descriptor
291      * that matches the same exact XML name. When {@link #mInitialXmlName} starts with an
292      * asterisk, it means to do a partial case-insensitive match on the start of the
293      * strings.
294      */
setInitialSelection(UiElementNode rootNode)295     private void setInitialSelection(UiElementNode rootNode) {
296         ElementDescriptor initialElement = null;
297 
298         if (mInitialXmlName != null && mInitialXmlName.length() > 0) {
299             String name = mInitialXmlName;
300             boolean partial = name.startsWith("*");   //$NON-NLS-1$
301             if (partial) {
302                 name = name.substring(1).toLowerCase();
303             }
304 
305             for (ElementDescriptor desc : getAllowedDescriptors(rootNode)) {
306                 if (!partial && desc.getXmlName().equals(name)) {
307                     initialElement = desc;
308                     break;
309                 } else if (partial) {
310                     String name2 = desc.getXmlLocalName().toLowerCase();
311                     if (name.startsWith(name2) || name2.startsWith(name)) {
312                         initialElement = desc;
313                         break;
314                     }
315                 }
316             }
317         }
318 
319         setSelection(initialElement == null ? null : new ElementDescriptor[] { initialElement });
320     }
321 
322     /**
323      * Creates the message text widget and sets layout data.
324      * @param content the parent composite of the message area.
325      */
createRadioControl(Composite content)326     private Composite createRadioControl(Composite content) {
327 
328         if (mSelectedUiNode != null) {
329             Button radio1 = new Button(content, SWT.RADIO);
330             radio1.setText(String.format("Create a new element at the top level, in %1$s.",
331                     mLocalRootNode.getShortDescription()));
332 
333             Button radio2 = new Button(content, SWT.RADIO);
334             radio2.setText(String.format("Create a new element in the selected element, %1$s.",
335                     mSelectedUiNode.getBreadcrumbTrailDescription(false /* include_root */)));
336 
337             // Set the initial selection before adding the listeners
338             // (they can't be run till the filtered list has been created)
339             radio1.setSelection(false);
340             radio2.setSelection(true);
341             mChosenRootNode = mSelectedUiNode;
342 
343             radio1.addSelectionListener(new SelectionAdapter() {
344                 @Override
345                 public void widgetSelected(SelectionEvent e) {
346                     super.widgetSelected(e);
347                     chooseNode(mLocalRootNode);
348                 }
349             });
350 
351             radio2.addSelectionListener(new SelectionAdapter() {
352                 @Override
353                 public void widgetSelected(SelectionEvent e) {
354                     super.widgetSelected(e);
355                     chooseNode(mSelectedUiNode);
356                 }
357             });
358         } else {
359             setMessage(String.format("Create a new element at the top level, in %1$s.",
360                     mLocalRootNode.getShortDescription()));
361             createMessageArea(content);
362 
363             mChosenRootNode = mLocalRootNode;
364         }
365 
366         return content;
367     }
368 
369     /**
370      * Internal helper to remember the root node choosen by the user.
371      * It also sets the list view to the adequate list of children that can
372      * be added to the chosen root node.
373      *
374      * If the chosen root node is mLocalRootNode and a descriptor filter was specified
375      * when creating the master-detail part, we use this as the set of nodes that
376      * can be created on the root node.
377      *
378      * @param ui_node The chosen root node, either mLocalRootNode or
379      *                mSelectedUiNode.
380      */
chooseNode(UiElementNode ui_node)381     private void chooseNode(UiElementNode ui_node) {
382         mChosenRootNode = ui_node;
383         setListElements(getAllowedDescriptors(ui_node));
384     }
385 
386     /**
387      * Returns the list of {@link ElementDescriptor}s that can be added to the given
388      * UI node.
389      *
390      * @param ui_node The UI node to which element should be added. Cannot be null.
391      * @return A non-null array of {@link ElementDescriptor}. The array might be empty.
392      */
getAllowedDescriptors(UiElementNode ui_node)393     private ElementDescriptor[] getAllowedDescriptors(UiElementNode ui_node) {
394         if (ui_node == mLocalRootNode &&
395                 mDescriptorFilters != null &&
396                 mDescriptorFilters.length != 0) {
397             return mDescriptorFilters;
398         } else {
399             return ui_node.getDescriptor().getChildren();
400         }
401     }
402 }
403