blob: 16cd7b355d3379c8c681551627af20b51ea5aec8 [file] [log] [blame]
/*
* 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;
}
}
}