aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java1163
1 files changed, 1163 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
new file mode 100644
index 000000000..28fb8c032
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
@@ -0,0 +1,1163 @@
+/*
+ * Copyright (C) 2008 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.wizards.newxmlfile;
+
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
+import static com.android.SdkConstants.LINEAR_LAYOUT;
+import static com.android.SdkConstants.RES_QUALIFIER_SEP;
+import static com.android.SdkConstants.SCROLL_VIEW;
+import static com.android.SdkConstants.VALUE_FILL_PARENT;
+import static com.android.SdkConstants.VALUE_MATCH_PARENT;
+import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP_CHAR;
+import static com.android.ide.eclipse.adt.internal.wizards.newxmlfile.ChooseConfigurationPage.RES_FOLDER_ABS;
+
+import com.android.SdkConstants;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.ResourceQualifier;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
+import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.ProjectCombo;
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
+import com.android.resources.ResourceFolderType;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.FileEditorInput;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * This is the first page of the {@link NewXmlFileWizard} which provides the ability to create
+ * skeleton XML resources files for Android projects.
+ * <p/>
+ * This page is used to select the project, resource type and file name.
+ */
+class NewXmlFileCreationPage extends WizardPage {
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ // Ensure the initial focus is in the Name field; you usually don't need
+ // to edit the default text field (the project name)
+ if (visible && mFileNameTextField != null) {
+ mFileNameTextField.setFocus();
+ }
+
+ validatePage();
+ }
+
+ /**
+ * Information on one type of resource that can be created (e.g. menu, pref, layout, etc.)
+ */
+ static class TypeInfo {
+ private final String mUiName;
+ private final ResourceFolderType mResFolderType;
+ private final String mTooltip;
+ private final Object mRootSeed;
+ private ArrayList<String> mRoots = new ArrayList<String>();
+ private final String mXmlns;
+ private final String mDefaultAttrs;
+ private final String mDefaultRoot;
+ private final int mTargetApiLevel;
+
+ public TypeInfo(String uiName,
+ String tooltip,
+ ResourceFolderType resFolderType,
+ Object rootSeed,
+ String defaultRoot,
+ String xmlns,
+ String defaultAttrs,
+ int targetApiLevel) {
+ mUiName = uiName;
+ mResFolderType = resFolderType;
+ mTooltip = tooltip;
+ mRootSeed = rootSeed;
+ mDefaultRoot = defaultRoot;
+ mXmlns = xmlns;
+ mDefaultAttrs = defaultAttrs;
+ mTargetApiLevel = targetApiLevel;
+ }
+
+ /** Returns the UI name for the resource type. Unique. Never null. */
+ String getUiName() {
+ return mUiName;
+ }
+
+ /** Returns the tooltip for the resource type. Can be null. */
+ String getTooltip() {
+ return mTooltip;
+ }
+
+ /**
+ * Returns the name of the {@link ResourceFolderType}.
+ * Never null but not necessarily unique,
+ * e.g. two types use {@link ResourceFolderType#XML}.
+ */
+ String getResFolderName() {
+ return mResFolderType.getName();
+ }
+
+ /**
+ * Returns the matching {@link ResourceFolderType}.
+ * Never null but not necessarily unique,
+ * e.g. two types use {@link ResourceFolderType#XML}.
+ */
+ ResourceFolderType getResFolderType() {
+ return mResFolderType;
+ }
+
+ /**
+ * Returns the seed used to fill the root element values.
+ * The seed might be either a String, a String array, an {@link ElementDescriptor},
+ * a {@link DocumentDescriptor} or null.
+ */
+ Object getRootSeed() {
+ return mRootSeed;
+ }
+
+ /**
+ * Returns the default root element that should be selected by default. Can be
+ * null.
+ *
+ * @param project the associated project, or null if not known
+ */
+ String getDefaultRoot(IProject project) {
+ return mDefaultRoot;
+ }
+
+ /**
+ * Returns the list of all possible root elements for the resource type.
+ * This can be an empty ArrayList but not null.
+ * <p/>
+ * TODO: the root list SHOULD depend on the currently selected project, to include
+ * custom classes.
+ */
+ ArrayList<String> getRoots() {
+ return mRoots;
+ }
+
+ /**
+ * If the generated resource XML file requires an "android" XMLNS, this should be set
+ * to {@link SdkConstants#NS_RESOURCES}. When it is null, no XMLNS is generated.
+ */
+ String getXmlns() {
+ return mXmlns;
+ }
+
+ /**
+ * When not null, this represent extra attributes that must be specified in the
+ * root element of the generated XML file. When null, no extra attributes are inserted.
+ *
+ * @param project the project to get the attributes for
+ * @param root the selected root element string, never null
+ */
+ String getDefaultAttrs(IProject project, String root) {
+ return mDefaultAttrs;
+ }
+
+ /**
+ * When not null, represents an extra string that should be written inside
+ * the element when constructed
+ *
+ * @param project the project to get the child content for
+ * @param root the chosen root element
+ * @return a string to be written inside the root element, or null if nothing
+ */
+ String getChild(IProject project, String root) {
+ return null;
+ }
+
+ /**
+ * The minimum API level required by the current SDK target to support this feature.
+ *
+ * @return the minimum API level
+ */
+ public int getTargetApiLevel() {
+ return mTargetApiLevel;
+ }
+ }
+
+ /**
+ * TypeInfo, information for each "type" of file that can be created.
+ */
+ private static final TypeInfo[] sTypes = {
+ new TypeInfo(
+ "Layout", // UI name
+ "An XML file that describes a screen layout.", // tooltip
+ ResourceFolderType.LAYOUT, // folder type
+ AndroidTargetData.DESCRIPTOR_LAYOUT, // root seed
+ LINEAR_LAYOUT, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ "", // not used, see below
+ 1 // target API level
+ ) {
+
+ @Override
+ String getDefaultRoot(IProject project) {
+ // TODO: Use GridLayout by default for new SDKs
+ // (when we've ironed out all the usability issues)
+ //Sdk currentSdk = Sdk.getCurrent();
+ //if (project != null && currentSdk != null) {
+ // IAndroidTarget target = currentSdk.getTarget(project);
+ // // fill_parent was renamed match_parent in API level 8
+ // if (target != null && target.getVersion().getApiLevel() >= 13) {
+ // return GRID_LAYOUT;
+ // }
+ //}
+
+ return LINEAR_LAYOUT;
+ };
+
+ // The default attributes must be determined dynamically since whether
+ // we use match_parent or fill_parent depends on the API level of the
+ // project
+ @Override
+ String getDefaultAttrs(IProject project, String root) {
+ Sdk currentSdk = Sdk.getCurrent();
+ String fill = VALUE_FILL_PARENT;
+ if (currentSdk != null) {
+ IAndroidTarget target = currentSdk.getTarget(project);
+ // fill_parent was renamed match_parent in API level 8
+ if (target != null && target.getVersion().getApiLevel() >= 8) {
+ fill = VALUE_MATCH_PARENT;
+ }
+ }
+
+ // Only set "vertical" orientation of LinearLayouts by default;
+ // for GridLayouts for example we want to rely on the real default
+ // of the layout
+ String size = String.format(
+ "android:layout_width=\"%1$s\"\n" //$NON-NLS-1$
+ + "android:layout_height=\"%2$s\"", //$NON-NLS-1$
+ fill, fill);
+ if (LINEAR_LAYOUT.equals(root)) {
+ return "android:orientation=\"vertical\"\n" + size; //$NON-NLS-1$
+ } else {
+ return size;
+ }
+ }
+
+ @Override
+ String getChild(IProject project, String root) {
+ // Create vertical linear layouts inside new scroll views
+ if (SCROLL_VIEW.equals(root) || HORIZONTAL_SCROLL_VIEW.equals(root)) {
+ return " <LinearLayout " //$NON-NLS-1$
+ + getDefaultAttrs(project, root).replace('\n', ' ')
+ + " android:orientation=\"vertical\"" //$NON-NLS-1$
+ + "></LinearLayout>\n"; //$NON-NLS-1$
+ }
+ return null;
+ }
+ },
+ new TypeInfo("Values", // UI name
+ "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
+ ResourceFolderType.VALUES, // folder type
+ SdkConstants.TAG_RESOURCES, // root seed
+ null, // default root
+ null, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("Drawable", // UI name
+ "An XML file that describes a drawable.", // tooltip
+ ResourceFolderType.DRAWABLE, // folder type
+ AndroidTargetData.DESCRIPTOR_DRAWABLE, // root seed
+ null, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("Menu", // UI name
+ "An XML file that describes an menu.", // tooltip
+ ResourceFolderType.MENU, // folder type
+ SdkConstants.TAG_MENU, // root seed
+ null, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("Color List", // UI name
+ "An XML file that describes a color state list.", // tooltip
+ ResourceFolderType.COLOR, // folder type
+ AndroidTargetData.DESCRIPTOR_COLOR, // root seed
+ "selector", //$NON-NLS-1$ // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("Property Animation", // UI name
+ "An XML file that describes a property animation", // tooltip
+ ResourceFolderType.ANIMATOR, // folder type
+ AndroidTargetData.DESCRIPTOR_ANIMATOR, // root seed
+ "set", //$NON-NLS-1$ // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 11 // target API level
+ ),
+ new TypeInfo("Tween Animation", // UI name
+ "An XML file that describes a tween animation.", // tooltip
+ ResourceFolderType.ANIM, // folder type
+ AndroidTargetData.DESCRIPTOR_ANIM, // root seed
+ "set", //$NON-NLS-1$ // default root
+ null, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("AppWidget Provider", // UI name
+ "An XML file that describes a widget provider.", // tooltip
+ ResourceFolderType.XML, // folder type
+ AndroidTargetData.DESCRIPTOR_APPWIDGET_PROVIDER, // root seed
+ null, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 3 // target API level
+ ),
+ new TypeInfo("Preference", // UI name
+ "An XML file that describes preferences.", // tooltip
+ ResourceFolderType.XML, // folder type
+ AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed
+ SdkConstants.CLASS_NAME_PREFERENCE_SCREEN, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("Searchable", // UI name
+ "An XML file that describes a searchable.", // tooltip
+ ResourceFolderType.XML, // folder type
+ AndroidTargetData.DESCRIPTOR_SEARCHABLE, // root seed
+ null, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 1 // target API level
+ ),
+ // Still missing: Interpolator, Raw and Mipmap. Raw should probably never be in
+ // this menu since it's not often used for creating XML files.
+ };
+
+ private NewXmlFileWizard.Values mValues;
+ private ProjectCombo mProjectButton;
+ private Text mFileNameTextField;
+ private Combo mTypeCombo;
+ private IStructuredSelection mInitialSelection;
+ private ResourceFolderType mInitialFolderType;
+ private boolean mInternalTypeUpdate;
+ private TargetChangeListener mSdkTargetChangeListener;
+ private Table mRootTable;
+ private TableViewer mRootTableViewer;
+
+ // --- UI creation ---
+
+ /**
+ * Constructs a new {@link NewXmlFileCreationPage}.
+ * <p/>
+ * Called by {@link NewXmlFileWizard#createMainPage}.
+ */
+ protected NewXmlFileCreationPage(String pageName, NewXmlFileWizard.Values values) {
+ super(pageName);
+ mValues = values;
+ setPageComplete(false);
+ }
+
+ public void setInitialSelection(IStructuredSelection initialSelection) {
+ mInitialSelection = initialSelection;
+ }
+
+ public void setInitialFolderType(ResourceFolderType initialType) {
+ mInitialFolderType = initialType;
+ }
+
+ /**
+ * Called by the parent Wizard to create the UI for this Wizard Page.
+ *
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ @SuppressWarnings("unused") // SWT constructors have side effects, they aren't unused
+ public void createControl(Composite parent) {
+ // This UI is maintained with WindowBuilder.
+
+ Composite composite = new Composite(parent, SWT.NULL);
+ composite.setLayout(new GridLayout(2, false /*makeColumnsEqualWidth*/));
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ // label before type radios
+ Label typeLabel = new Label(composite, SWT.NONE);
+ typeLabel.setText("Resource Type:");
+
+ mTypeCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
+ mTypeCombo.setToolTipText("What type of resource would you like to create?");
+ mTypeCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ if (mInitialFolderType != null) {
+ mTypeCombo.setEnabled(false);
+ }
+ mTypeCombo.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ TypeInfo type = getSelectedType();
+ if (type != null) {
+ onSelectType(type);
+ }
+ }
+ });
+
+ // separator
+ Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData gd2 = new GridData(GridData.GRAB_HORIZONTAL);
+ gd2.horizontalAlignment = SWT.FILL;
+ gd2.horizontalSpan = 2;
+ separator.setLayoutData(gd2);
+
+ // Project: [button]
+ String tooltip = "The Android Project where the new resource file will be created.";
+ Label projectLabel = new Label(composite, SWT.NONE);
+ projectLabel.setText("Project:");
+ projectLabel.setToolTipText(tooltip);
+
+ ProjectChooserHelper helper =
+ new ProjectChooserHelper(getShell(), null /* filter */);
+
+ mProjectButton = new ProjectCombo(helper, composite, mValues.project);
+ mProjectButton.setToolTipText(tooltip);
+ mProjectButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mProjectButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ IProject project = mProjectButton.getSelectedProject();
+ if (project != mValues.project) {
+ changeProject(project);
+ }
+ };
+ });
+
+ // Filename: [text]
+ Label fileLabel = new Label(composite, SWT.NONE);
+ fileLabel.setText("File:");
+ fileLabel.setToolTipText("The name of the resource file to create.");
+
+ mFileNameTextField = new Text(composite, SWT.BORDER);
+ mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mFileNameTextField.setToolTipText(tooltip);
+ mFileNameTextField.addModifyListener(new ModifyListener() {
+ @Override
+ public void modifyText(ModifyEvent e) {
+ mValues.name = mFileNameTextField.getText();
+ validatePage();
+ }
+ });
+
+ // separator
+ Label rootSeparator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData gd = new GridData(GridData.GRAB_HORIZONTAL);
+ gd.horizontalAlignment = SWT.FILL;
+ gd.horizontalSpan = 2;
+ rootSeparator.setLayoutData(gd);
+
+ // Root Element:
+ // [TableViewer]
+ Label rootLabel = new Label(composite, SWT.NONE);
+ rootLabel.setText("Root Element:");
+ new Label(composite, SWT.NONE);
+
+ mRootTableViewer = new TableViewer(composite, SWT.BORDER | SWT.FULL_SELECTION);
+ mRootTable = mRootTableViewer.getTable();
+ GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
+ tableGridData.heightHint = 200;
+ mRootTable.setLayoutData(tableGridData);
+
+ setControl(composite);
+
+ // Update state the first time
+ setErrorMessage(null);
+ setMessage(null);
+
+ initializeFromSelection(mInitialSelection);
+ updateAvailableTypes();
+ initializeFromFixedType();
+ initializeRootValues();
+ installTargetChangeListener();
+
+ initialSelectType();
+ validatePage();
+ }
+
+ private void initialSelectType() {
+ TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
+ int typeIndex = getTypeComboIndex(mValues.type);
+ if (typeIndex == -1) {
+ typeIndex = 0;
+ } else {
+ assert mValues.type == types[typeIndex];
+ }
+ mTypeCombo.select(typeIndex);
+ onSelectType(types[typeIndex]);
+ updateRootCombo(types[typeIndex]);
+ }
+
+ private void installTargetChangeListener() {
+ mSdkTargetChangeListener = new TargetChangeListener() {
+ @Override
+ public IProject getProject() {
+ return mValues.project;
+ }
+
+ @Override
+ public void reload() {
+ if (mValues.project != null) {
+ changeProject(mValues.project);
+ }
+ }
+ };
+
+ AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
+ }
+
+ @Override
+ public void dispose() {
+
+ if (mSdkTargetChangeListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
+ mSdkTargetChangeListener = null;
+ }
+
+ super.dispose();
+ }
+
+ /**
+ * Returns the selected root element string, if any.
+ *
+ * @return The selected root element string or null.
+ */
+ public String getRootElement() {
+ int index = mRootTable.getSelectionIndex();
+ if (index >= 0) {
+ Object[] roots = (Object[]) mRootTableViewer.getInput();
+ return roots[index].toString();
+ }
+ return null;
+ }
+
+ /**
+ * Called by {@link NewXmlFileWizard} to initialize the page with the selection
+ * received by the wizard -- typically the current user workbench selection.
+ * <p/>
+ * Things we expect to find out from the selection:
+ * <ul>
+ * <li>The project name, valid if it's an android nature.</li>
+ * <li>The current folder, valid if it's a folder under /res</li>
+ * <li>An existing filename, in which case the user will be asked whether to override it.</li>
+ * </ul>
+ * <p/>
+ * The selection can also be set to a {@link Pair} of {@link IProject} and a workspace
+ * resource path (where the resource path does not have to exist yet, such as res/anim/).
+ *
+ * @param selection The selection when the wizard was initiated.
+ */
+ private boolean initializeFromSelection(IStructuredSelection selection) {
+ if (selection == null) {
+ return false;
+ }
+
+ // Find the best match in the element list. In case there are multiple selected elements
+ // select the one that provides the most information and assign them a score,
+ // e.g. project=1 + folder=2 + file=4.
+ IProject targetProject = null;
+ String targetWsFolderPath = null;
+ String targetFileName = null;
+ int targetScore = 0;
+ for (Object element : selection.toList()) {
+ if (element instanceof IAdaptable) {
+ IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
+ IProject project = res != null ? res.getProject() : null;
+
+ // Is this an Android project?
+ try {
+ if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) {
+ continue;
+ }
+ } catch (CoreException e) {
+ // checking the nature failed, ignore this resource
+ continue;
+ }
+
+ int score = 1; // we have a valid project at least
+
+ IPath wsFolderPath = null;
+ String fileName = null;
+ assert res != null; // Eclipse incorrectly thinks res could be null, so tell it no
+ if (res.getType() == IResource.FOLDER) {
+ wsFolderPath = res.getProjectRelativePath();
+ } else if (res.getType() == IResource.FILE) {
+ if (SdkUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) {
+ fileName = res.getName();
+ }
+ wsFolderPath = res.getParent().getProjectRelativePath();
+ }
+
+ // Disregard this folder selection if it doesn't point to /res/something
+ if (wsFolderPath != null &&
+ wsFolderPath.segmentCount() > 1 &&
+ SdkConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) {
+ score += 2;
+ } else {
+ wsFolderPath = null;
+ fileName = null;
+ }
+
+ score += fileName != null ? 4 : 0;
+
+ if (score > targetScore) {
+ targetScore = score;
+ targetProject = project;
+ targetWsFolderPath = wsFolderPath != null ? wsFolderPath.toString() : null;
+ targetFileName = fileName;
+ }
+ } else if (element instanceof Pair<?,?>) {
+ // Pair of Project/String
+ @SuppressWarnings("unchecked")
+ Pair<IProject,String> pair = (Pair<IProject,String>)element;
+ targetScore = 1;
+ targetProject = pair.getFirst();
+ targetWsFolderPath = pair.getSecond();
+ targetFileName = "";
+ }
+ }
+
+ if (targetProject == null) {
+ // Try to figure out the project from the active editor
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page != null) {
+ IEditorPart activeEditor = page.getActiveEditor();
+ if (activeEditor instanceof AndroidXmlEditor) {
+ Object input = ((AndroidXmlEditor) activeEditor).getEditorInput();
+ if (input instanceof FileEditorInput) {
+ FileEditorInput fileInput = (FileEditorInput) input;
+ targetScore = 1;
+ IFile file = fileInput.getFile();
+ targetProject = file.getProject();
+ IPath path = file.getParent().getProjectRelativePath();
+ targetWsFolderPath = path != null ? path.toString() : null;
+ }
+ }
+ }
+ }
+ }
+
+ if (targetProject == null) {
+ // If we didn't find a default project based on the selection, check how many
+ // open Android projects we can find in the current workspace. If there's only
+ // one, we'll just select it by default.
+ IJavaProject[] projects = AdtUtils.getOpenAndroidProjects();
+ if (projects != null && projects.length == 1) {
+ targetScore = 1;
+ targetProject = projects[0].getProject();
+ }
+ }
+
+ // Now set the UI accordingly
+ if (targetScore > 0) {
+ mValues.project = targetProject;
+ mValues.folderPath = targetWsFolderPath;
+ mProjectButton.setSelectedProject(targetProject);
+ mFileNameTextField.setText(targetFileName != null ? targetFileName : ""); //$NON-NLS-1$
+
+ // If the current selection context corresponds to a specific file type,
+ // select it.
+ if (targetWsFolderPath != null) {
+ int pos = targetWsFolderPath.lastIndexOf(WS_SEP_CHAR);
+ if (pos >= 0) {
+ targetWsFolderPath = targetWsFolderPath.substring(pos + 1);
+ }
+ String[] folderSegments = targetWsFolderPath.split(RES_QUALIFIER_SEP);
+ if (folderSegments.length > 0) {
+ mValues.configuration = FolderConfiguration.getConfig(folderSegments);
+ String folderName = folderSegments[0];
+ selectTypeFromFolder(folderName);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void initializeFromFixedType() {
+ if (mInitialFolderType != null) {
+ for (TypeInfo type : sTypes) {
+ if (type.getResFolderType() == mInitialFolderType) {
+ mValues.type = type;
+ updateFolderPath(type);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Given a folder name, such as "drawable", select the corresponding type in
+ * the dropdown.
+ */
+ void selectTypeFromFolder(String folderName) {
+ List<TypeInfo> matches = new ArrayList<TypeInfo>();
+ boolean selected = false;
+
+ TypeInfo selectedType = getSelectedType();
+ for (TypeInfo type : sTypes) {
+ if (type.getResFolderName().equals(folderName)) {
+ matches.add(type);
+ selected |= type == selectedType;
+ }
+ }
+
+ if (matches.size() == 1) {
+ // If there's only one match, select it if it's not already selected
+ if (!selected) {
+ selectType(matches.get(0));
+ }
+ } else if (matches.size() > 1) {
+ // There are multiple type candidates for this folder. This can happen
+ // for /res/xml for example. Check to see if one of them is currently
+ // selected. If yes, leave the selection unchanged. If not, deselect all type.
+ if (!selected) {
+ selectType(null);
+ }
+ } else {
+ // Nothing valid was selected.
+ selectType(null);
+ }
+ }
+
+ /**
+ * Initialize the root values of the type infos based on the current framework values.
+ */
+ private void initializeRootValues() {
+ IProject project = mValues.project;
+ for (TypeInfo type : sTypes) {
+ // Clear all the roots for this type
+ ArrayList<String> roots = type.getRoots();
+ if (roots.size() > 0) {
+ roots.clear();
+ }
+
+ // depending of the type of the seed, initialize the root in different ways
+ Object rootSeed = type.getRootSeed();
+
+ if (rootSeed instanceof String) {
+ // The seed is a single string, Add it as-is.
+ roots.add((String) rootSeed);
+ } else if (rootSeed instanceof String[]) {
+ // The seed is an array of strings. Add them as-is.
+ for (String value : (String[]) rootSeed) {
+ roots.add(value);
+ }
+ } else if (rootSeed instanceof Integer && project != null) {
+ // The seed is a descriptor reference defined in AndroidTargetData.DESCRIPTOR_*
+ // In this case add all the children element descriptors defined, recursively,
+ // and avoid infinite recursion by keeping track of what has already been added.
+
+ // Note: if project is null, the root list will be empty since it has been
+ // cleared above.
+
+ // get the AndroidTargetData from the project
+ IAndroidTarget target = null;
+ AndroidTargetData data = null;
+
+ target = Sdk.getCurrent().getTarget(project);
+ if (target == null) {
+ // A project should have a target. The target can be missing if the project
+ // is an old project for which a target hasn't been affected or if the
+ // target no longer exists in this SDK. Simply log the error and dismiss.
+
+ AdtPlugin.log(IStatus.INFO,
+ "NewXmlFile wizard: no platform target for project %s", //$NON-NLS-1$
+ project.getName());
+ continue;
+ } else {
+ data = Sdk.getCurrent().getTargetData(target);
+
+ if (data == null) {
+ // We should have both a target and its data.
+ // However if the wizard is invoked whilst the platform is still being
+ // loaded we can end up in a weird case where we have a target but it
+ // doesn't have any data yet.
+ // Lets log a warning and silently ignore this root.
+
+ AdtPlugin.log(IStatus.INFO,
+ "NewXmlFile wizard: no data for target %s, project %s", //$NON-NLS-1$
+ target.getName(), project.getName());
+ continue;
+ }
+ }
+
+ IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
+ ElementDescriptor descriptor = provider.getDescriptor();
+ if (descriptor != null) {
+ HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
+ initRootElementDescriptor(roots, descriptor, visited);
+ }
+
+ // Sort alphabetically.
+ Collections.sort(roots);
+ }
+ }
+ }
+
+ /**
+ * Helper method to recursively insert all XML names for the given {@link ElementDescriptor}
+ * into the roots array list. Keeps track of visited nodes to avoid infinite recursion.
+ * Also avoids inserting the top {@link DocumentDescriptor} which is generally synthetic
+ * and not a valid root element.
+ */
+ private void initRootElementDescriptor(ArrayList<String> roots,
+ ElementDescriptor desc, HashSet<ElementDescriptor> visited) {
+ if (!(desc instanceof DocumentDescriptor)) {
+ String xmlName = desc.getXmlName();
+ if (xmlName != null && xmlName.length() > 0) {
+ roots.add(xmlName);
+ }
+ }
+
+ visited.add(desc);
+
+ for (ElementDescriptor child : desc.getChildren()) {
+ if (!visited.contains(child)) {
+ initRootElementDescriptor(roots, child, visited);
+ }
+ }
+ }
+
+ /**
+ * Changes mProject to the given new project and update the UI accordingly.
+ * <p/>
+ * Note that this does not check if the new project is the same as the current one
+ * on purpose, which allows a project to be updated when its target has changed or
+ * when targets are loaded in the background.
+ */
+ private void changeProject(IProject newProject) {
+ mValues.project = newProject;
+
+ // enable types based on new API level
+ updateAvailableTypes();
+ initialSelectType();
+
+ // update the folder name based on API level
+ updateFolderPath(mValues.type);
+
+ // update the Type with the new descriptors.
+ initializeRootValues();
+
+ // update the combo
+ updateRootCombo(mValues.type);
+
+ validatePage();
+ }
+
+ private void onSelectType(TypeInfo type) {
+ // Do nothing if this is an internal modification or if the widget has been
+ // deselected.
+ if (mInternalTypeUpdate) {
+ return;
+ }
+
+ mValues.type = type;
+
+ if (type == null) {
+ return;
+ }
+
+ // update the combo
+ updateRootCombo(type);
+
+ // update the folder path
+ updateFolderPath(type);
+
+ validatePage();
+ }
+
+ /** Updates the selected type in the type dropdown control */
+ private void setSelectedType(TypeInfo type) {
+ TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
+ if (types != null) {
+ for (int i = 0, n = types.length; i < n; i++) {
+ if (types[i] == type) {
+ mTypeCombo.select(i);
+ break;
+ }
+ }
+ }
+ }
+
+ /** Returns the selected type in the type dropdown control */
+ private TypeInfo getSelectedType() {
+ int index = mTypeCombo.getSelectionIndex();
+ if (index != -1) {
+ TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
+ return types[index];
+ }
+
+ return null;
+ }
+
+ /** Returns the selected index in the type dropdown control */
+ private int getTypeComboIndex(TypeInfo type) {
+ TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
+ for (int i = 0, n = types.length; i < n; i++) {
+ if (type == types[i]) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /** Updates the folder path to reflect the given type */
+ private void updateFolderPath(TypeInfo type) {
+ String wsFolderPath = mValues.folderPath;
+ String newPath = null;
+ FolderConfiguration config = mValues.configuration;
+ ResourceQualifier qual = config.getInvalidQualifier();
+ if (qual == null) {
+ // The configuration is valid. Reformat the folder path using the canonical
+ // value from the configuration.
+ newPath = RES_FOLDER_ABS + config.getFolderName(type.getResFolderType());
+ } else {
+ // The configuration is invalid. We still update the path but this time
+ // do it manually on the string.
+ if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
+ wsFolderPath = wsFolderPath.replaceFirst(
+ "^(" + RES_FOLDER_ABS +")[^-]*(.*)", //$NON-NLS-1$ //$NON-NLS-2$
+ "\\1" + type.getResFolderName() + "\\2"); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ newPath = RES_FOLDER_ABS + config.getFolderName(type.getResFolderType());
+ }
+ }
+
+ if (newPath != null && !newPath.equals(wsFolderPath)) {
+ mValues.folderPath = newPath;
+ }
+ }
+
+ /**
+ * Helper method that fills the values of the "root element" combo box based
+ * on the currently selected type radio button. Also disables the combo is there's
+ * only one choice. Always select the first root element for the given type.
+ *
+ * @param type The currently selected {@link TypeInfo}, or null
+ */
+ private void updateRootCombo(TypeInfo type) {
+ IBaseLabelProvider labelProvider = new ColumnLabelProvider() {
+ @Override
+ public Image getImage(Object element) {
+ return IconFactory.getInstance().getIcon(element.toString());
+ }
+ };
+ mRootTableViewer.setContentProvider(new ArrayContentProvider());
+ mRootTableViewer.setLabelProvider(labelProvider);
+
+ if (type != null) {
+ // get the list of roots. The list can be empty but not null.
+ ArrayList<String> roots = type.getRoots();
+ mRootTableViewer.setInput(roots.toArray());
+
+ int index = 0; // default is to select the first one
+ String defaultRoot = type.getDefaultRoot(mValues.project);
+ if (defaultRoot != null) {
+ index = roots.indexOf(defaultRoot);
+ }
+ mRootTable.select(index < 0 ? 0 : index);
+ mRootTable.showSelection();
+ }
+ }
+
+ /**
+ * Helper method to select the current type in the type dropdown
+ *
+ * @param type The TypeInfo matching the radio button to selected or null to deselect them all.
+ */
+ private void selectType(TypeInfo type) {
+ mInternalTypeUpdate = true;
+ mValues.type = type;
+ if (type == null) {
+ if (mTypeCombo.getSelectionIndex() != -1) {
+ mTypeCombo.deselect(mTypeCombo.getSelectionIndex());
+ }
+ } else {
+ setSelectedType(type);
+ }
+ updateRootCombo(type);
+ mInternalTypeUpdate = false;
+ }
+
+ /**
+ * Add the available types in the type combobox, based on whether they are available
+ * for the current SDK.
+ * <p/>
+ * A type is available either if:
+ * - if mProject is null, API level 1 is considered valid
+ * - if mProject is !null, the project->target->API must be >= to the type's API level.
+ */
+ private void updateAvailableTypes() {
+ IProject project = mValues.project;
+ IAndroidTarget target = project != null ? Sdk.getCurrent().getTarget(project) : null;
+ int currentApiLevel = 1;
+ if (target != null) {
+ currentApiLevel = target.getVersion().getApiLevel();
+ }
+
+ List<String> items = new ArrayList<String>(sTypes.length);
+ List<TypeInfo> types = new ArrayList<TypeInfo>(sTypes.length);
+ for (int i = 0, n = sTypes.length; i < n; i++) {
+ TypeInfo type = sTypes[i];
+ if (type.getTargetApiLevel() <= currentApiLevel) {
+ items.add(type.getUiName());
+ types.add(type);
+ }
+ }
+ mTypeCombo.setItems(items.toArray(new String[items.size()]));
+ mTypeCombo.setData(types.toArray(new TypeInfo[types.size()]));
+ }
+
+ /**
+ * Validates the fields, displays errors and warnings.
+ * Enables the finish button if there are no errors.
+ */
+ private void validatePage() {
+ String error = null;
+ String warning = null;
+
+ // -- validate type
+ TypeInfo type = mValues.type;
+ if (error == null) {
+ if (type == null) {
+ error = "One of the types must be selected (e.g. layout, values, etc.)";
+ }
+ }
+
+ // -- validate project
+ if (mValues.project == null) {
+ error = "Please select an Android project.";
+ }
+
+ // -- validate type API level
+ if (error == null) {
+ IAndroidTarget target = Sdk.getCurrent().getTarget(mValues.project);
+ int currentApiLevel = 1;
+ if (target != null) {
+ currentApiLevel = target.getVersion().getApiLevel();
+ }
+
+ assert type != null;
+ if (type.getTargetApiLevel() > currentApiLevel) {
+ error = "The API level of the selected type (e.g. AppWidget, etc.) is not " +
+ "compatible with the API level of the project.";
+ }
+ }
+
+ // -- validate filename
+ if (error == null) {
+ String fileName = mValues.getFileName();
+ assert type != null;
+ ResourceFolderType folderType = type.getResFolderType();
+ error = ResourceNameValidator.create(true, folderType).isValid(fileName);
+ }
+
+ // -- validate destination file doesn't exist
+ if (error == null) {
+ IFile file = mValues.getDestinationFile();
+ if (file != null && file.exists()) {
+ warning = "The destination file already exists";
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else if (warning != null) {
+ setMessage(warning, IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ /**
+ * Returns the {@link TypeInfo} for the given {@link ResourceFolderType}, or null if
+ * not found
+ *
+ * @param folderType the {@link ResourceFolderType} to look for
+ * @return the corresponding {@link TypeInfo}
+ */
+ static TypeInfo getTypeInfo(ResourceFolderType folderType) {
+ for (TypeInfo typeInfo : sTypes) {
+ if (typeInfo.getResFolderType() == folderType) {
+ return typeInfo;
+ }
+ }
+
+ return null;
+ }
+}