/* * Copyright (C) 2011 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 com.android.SdkConstants; import com.android.ide.common.resources.configuration.ResourceQualifier; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode; import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo; import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard.Values; import org.eclipse.core.resources.IFile; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.wizard.IWizardPage; 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.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; /** * Second page of the {@link NewXmlFileWizard}. * <p> * This page is used for choosing the current configuration or specific resource * folder. */ public class ChooseConfigurationPage extends WizardPage { private Values mValues; private Text mWsFolderPathTextField; private ConfigurationSelector mConfigSelector; private boolean mInternalWsFolderPathUpdate; private boolean mInternalConfigSelectorUpdate; /** Absolute destination folder root, e.g. "/res/" */ static final String RES_FOLDER_ABS = AdtConstants.WS_RESOURCES + AdtConstants.WS_SEP; /** Relative destination folder root, e.g. "res/" */ static final String RES_FOLDER_REL = SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP; /** * Create the wizard. * * @param values value object holding current wizard state */ public ChooseConfigurationPage(NewXmlFileWizard.Values values) { super("chooseConfig"); mValues = values; setTitle("Choose Configuration Folder"); } @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { if (mValues.folderPath != null) { mWsFolderPathTextField.setText(mValues.folderPath); } } } @Override 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 configuration selector Label label = new Label(composite, SWT.NONE); label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); label.setText("Optional: Choose a specific configuration to limit the XML to:"); // configuration selector mConfigSelector = new ConfigurationSelector(composite, SelectorMode.DEFAULT); GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); gd.verticalAlignment = SWT.FILL; gd.horizontalAlignment = SWT.FILL; gd.horizontalSpan = 2; gd.heightHint = ConfigurationSelector.HEIGHT_HINT; mConfigSelector.setLayoutData(gd); mConfigSelector.setOnChangeListener(new ConfigurationChangeListener()); // Folder name: [text] String tooltip = "The folder where the file will be generated, relative to the project."; Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); GridData gdSeparator = new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1); gdSeparator.heightHint = 10; separator.setLayoutData(gdSeparator); Label folderLabel = new Label(composite, SWT.NONE); folderLabel.setText("Folder:"); folderLabel.setToolTipText(tooltip); mWsFolderPathTextField = new Text(composite, SWT.BORDER); mWsFolderPathTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mWsFolderPathTextField.setToolTipText(tooltip); mWsFolderPathTextField.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { onWsFolderPathUpdated(); } }); setControl(composite); mConfigSelector.setConfiguration(mValues.configuration); } /** * Callback called when the Folder text field is changed, either programmatically * or by the user. */ private void onWsFolderPathUpdated() { if (mInternalWsFolderPathUpdate) { return; } String wsFolderPath = mWsFolderPathTextField.getText(); // This is a custom path, we need to sanitize it. // First it should start with "/res/". Then we need to make sure there are no // relative paths, things like "../" or "./" or even "//". wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$ wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$ wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ // We get "res/foo" from selections relative to the project when we want a "/res/foo" path. if (wsFolderPath.startsWith(RES_FOLDER_REL)) { wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length()); mInternalWsFolderPathUpdate = true; mWsFolderPathTextField.setText(wsFolderPath); mInternalWsFolderPathUpdate = false; } mValues.folderPath = wsFolderPath; if (wsFolderPath.startsWith(RES_FOLDER_ABS)) { wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length()); int pos = wsFolderPath.indexOf(AdtConstants.WS_SEP_CHAR); if (pos >= 0) { wsFolderPath = wsFolderPath.substring(0, pos); } String[] folderSegments = wsFolderPath.split(SdkConstants.RES_QUALIFIER_SEP); if (folderSegments.length > 0) { String folderName = folderSegments[0]; // update config selector mInternalConfigSelectorUpdate = true; mConfigSelector.setConfiguration(folderSegments); mInternalConfigSelectorUpdate = false; IWizardPage previous = ((NewXmlFileWizard) getWizard()).getPreviousPage(this); if (previous instanceof NewXmlFileCreationPage) { NewXmlFileCreationPage p = (NewXmlFileCreationPage) previous; p.selectTypeFromFolder(folderName); } } } validatePage(); } /** * Callback called when the configuration has changed in the {@link ConfigurationSelector}. */ private class ConfigurationChangeListener implements Runnable { @Override public void run() { if (mInternalConfigSelectorUpdate) { return; } resetFolderPath(true /*validate*/); } } /** * Reset the current Folder path based on the UI selection * @param validate if true, force a call to {@link #validatePage()}. */ private void resetFolderPath(boolean validate) { TypeInfo type = mValues.type; if (type != null) { mConfigSelector.getConfiguration(mValues.configuration); StringBuilder sb = new StringBuilder(RES_FOLDER_ABS); sb.append(mValues.configuration.getFolderName(type.getResFolderType())); mInternalWsFolderPathUpdate = true; String newPath = sb.toString(); mValues.folderPath = newPath; mWsFolderPathTextField.setText(newPath); mInternalWsFolderPathUpdate = false; if (validate) { validatePage(); } } } /** * Returns the destination folder path relative to the project or an empty string. * * @return the currently edited folder */ public String getWsFolderPath() { return mWsFolderPathTextField == null ? "" : mWsFolderPathTextField.getText(); //$NON-NLS-1$ } /** * 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 folder configuration if (error == null) { ConfigurationState state = mConfigSelector.getState(); if (state == ConfigurationState.INVALID_CONFIG) { ResourceQualifier qual = mConfigSelector.getInvalidQualifier(); if (qual != null) { error = String.format("The qualifier '%1$s' is invalid in the folder configuration.", qual.getName()); } } else if (state == ConfigurationState.REGION_WITHOUT_LANGUAGE) { error = "The Region qualifier requires the Language qualifier."; } } // -- validate generated path if (error == null) { String wsFolderPath = getWsFolderPath(); if (!wsFolderPath.startsWith(RES_FOLDER_ABS)) { error = String.format("Target folder must start with %1$s.", RES_FOLDER_ABS); } } // -- 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); } } }