diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java new file mode 100644 index 000000000..16cd7b355 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java @@ -0,0 +1,431 @@ +/* + * 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.FQCN_GRID_LAYOUT; +import static com.android.SdkConstants.GRID_LAYOUT; + +import com.android.SdkConstants; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.xml.XmlFormatStyle; +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.formatting.EclipseXmlFormatPreferences; +import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewManager; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.project.SupportLibraryHelper; +import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo; +import com.android.resources.ResourceFolderType; +import com.android.utils.Pair; + +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.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PartInitException; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +/** + * The "New Android XML File Wizard" provides the ability to create skeleton XML + * resources files for Android projects. + * <p/> + * The wizard has one page, {@link NewXmlFileCreationPage}, used to select the project, + * the resource folder, resource type and file name. It then creates the XML file. + */ +public class NewXmlFileWizard extends Wizard implements INewWizard { + /** The XML header to write at the top of the XML file */ + public static final String XML_HEADER_LINE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ + + private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ + + protected static final String MAIN_PAGE_NAME = "newAndroidXmlFilePage"; //$NON-NLS-1$ + + private NewXmlFileCreationPage mMainPage; + private ChooseConfigurationPage mConfigPage; + private Values mValues; + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + setHelpAvailable(false); // TODO have help + setWindowTitle("New Android XML File"); + setImageDescriptor(); + + mValues = new Values(); + mMainPage = createMainPage(mValues); + mMainPage.setTitle("New Android XML File"); + mMainPage.setDescription("Creates a new Android XML file."); + mMainPage.setInitialSelection(selection); + + mConfigPage = new ChooseConfigurationPage(mValues); + + // Trigger a check to see if the SDK needs to be reloaded (which will + // invoke onSdkLoaded asynchronously as needed). + AdtPlugin.getDefault().refreshSdk(); + } + + /** + * Creates the wizard page. + * <p/> + * Please do NOT override this method. + * <p/> + * This is protected so that it can be overridden by unit tests. + * However the contract of this class is private and NO ATTEMPT will be made + * to maintain compatibility between different versions of the plugin. + */ + protected NewXmlFileCreationPage createMainPage(NewXmlFileWizard.Values values) { + return new NewXmlFileCreationPage(MAIN_PAGE_NAME, values); + } + + // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- + // + // The Wizard class implements most defaults and boilerplate code needed by + // IWizard + + /** + * Adds pages to this wizard. + */ + @Override + public void addPages() { + addPage(mMainPage); + addPage(mConfigPage); + + } + + /** + * Performs any actions appropriate in response to the user having pressed + * the Finish button, or refuse if finishing now is not permitted: here, it + * actually creates the workspace project and then switch to the Java + * perspective. + * + * @return True + */ + @Override + public boolean performFinish() { + final Pair<IFile, IRegion> created = createXmlFile(); + if (created == null) { + return false; + } else { + // Open the file + // This has to be delayed in order for focus handling to work correctly + AdtPlugin.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + IFile file = created.getFirst(); + IRegion region = created.getSecond(); + try { + IEditorPart editor = AdtPlugin.openFile(file, null, + false /*showEditorTab*/); + if (editor instanceof AndroidXmlEditor) { + final AndroidXmlEditor xmlEditor = (AndroidXmlEditor)editor; + if (!xmlEditor.hasMultiplePages()) { + xmlEditor.show(region.getOffset(), region.getLength(), + true /* showEditorTab */); + } + } + } catch (PartInitException e) { + AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$ + file.getFullPath().toString()); + } + }}); + + return true; + } + } + + // -- Custom Methods -- + + private Pair<IFile, IRegion> createXmlFile() { + IFile file = mValues.getDestinationFile(); + TypeInfo type = mValues.type; + if (type == null) { + // this is not expected to happen + String name = file.getFullPath().toString(); + AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing type", name); //$NON-NLS-1$ + return null; + } + String xmlns = type.getXmlns(); + String root = mMainPage.getRootElement(); + if (root == null) { + // this is not expected to happen + AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing root element", //$NON-NLS-1$ + file.toString()); + return null; + } + + String attrs = type.getDefaultAttrs(mValues.project, root); + String child = type.getChild(mValues.project, root); + return createXmlFile(file, xmlns, root, attrs, child, type.getResFolderType()); + } + + /** Creates a new file using the given root element, namespace and root attributes */ + private static Pair<IFile, IRegion> createXmlFile(IFile file, String xmlns, + String root, String rootAttributes, String child, ResourceFolderType folderType) { + String name = file.getFullPath().toString(); + boolean need_delete = false; + + if (file.exists()) { + if (!AdtPlugin.displayPrompt("New Android XML File", + String.format("Do you want to overwrite the file %1$s ?", name))) { + // abort if user selects cancel. + return null; + } + need_delete = true; + } else { + AdtUtils.createWsParentDirectory(file.getParent()); + } + + StringBuilder sb = new StringBuilder(XML_HEADER_LINE); + + if (folderType == ResourceFolderType.LAYOUT && root.equals(GRID_LAYOUT)) { + IProject project = file.getParent().getProject(); + int minSdk = ManifestInfo.get(project).getMinSdkVersion(); + if (minSdk < 14) { + root = SupportLibraryHelper.getTagFor(project, FQCN_GRID_LAYOUT); + if (root.equals(FQCN_GRID_LAYOUT)) { + root = GRID_LAYOUT; + } + } + } + + sb.append('<').append(root); + if (xmlns != null) { + sb.append('\n').append(" xmlns:android=\"").append(xmlns).append('"'); //$NON-NLS-1$ + } + + if (rootAttributes != null) { + sb.append("\n "); //$NON-NLS-1$ + sb.append(rootAttributes.replace("\n", "\n ")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + sb.append(">\n"); //$NON-NLS-1$ + + if (child != null) { + sb.append(child); + } + + boolean autoFormat = AdtPrefs.getPrefs().getUseCustomXmlFormatter(); + + // Insert an indented caret. Since the markup here will be reformatted, we need to + // insert text tokens that the formatter will preserve, which we can then turn back + // into indentation and a caret offset: + final String indentToken = "${indent}"; //$NON-NLS-1$ + final String caretToken = "${caret}"; //$NON-NLS-1$ + sb.append(indentToken); + sb.append(caretToken); + if (!autoFormat) { + sb.append('\n'); + } + + sb.append("</").append(root).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$ + + EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create(); + String fileContents; + if (!autoFormat) { + fileContents = sb.toString(); + } else { + XmlFormatStyle style = EclipseXmlPrettyPrinter.getForFolderType(folderType); + fileContents = EclipseXmlPrettyPrinter.prettyPrint(sb.toString(), formatPrefs, + style, null /*lineSeparator*/); + } + + // Remove marker tokens and replace them with whitespace + fileContents = fileContents.replace(indentToken, formatPrefs.getOneIndentUnit()); + int caretOffset = fileContents.indexOf(caretToken); + if (caretOffset != -1) { + fileContents = fileContents.replace(caretToken, ""); //$NON-NLS-1$ + } + + String error = null; + try { + byte[] buf = fileContents.getBytes("UTF8"); //$NON-NLS-1$ + InputStream stream = new ByteArrayInputStream(buf); + if (need_delete) { + file.delete(IResource.KEEP_HISTORY | IResource.FORCE, null /*monitor*/); + } + file.create(stream, true /*force*/, null /*progress*/); + IRegion region = caretOffset != -1 ? new Region(caretOffset, 0) : null; + + // If you introduced a new locale, or new screen variations etc, ensure that + // the list of render previews is updated if necessary + if (file.getParent().getName().indexOf('-') != -1 + && (folderType == ResourceFolderType.LAYOUT + || folderType == ResourceFolderType.VALUES)) { + RenderPreviewManager.bumpRevision(); + } + + return Pair.of(file, region); + } catch (UnsupportedEncodingException e) { + error = e.getMessage(); + } catch (CoreException e) { + error = e.getMessage(); + } + + error = String.format("Failed to generate %1$s: %2$s", name, error); + AdtPlugin.displayError("New Android XML File", error); + return null; + } + + /** + * Returns true if the New XML Wizard can create new files of the given + * {@link ResourceFolderType} + * + * @param folderType the folder type to create a file for + * @return true if this wizard can create new files for the given folder type + */ + public static boolean canCreateXmlFile(ResourceFolderType folderType) { + TypeInfo typeInfo = NewXmlFileCreationPage.getTypeInfo(folderType); + return typeInfo != null && (typeInfo.getDefaultRoot(null /*project*/) != null || + typeInfo.getRootSeed() instanceof String); + } + + /** + * Creates a new XML file using the template according to the given folder type + * + * @param project the project to create the file in + * @param file the file to be created + * @param folderType the type of folder to look up a template for + * @return the created file + */ + public static Pair<IFile, IRegion> createXmlFile(IProject project, IFile file, + ResourceFolderType folderType) { + TypeInfo type = NewXmlFileCreationPage.getTypeInfo(folderType); + String xmlns = type.getXmlns(); + String root = type.getDefaultRoot(project); + if (root == null) { + root = type.getRootSeed().toString(); + } + String attrs = type.getDefaultAttrs(project, root); + return createXmlFile(file, xmlns, root, attrs, null, folderType); + } + + /** + * Returns an image descriptor for the wizard logo. + */ + private void setImageDescriptor() { + ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + } + + /** + * Specific New XML File wizard tied to the {@link ResourceFolderType#LAYOUT} type + */ + public static class NewLayoutWizard extends NewXmlFileWizard { + /** Creates a new {@link NewLayoutWizard} */ + public NewLayoutWizard() { + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + super.init(workbench, selection); + setWindowTitle("New Android Layout XML File"); + super.mMainPage.setTitle("New Android Layout XML File"); + super.mMainPage.setDescription("Creates a new Android Layout XML file."); + super.mMainPage.setInitialFolderType(ResourceFolderType.LAYOUT); + } + } + + /** + * Specific New XML File wizard tied to the {@link ResourceFolderType#VALUES} type + */ + public static class NewValuesWizard extends NewXmlFileWizard { + /** Creates a new {@link NewValuesWizard} */ + public NewValuesWizard() { + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + super.init(workbench, selection); + setWindowTitle("New Android Values XML File"); + super.mMainPage.setTitle("New Android Values XML File"); + super.mMainPage.setDescription("Creates a new Android Values XML file."); + super.mMainPage.setInitialFolderType(ResourceFolderType.VALUES); + } + } + + /** Value object which holds the current state of the wizard pages */ + public static class Values { + /** The currently selected project, or null */ + public IProject project; + /** The root name of the XML file to create, or null */ + public String name; + /** The type of XML file to create */ + public TypeInfo type; + /** The path within the project to create the new file in */ + public String folderPath; + /** The currently chosen configuration */ + public FolderConfiguration configuration = new FolderConfiguration(); + + /** + * Returns the destination filename or an empty string. + * + * @return the filename, never null. + */ + public String getFileName() { + String fileName; + if (name == null) { + fileName = ""; //$NON-NLS-1$ + } else { + fileName = name.trim(); + if (fileName.length() > 0 && fileName.indexOf('.') == -1) { + fileName = fileName + SdkConstants.DOT_XML; + } + } + + return fileName; + } + + /** + * Returns a {@link IFile} for the destination file. + * <p/> + * Returns null if the project, filename or folder are invalid and the + * destination file cannot be determined. + * <p/> + * The {@link IFile} is a resource. There might or might not be an + * actual real file. + * + * @return an {@link IFile} for the destination file + */ + public IFile getDestinationFile() { + String fileName = getFileName(); + if (project != null && folderPath != null && folderPath.length() > 0 + && fileName.length() > 0) { + IPath dest = new Path(folderPath).append(fileName); + IFile file = project.getFile(dest); + return file; + } + return null; + } + } +} |