| /* |
| * 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; |
| } |
| } |