diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/NewItemSelectionDialog.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/NewItemSelectionDialog.java | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/NewItemSelectionDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/NewItemSelectionDialog.java new file mode 100644 index 000000000..385a53a5f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/NewItemSelectionDialog.java @@ -0,0 +1,415 @@ +/* + * 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.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; +import org.eclipse.ui.dialogs.ISelectionStatusValidator; +import org.eclipse.ui.part.FileEditorInput; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +/** + * A selection dialog to select the type of the new element node to + * create, either in the application node or the selected sub node. + */ +public class NewItemSelectionDialog extends AbstractElementListSelectionDialog { + + /** The UI node selected in the tree view before creating the new item selection dialog. + * Can be null -- which means new items must be created in the root_node. */ + private UiElementNode mSelectedUiNode; + /** The root node chosen by the user, either root_node or the one passed + * to the constructor if not null */ + private UiElementNode mChosenRootNode; + private UiElementNode mLocalRootNode; + /** The descriptor of the elements to be displayed as root in this tree view. All elements + * of the same type in the root will be displayed. Can be null. */ + private ElementDescriptor[] mDescriptorFilters; + /** The key for the {@link #setLastUsedXmlName(Object[])}. It corresponds to the full + * workspace path of the currently edited file, if this can be computed. This is computed + * by {@link #getLastUsedXmlName(UiElementNode)}, called from the constructor. */ + private String mLastUsedKey; + /** A static map of known XML Names used for a given file. The map has full workspace + * paths as key and XML names as values. */ + private static final Map<String, String> sLastUsedXmlName = new HashMap<String, String>(); + /** The potential XML Name to initially select in the selection dialog. This is computed + * in the constructor and set by {@link #setInitialSelection(UiElementNode)}. */ + private String mInitialXmlName; + + /** + * Creates the new item selection dialog. + * + * @param shell The parent shell for the list. + * @param labelProvider ILabelProvider for the list. + * @param descriptorFilters The element allows at the root of the tree. Can be null. + * @param ui_node The selected node, or null if none is selected. + * @param root_node The root of the Ui Tree, either the UiDocumentNode or a sub-node. + */ + public NewItemSelectionDialog(Shell shell, ILabelProvider labelProvider, + ElementDescriptor[] descriptorFilters, + UiElementNode ui_node, + UiElementNode root_node) { + super(shell, labelProvider); + mDescriptorFilters = descriptorFilters; + mLocalRootNode = root_node; + + // Only accept the UI node if it is not the UI root node and it can have children. + // If the node cannot have children, select its parent as a potential target. + if (ui_node != null && ui_node != mLocalRootNode) { + if (ui_node.getDescriptor().hasChildren()) { + mSelectedUiNode = ui_node; + } else { + UiElementNode parent = ui_node.getUiParent(); + if (parent != null && parent != mLocalRootNode) { + mSelectedUiNode = parent; + } + } + } + + setHelpAvailable(false); + setMultipleSelection(false); + + setValidator(new ISelectionStatusValidator() { + @Override + public IStatus validate(Object[] selection) { + if (selection.length == 1 && selection[0] instanceof ViewElementDescriptor) { + return new Status(IStatus.OK, // severity + AdtPlugin.PLUGIN_ID, //plugin id + IStatus.OK, // code + ((ViewElementDescriptor) selection[0]).getFullClassName(), //msg + null); // exception + } else if (selection.length == 1 && selection[0] instanceof ElementDescriptor) { + return new Status(IStatus.OK, // severity + AdtPlugin.PLUGIN_ID, //plugin id + IStatus.OK, // code + "", //$NON-NLS-1$ // msg + null); // exception + } else { + return new Status(IStatus.ERROR, // severity + AdtPlugin.PLUGIN_ID, //plugin id + IStatus.ERROR, // code + "Invalid selection", // msg, translatable + null); // exception + } + } + }); + + // Determine the initial selection using a couple heuristics. + + // First check if we can get the last used node type for this file. + // The heuristic is that generally one keeps adding the same kind of items to the + // same file, so reusing the last used item type makes most sense. + String xmlName = getLastUsedXmlName(root_node); + if (xmlName == null) { + // Another heuristic is to find the most used item and default to that. + xmlName = getMostUsedXmlName(root_node); + } + if (xmlName == null) { + // Finally the last heuristic is to see if there's an item with a name + // similar to the edited file name. + xmlName = getLeafFileName(root_node); + } + // Set the potential name. Selecting the right item is done later by setInitialSelection(). + mInitialXmlName = xmlName; + } + + /** + * Returns a potential XML name based on the file name. + * The item name is marked with an asterisk to identify it as a partial match. + */ + private String getLeafFileName(UiElementNode ui_node) { + if (ui_node != null) { + AndroidXmlEditor editor = ui_node.getEditor(); + if (editor != null) { + IEditorInput editorInput = editor.getEditorInput(); + if (editorInput instanceof FileEditorInput) { + IFile f = ((FileEditorInput) editorInput).getFile(); + if (f != null) { + String leafName = f.getFullPath().removeFileExtension().lastSegment(); + return "*" + leafName; //$NON-NLS-1$ + } + } + } + } + + return null; + } + + /** + * Given a potential non-null root node, this method looks for the currently edited + * file path and uses it as a key to retrieve the last used item for this file by this + * selection dialog. Returns null if nothing can be found, otherwise returns the string + * name of the item. + */ + private String getLastUsedXmlName(UiElementNode ui_node) { + if (ui_node != null) { + AndroidXmlEditor editor = ui_node.getEditor(); + if (editor != null) { + IEditorInput editorInput = editor.getEditorInput(); + if (editorInput instanceof FileEditorInput) { + IFile f = ((FileEditorInput) editorInput).getFile(); + if (f != null) { + mLastUsedKey = f.getFullPath().toPortableString(); + + return sLastUsedXmlName.get(mLastUsedKey); + } + } + } + } + + return null; + } + + /** + * Sets the last used item for this selection dialog for this file. + * @param objects The currently selected items. Only the first one is used if it is an + * {@link ElementDescriptor}. + */ + private void setLastUsedXmlName(Object[] objects) { + if (mLastUsedKey != null && + objects != null && + objects.length > 0 && + objects[0] instanceof ElementDescriptor) { + ElementDescriptor desc = (ElementDescriptor) objects[0]; + sLastUsedXmlName.put(mLastUsedKey, desc.getXmlName()); + } + } + + /** + * Returns the most used sub-element name, if any, or null. + */ + private String getMostUsedXmlName(UiElementNode ui_node) { + if (ui_node != null) { + TreeMap<String, Integer> counts = new TreeMap<String, Integer>(); + int max = -1; + + for (UiElementNode child : ui_node.getUiChildren()) { + String name = child.getDescriptor().getXmlName(); + Integer i = counts.get(name); + int count = i == null ? 1 : i.intValue() + 1; + counts.put(name, count); + max = Math.max(max, count); + } + + if (max > 0) { + // Find first key with this max and return it + for (Entry<String, Integer> entry : counts.entrySet()) { + if (entry.getValue().intValue() == max) { + return entry.getKey(); + } + } + } + } + return null; + } + + /** + * @return The root node selected by the user, either root node or the + * one passed to the constructor if not null. + */ + public UiElementNode getChosenRootNode() { + return mChosenRootNode; + } + + /** + * Internal helper to compute the result. Returns the selection from + * the list view, if any. + */ + @Override + protected void computeResult() { + setResult(Arrays.asList(getSelectedElements())); + setLastUsedXmlName(getSelectedElements()); + } + + /** + * Creates the dialog area. + * + * First add a radio area, which may be either 2 radio controls or + * just a message area if there's only one choice (the app root node). + * + * Then uses the default from the AbstractElementListSelectionDialog + * which is to add both a filter text and a filtered list. Adding both + * is necessary (since the base class accesses both internal directly + * fields without checking for null pointers.) + * + * Finally sets the initial selection list. + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite contents = (Composite) super.createDialogArea(parent); + + createRadioControl(contents); + createFilterText(contents); + createFilteredList(contents); + + // We don't want the builtin message area label (we use a radio control + // instead), but if we don't create it, Bad Stuff happens on + // Eclipse 3.8 and later (see issue 32527). + Label label = createMessageArea(contents); + if (label != null) { + GridData data = (GridData) label.getLayoutData(); + data.exclude = true; + } + + // Initialize the list state. + // This must be done after the filtered list as been created. + chooseNode(mChosenRootNode); + + // Set the initial selection + setInitialSelection(mChosenRootNode); + return contents; + } + + /** + * Tries to set the initial selection based on the {@link #mInitialXmlName} computed + * in the constructor. The selection is only set if there's an element descriptor + * that matches the same exact XML name. When {@link #mInitialXmlName} starts with an + * asterisk, it means to do a partial case-insensitive match on the start of the + * strings. + */ + private void setInitialSelection(UiElementNode rootNode) { + ElementDescriptor initialElement = null; + + if (mInitialXmlName != null && mInitialXmlName.length() > 0) { + String name = mInitialXmlName; + boolean partial = name.startsWith("*"); //$NON-NLS-1$ + if (partial) { + name = name.substring(1).toLowerCase(Locale.US); + } + + for (ElementDescriptor desc : getAllowedDescriptors(rootNode)) { + if (!partial && desc.getXmlName().equals(name)) { + initialElement = desc; + break; + } else if (partial) { + String name2 = desc.getXmlLocalName().toLowerCase(Locale.US); + if (name.startsWith(name2) || name2.startsWith(name)) { + initialElement = desc; + break; + } + } + } + } + + setSelection(initialElement == null ? null : new ElementDescriptor[] { initialElement }); + } + + /** + * Creates the message text widget and sets layout data. + * @param content the parent composite of the message area. + */ + private Composite createRadioControl(Composite content) { + + if (mSelectedUiNode != null) { + Button radio1 = new Button(content, SWT.RADIO); + radio1.setText(String.format("Create a new element at the top level, in %1$s.", + mLocalRootNode.getShortDescription())); + + Button radio2 = new Button(content, SWT.RADIO); + radio2.setText(String.format("Create a new element in the selected element, %1$s.", + mSelectedUiNode.getBreadcrumbTrailDescription(false /* include_root */))); + + // Set the initial selection before adding the listeners + // (they can't be run till the filtered list has been created) + radio1.setSelection(false); + radio2.setSelection(true); + mChosenRootNode = mSelectedUiNode; + + radio1.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + chooseNode(mLocalRootNode); + } + }); + + radio2.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + chooseNode(mSelectedUiNode); + } + }); + } else { + setMessage(String.format("Create a new element at the top level, in %1$s.", + mLocalRootNode.getShortDescription())); + createMessageArea(content); + + mChosenRootNode = mLocalRootNode; + } + + return content; + } + + /** + * Internal helper to remember the root node choosen by the user. + * It also sets the list view to the adequate list of children that can + * be added to the chosen root node. + * + * If the chosen root node is mLocalRootNode and a descriptor filter was specified + * when creating the master-detail part, we use this as the set of nodes that + * can be created on the root node. + * + * @param ui_node The chosen root node, either mLocalRootNode or + * mSelectedUiNode. + */ + private void chooseNode(UiElementNode ui_node) { + mChosenRootNode = ui_node; + setListElements(getAllowedDescriptors(ui_node)); + } + + /** + * Returns the list of {@link ElementDescriptor}s that can be added to the given + * UI node. + * + * @param ui_node The UI node to which element should be added. Cannot be null. + * @return A non-null array of {@link ElementDescriptor}. The array might be empty. + */ + private ElementDescriptor[] getAllowedDescriptors(UiElementNode ui_node) { + if (ui_node == mLocalRootNode && + mDescriptorFilters != null && + mDescriptorFilters.length != 0) { + return mDescriptorFilters; + } else { + return ui_node.getDescriptor().getChildren(); + } + } +} |