/* * Copyright (C) 2007 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.ui; import static com.android.ide.eclipse.adt.AndroidConstants.EXT_XML; import static com.android.ide.eclipse.adt.AndroidConstants.WS_SEP; import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; import static com.android.sdklib.SdkConstants.FD_RESOURCES; import static com.android.sdklib.SdkConstants.FD_VALUES; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.ide.eclipse.adt.internal.resources.ResourceItem; 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.resources.ResourceType; 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.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.window.Window; import org.eclipse.ltk.ui.refactoring.RefactoringWizard; import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; import org.eclipse.ui.dialogs.SelectionStatusDialog; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A dialog to let the user select a resource based on a resource type. */ @SuppressWarnings("restriction") // XML model public class ResourceChooser extends AbstractElementListSelectionDialog { private Pattern mProjectResourcePattern; private ResourceType mResourceType; private IResourceRepository mProjectResources; private Pattern mSystemResourcePattern; private Button mProjectButton; private Button mSystemButton; private Button mNewButton; private String mCurrentResource; private final IProject mProject; /** * Creates a Resource Chooser dialog. * @param project Project being worked on * @param type The type of the resource to choose * @param projectResources The repository for the project * @param systemResources The System resource repository * @param parent the parent shell */ public ResourceChooser(IProject project, ResourceType type, IResourceRepository projectResources, IResourceRepository systemResources, Shell parent) { super(parent, new ResourceLabelProvider()); mProject = project; mResourceType = type; mProjectResources = projectResources; mProjectResourcePattern = Pattern.compile( "@" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ mSystemResourcePattern = Pattern.compile( "@android:" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ setTitle("Resource Chooser"); setMessage(String.format("Choose a %1$s resource", mResourceType.getDisplayName().toLowerCase())); } public void setCurrentResource(String resource) { mCurrentResource = resource; } public String getCurrentResource() { return mCurrentResource; } @Override protected void computeResult() { Object[] elements = getSelectedElements(); if (elements.length == 1 && elements[0] instanceof ResourceItem) { ResourceItem item = (ResourceItem)elements[0]; mCurrentResource = ResourceHelper.getXmlString(mResourceType, item, mSystemButton.getSelection()); } } @Override protected Control createDialogArea(Composite parent) { Composite top = (Composite)super.createDialogArea(parent); createMessageArea(top); createButtons(top); createFilterText(top); createFilteredList(top); // create the "New Resource" button createNewResButtons(top); setupResourceList(); selectResourceString(mCurrentResource); return top; } /** * Creates the radio button to switch between project and system resources. * @param top the parent composite */ private void createButtons(Composite top) { mProjectButton = new Button(top, SWT.RADIO); mProjectButton.setText("Project Resources"); mProjectButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); if (mProjectButton.getSelection()) { setupResourceList(); mNewButton.setEnabled(true); } } }); mSystemButton = new Button(top, SWT.RADIO); mSystemButton.setText("System Resources"); mSystemButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); if (mSystemButton.getSelection()) { setupResourceList(); mNewButton.setEnabled(false); } } }); } /** * Creates the "New Resource" button. * @param top the parent composite */ private void createNewResButtons(Composite top) { mNewButton = new Button(top, SWT.NONE); String title = String.format("New %1$s...", mResourceType.getDisplayName()); mNewButton.setText(title); // We only support adding new values right now mNewButton.setEnabled(ResourceNameValidator.isValueBasedResourceType(mResourceType)); mNewButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); if (mResourceType == ResourceType.STRING) { createNewString(); } else { assert ResourceNameValidator.isValueBasedResourceType(mResourceType); String newName = createNewValue(mResourceType); if (newName != null) { // Recompute the "current resource" to select the new id ResourceItem[] items = setupResourceList(); selectItemName(newName, items); } } } }); } private String createNewValue(ResourceType type) { // Show a name/value dialog entering the key name and the value Shell shell = AdtPlugin.getDisplay().getActiveShell(); if (shell == null) { return null; } NameValueDialog dialog = new NameValueDialog(shell, getFilter()); if (dialog.open() != Window.OK) { return null; } String name = dialog.getName(); String value = dialog.getValue(); if (name.length() == 0 || value.length() == 0) { return null; } // Find "dimens.xml" file in res/values/ (or corresponding name for other // value types) String fileName = type.getName() + 's'; String projectPath = FD_RESOURCES + WS_SEP + FD_VALUES + WS_SEP + fileName + '.' + EXT_XML; IResource member = mProject.findMember(projectPath); if (member != null) { if (member instanceof IFile) { IFile file = (IFile) member; // File exists: Must add item to the XML IModelManager manager = StructuredModelManager.getModelManager(); IStructuredModel model = null; try { model = manager.getExistingModelForEdit(file); if (model == null) { model = manager.getModelForEdit(file); } if (model instanceof IDOMModel) { model.beginRecording(this, String.format("Add %1$s", type.getDisplayName())); IDOMModel domModel = (IDOMModel) model; Document document = domModel.getDocument(); Element root = document.getDocumentElement(); IStructuredDocument structuredDocument = model.getStructuredDocument(); Node lastElement = null; NodeList childNodes = root.getChildNodes(); String indent = null; for (int i = childNodes.getLength() - 1; i >= 0; i--) { Node node = childNodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { lastElement = node; indent = AndroidXmlEditor.getIndent(structuredDocument, node); break; } } if (indent == null || indent.length() == 0) { indent = " "; //$NON-NLS-1$ } Node nextChild = lastElement != null ? lastElement.getNextSibling() : null; Text indentNode = document.createTextNode('\n' + indent); root.insertBefore(indentNode, nextChild); Element element = document.createElement(Hyperlinks.getTagName(type)); element.setAttribute(NAME_ATTR, name); root.insertBefore(element, nextChild); Text valueNode = document.createTextNode(value); element.appendChild(valueNode); model.save(); return name; } } catch (Exception e) { AdtPlugin.log(e, "Cannot access XML value model"); } finally { if (model != null) { model.endRecording(this); model.releaseFromEdit(); } } } return null; } else { // No such file exists: just create it String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ StringBuilder sb = new StringBuilder(prolog); String root = ResourcesDescriptors.ROOT_ELEMENT; sb.append('<').append(root).append('>').append('\n'); sb.append(" "); //$NON-NLS-1$ sb.append('<'); sb.append(type.getName()); sb.append(" name=\""); //$NON-NLS-1$ sb.append(name); sb.append('"'); sb.append('>'); sb.append(value); sb.append('<').append('/'); sb.append(type.getName()); sb.append(">\n"); //$NON-NLS-1$ sb.append('<').append('/').append(root).append('>').append('\n'); String result = sb.toString(); String error = null; try { byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$ InputStream stream = new ByteArrayInputStream(buf); IFile file = mProject.getFile(new Path(projectPath)); file.create(stream, true /*force*/, null /*progress*/); return name; } 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; } private void createNewString() { ExtractStringRefactoring ref = new ExtractStringRefactoring( mProject, true /*enforceNew*/); RefactoringWizard wizard = new ExtractStringWizard(ref, mProject); RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); try { IWorkbench w = PlatformUI.getWorkbench(); if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) == IDialogConstants.OK_ID) { // Recompute the "current resource" to select the new id ResourceItem[] items = setupResourceList(); // select it if possible selectItemName(ref.getXmlStringId(), items); } } catch (InterruptedException ex) { // Interrupted. Pass. } } /** * Setups the current list. */ private ResourceItem[] setupResourceList() { ResourceItem[] items = null; if (mProjectButton.getSelection()) { items = mProjectResources.getResources(mResourceType); setListElements(items); } else if (mSystemButton.getSelection()) { AndroidTargetData targetData = Sdk.getCurrent().getTargetData(mProject); if (targetData != null) { Collection<String> names = targetData.getPublicResourceNames(mResourceType); List<ResourceItem> list = new ArrayList<ResourceItem>(); for (String name : names) { list.add(new ResourceItem(name)); } Collections.sort(list); items = list.toArray(new ResourceItem[list.size()]); setListElements(items); } } return items; } /** * Select an item by its name, if possible. */ private void selectItemName(String itemName, ResourceItem[] items) { if (itemName == null || items == null) { return; } for (ResourceItem item : items) { if (itemName.equals(item.getName())) { setSelection(new Object[] { item }); break; } } } /** * Select an item by its full resource string. * This also selects between project and system repository based on the resource string. */ private void selectResourceString(String resourceString) { boolean isSystem = false; String itemName = null; // Is this a system resource? // If not a system resource or if they are not available, this will be a project res. Matcher m = mSystemResourcePattern.matcher(resourceString); if (m.matches()) { itemName = m.group(1); isSystem = true; } if (!isSystem && itemName == null) { // Try to match project resource name m = mProjectResourcePattern.matcher(resourceString); if (m.matches()) { itemName = m.group(1); } } // Update the repository selection mProjectButton.setSelection(!isSystem); mSystemButton.setSelection(isSystem); mNewButton.setEnabled(!isSystem); // Update the list ResourceItem[] items = setupResourceList(); // If we have a selection name, select it if (itemName != null) { selectItemName(itemName, items); } } /** Dialog asking for a Name/Value pair */ private class NameValueDialog extends SelectionStatusDialog implements Listener { private org.eclipse.swt.widgets.Text mNameText; private org.eclipse.swt.widgets.Text mValueText; private String mInitialName; private String mName; private String mValue; private ResourceNameValidator mValidator; public NameValueDialog(Shell parent, String initialName) { super(parent); mInitialName = initialName; } @Override protected Control createDialogArea(Composite parent) { Composite container = new Composite(parent, SWT.NONE); container.setLayout(new GridLayout(2, false)); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); // Wide enough to accommodate the error label gridData.widthHint = 500; container.setLayoutData(gridData); Label nameLabel = new Label(container, SWT.NONE); nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); nameLabel.setText("Name:"); mNameText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); if (mInitialName != null) { mNameText.setText(mInitialName); mNameText.selectAll(); } Label valueLabel = new Label(container, SWT.NONE); valueLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); valueLabel.setText("Value:"); mValueText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); mValueText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); mNameText.addListener(SWT.Modify, this); mValueText.addListener(SWT.Modify, this); validate(); return container; } @Override protected void computeResult() { mName = mNameText.getText().trim(); mValue = mValueText.getText().trim(); } private String getName() { return mName; } private String getValue() { return mValue; } public void handleEvent(Event event) { validate(); } private void validate() { IStatus status; computeResult(); if (mName.length() == 0) { status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a name"); } else if (mValue.length() == 0) { status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a value"); } else { if (mValidator == null) { mValidator = ResourceNameValidator.create(false, mProject, mResourceType); } String error = mValidator.isValid(mName); if (error != null) { status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); } else { status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); } } updateStatus(status); } } }