diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java new file mode 100644 index 000000000..87fb0e6ed --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2012 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.editors.layout.properties; + +import static com.android.SdkConstants.ANDROID_PREFIX; +import static com.android.SdkConstants.ANDROID_THEME_PREFIX; +import static com.android.SdkConstants.ATTR_ID; +import static com.android.SdkConstants.DOT_PNG; +import static com.android.SdkConstants.DOT_XML; +import static com.android.SdkConstants.NEW_ID_PREFIX; +import static com.android.SdkConstants.PREFIX_RESOURCE_REF; +import static com.android.SdkConstants.PREFIX_THEME_REF; +import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix; + +import com.android.annotations.NonNull; +import com.android.ide.common.api.IAttributeInfo; +import com.android.ide.common.api.IAttributeInfo.Format; +import com.android.ide.common.layout.BaseViewRule; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard; +import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog; +import com.android.ide.eclipse.adt.internal.ui.ResourceChooser; +import com.android.ide.eclipse.adt.internal.ui.ResourcePreviewHelper; +import com.android.resources.ResourceType; +import com.google.common.collect.Maps; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialogWithToggle; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.wb.draw2d.IColorConstants; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.AbstractTextPropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.utils.ui.DrawUtils; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +/** + * Special property editor used for the {@link XmlProperty} instances which handles + * editing the XML properties, rendering defaults by looking up the actual colors and images, + */ +class XmlPropertyEditor extends AbstractTextPropertyEditor { + public static final XmlPropertyEditor INSTANCE = new XmlPropertyEditor(); + private static final int SAMPLE_SIZE = 10; + private static final int SAMPLE_MARGIN = 3; + + protected XmlPropertyEditor() { + } + + private final PropertyEditorPresentation mPresentation = + new ButtonPropertyEditorPresentation() { + @Override + protected void onClick(PropertyTable propertyTable, Property property) throws Exception { + openDialog(propertyTable, property); + } + }; + + @Override + public PropertyEditorPresentation getPresentation() { + return mPresentation; + } + + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof String) { + return (String) value; + } + return null; + } + + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + public void paint(Property property, GC gc, int x, int y, int width, int height) + throws Exception { + String text = getText(property); + if (text != null) { + ResourceValue resValue = null; + String resolvedText = null; + + // TODO: Use the constants for @, ?, @android: etc + if (text.startsWith("@") || text.startsWith("?")) { //$NON-NLS-1$ //$NON-NLS-2$ + // Yes, try to resolve it in order to show better info + XmlProperty xmlProperty = (XmlProperty) property; + GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor(); + if (graphicalEditor != null) { + ResourceResolver resolver = graphicalEditor.getResourceResolver(); + boolean isFramework = text.startsWith(ANDROID_PREFIX) + || text.startsWith(ANDROID_THEME_PREFIX); + resValue = resolver.findResValue(text, isFramework); + while (resValue != null && resValue.getValue() != null) { + String value = resValue.getValue(); + if (value.startsWith(PREFIX_RESOURCE_REF) + || value.startsWith(PREFIX_THEME_REF)) { + // TODO: do I have to strip off the @ too? + isFramework = isFramework + || value.startsWith(ANDROID_PREFIX) + || value.startsWith(ANDROID_THEME_PREFIX); + ResourceValue v = resolver.findResValue(text, isFramework); + if (v != null && !value.equals(v.getValue())) { + resValue = v; + } else { + break; + } + } else { + break; + } + } + } + } else if (text.startsWith("#") && text.matches("#\\p{XDigit}+")) { //$NON-NLS-1$ + resValue = new ResourceValue(ResourceType.COLOR, property.getName(), text, false); + } + + if (resValue != null && resValue.getValue() != null) { + String value = resValue.getValue(); + // Decide whether it's a color, an image, a nine patch etc + // and decide how to render it + if (value.startsWith("#") || value.endsWith(DOT_XML) //$NON-NLS-1$ + && value.contains("res/color")) { //$NON-NLS-1$ // TBD: File.separator? + XmlProperty xmlProperty = (XmlProperty) property; + GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor(); + if (graphicalEditor != null) { + ResourceResolver resolver = graphicalEditor.getResourceResolver(); + RGB rgb = ResourceHelper.resolveColor(resolver, resValue); + if (rgb != null) { + Color color = new Color(gc.getDevice(), rgb); + // draw color sample + Color oldBackground = gc.getBackground(); + Color oldForeground = gc.getForeground(); + try { + int width_c = SAMPLE_SIZE; + int height_c = SAMPLE_SIZE; + int x_c = x; + int y_c = y + (height - height_c) / 2; + // update rest bounds + int delta = SAMPLE_SIZE + SAMPLE_MARGIN; + x += delta; + width -= delta; + // fill + gc.setBackground(color); + gc.fillRectangle(x_c, y_c, width_c, height_c); + // draw line + gc.setForeground(IColorConstants.gray); + gc.drawRectangle(x_c, y_c, width_c, height_c); + } finally { + gc.setBackground(oldBackground); + gc.setForeground(oldForeground); + } + color.dispose(); + } + } + } else { + Image swtImage = null; + if (value.endsWith(DOT_XML) && value.contains("res/drawable")) { // TBD: Filesep? + Map<String, Image> cache = getImageCache(property); + swtImage = cache.get(value); + if (swtImage == null) { + XmlProperty xmlProperty = (XmlProperty) property; + GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor(); + RenderService service = RenderService.create(graphicalEditor); + service.setOverrideRenderSize(SAMPLE_SIZE, SAMPLE_SIZE); + BufferedImage drawable = service.renderDrawable(resValue); + if (drawable != null) { + swtImage = SwtUtils.convertToSwt(gc.getDevice(), drawable, + true /*transferAlpha*/, -1); + cache.put(value, swtImage); + } + } + } else if (value.endsWith(DOT_PNG)) { + // TODO: 9-patch handling? + //if (text.endsWith(DOT_9PNG)) { + // // 9-patch image: How do we paint this? + // URL url = new File(text).toURI().toURL(); + // NinePatch ninepatch = NinePatch.load(url, false /* ?? */); + // BufferedImage image = ninepatch.getImage(); + //} + Map<String, Image> cache = getImageCache(property); + swtImage = cache.get(value); + if (swtImage == null) { + File file = new File(value); + if (file.exists()) { + try { + BufferedImage awtImage = ImageIO.read(file); + if (awtImage != null && awtImage.getWidth() > 0 + && awtImage.getHeight() > 0) { + awtImage = ImageUtils.cropBlank(awtImage, null); + if (awtImage != null) { + // Scale image + int imageWidth = awtImage.getWidth(); + int imageHeight = awtImage.getHeight(); + int maxWidth = 3 * height; + + if (imageWidth > maxWidth || imageHeight > height) { + double scale = height / (double) imageHeight; + int scaledWidth = (int) (imageWidth * scale); + if (scaledWidth > maxWidth) { + scale = maxWidth / (double) imageWidth; + } + awtImage = ImageUtils.scale(awtImage, scale, + scale); + } + swtImage = SwtUtils.convertToSwt(gc.getDevice(), + awtImage, true /*transferAlpha*/, -1); + } + } + } catch (IOException e) { + AdtPlugin.log(e, value); + } + } + cache.put(value, swtImage); + } + + } else if (value != null) { + // It's a normal string: if different from the text, paint + // it in parentheses, e.g. + // @string/foo: Foo Bar (probably cropped) + if (!value.equals(text) && !value.equals("@null")) { //$NON-NLS-1$ + resolvedText = value; + } + } + + if (swtImage != null) { + // Make a square the size of the height + ImageData imageData = swtImage.getImageData(); + int imageWidth = imageData.width; + int imageHeight = imageData.height; + if (imageWidth > 0 && imageHeight > 0) { + gc.drawImage(swtImage, x, y + (height - imageHeight) / 2); + int delta = imageWidth + SAMPLE_MARGIN; + x += delta; + width -= delta; + } + } + } + } + + DrawUtils.drawStringCV(gc, text, x, y, width, height); + + if (resolvedText != null && resolvedText.length() > 0) { + Point size = gc.stringExtent(text); + x += size.x; + width -= size.x; + + x += SAMPLE_MARGIN; + width -= SAMPLE_MARGIN; + + if (width > 0) { + Color oldForeground = gc.getForeground(); + try { + gc.setForeground(PropertyTable.COLOR_PROPERTY_FG_DEFAULT); + DrawUtils.drawStringCV(gc, '(' + resolvedText + ')', x, y, width, height); + } finally { + gc.setForeground(oldForeground); + } + } + } + } + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + Object oldValue = property.getValue(); + String old = oldValue != null ? oldValue.toString() : null; + + // If users enters a new id without specifying the @id/@+id prefix, insert it + boolean isId = isIdProperty(property); + if (isId && !text.startsWith(PREFIX_RESOURCE_REF)) { + text = NEW_ID_PREFIX + text; + } + + // Handle id refactoring: if you change an id, may want to update references too. + // Ask user. + if (isId && property instanceof XmlProperty + && old != null && !old.isEmpty() + && text != null && !text.isEmpty() + && !text.equals(old)) { + XmlProperty xmlProperty = (XmlProperty) property; + IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + String refactorPref = store.getString(AdtPrefs.PREFS_REFACTOR_IDS); + boolean performRefactor = false; + Shell shell = AdtPlugin.getShell(); + if (refactorPref == null + || refactorPref.isEmpty() + || refactorPref.equals(MessageDialogWithToggle.PROMPT)) { + MessageDialogWithToggle dialog = + MessageDialogWithToggle.openYesNoCancelQuestion( + shell, + "Update References?", + "Update all references as well? " + + "This will update all XML references and Java R field references.", + "Do not show again", + false, + store, + AdtPrefs.PREFS_REFACTOR_IDS); + switch (dialog.getReturnCode()) { + case IDialogConstants.CANCEL_ID: + return false; + case IDialogConstants.YES_ID: + performRefactor = true; + break; + case IDialogConstants.NO_ID: + performRefactor = false; + break; + } + } else { + performRefactor = refactorPref.equals(MessageDialogWithToggle.ALWAYS); + } + if (performRefactor) { + CommonXmlEditor xmlEditor = xmlProperty.getXmlEditor(); + if (xmlEditor != null) { + IProject project = xmlEditor.getProject(); + if (project != null && shell != null) { + RenameResourceWizard.renameResource(shell, project, + ResourceType.ID, stripIdPrefix(old), stripIdPrefix(text), false); + } + } + } + } + + property.setValue(text); + + return true; + } + + private static boolean isIdProperty(Property property) { + XmlProperty xmlProperty = (XmlProperty) property; + return xmlProperty.getDescriptor().getXmlLocalName().equals(ATTR_ID); + } + + private void openDialog(PropertyTable propertyTable, Property property) throws Exception { + XmlProperty xmlProperty = (XmlProperty) property; + IAttributeInfo attributeInfo = xmlProperty.getDescriptor().getAttributeInfo(); + + if (isIdProperty(property)) { + Object value = xmlProperty.getValue(); + if (value != null && !value.toString().isEmpty()) { + GraphicalEditorPart editor = xmlProperty.getGraphicalEditor(); + if (editor != null) { + LayoutCanvas canvas = editor.getCanvasControl(); + SelectionManager manager = canvas.getSelectionManager(); + + NodeProxy primary = canvas.getNodeFactory().create(xmlProperty.getNode()); + if (primary != null) { + RenameResult result = manager.performRename(primary, null); + if (result.isCanceled()) { + return; + } else if (!result.isUnavailable()) { + String name = result.getName(); + String id = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(name); + xmlProperty.setValue(id); + return; + } + } + } + } + + // When editing the id attribute, don't offer a resource chooser: usually + // you want to enter a *new* id here + attributeInfo = null; + } + + boolean referenceAllowed = false; + if (attributeInfo != null) { + EnumSet<Format> formats = attributeInfo.getFormats(); + ResourceType type = null; + List<ResourceType> types = null; + if (formats.contains(Format.FLAG)) { + String[] flagValues = attributeInfo.getFlagValues(); + if (flagValues != null) { + FlagXmlPropertyDialog dialog = + new FlagXmlPropertyDialog(propertyTable.getShell(), + "Select Flag Values", false /* radio */, + flagValues, xmlProperty); + + dialog.open(); + return; + } + } else if (formats.contains(Format.ENUM)) { + String[] enumValues = attributeInfo.getEnumValues(); + if (enumValues != null) { + FlagXmlPropertyDialog dialog = + new FlagXmlPropertyDialog(propertyTable.getShell(), + "Select Enum Value", true /* radio */, + enumValues, xmlProperty); + dialog.open(); + return; + } + } else { + for (Format format : formats) { + ResourceType t = format.getResourceType(); + if (t != null) { + if (type != null) { + if (types == null) { + types = new ArrayList<ResourceType>(); + types.add(type); + } + types.add(t); + } + type = t; + } else if (format == Format.REFERENCE) { + referenceAllowed = true; + } + } + } + if (types != null || referenceAllowed) { + // Multiple resource types (such as string *and* boolean): + // just use a reference chooser + GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor(); + if (graphicalEditor != null) { + LayoutEditorDelegate delegate = graphicalEditor.getEditorDelegate(); + IProject project = delegate.getEditor().getProject(); + if (project != null) { + // get the resource repository for this project and the system resources. + ResourceRepository projectRepository = + ResourceManager.getInstance().getProjectResources(project); + Shell shell = AdtPlugin.getShell(); + ReferenceChooserDialog dlg = new ReferenceChooserDialog( + project, + projectRepository, + shell); + dlg.setPreviewHelper(new ResourcePreviewHelper(dlg, graphicalEditor)); + + String currentValue = (String) property.getValue(); + dlg.setCurrentResource(currentValue); + + if (dlg.open() == Window.OK) { + String resource = dlg.getCurrentResource(); + if (resource != null) { + // Returns null for cancel, "" for clear and otherwise a new value + if (resource.length() > 0) { + property.setValue(resource); + } else { + property.setValue(null); + } + } + } + + return; + } + } + } else if (type != null) { + // Single resource type: use a resource chooser + GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor(); + if (graphicalEditor != null) { + String currentValue = (String) property.getValue(); + // TODO: Add validator factory? + String resource = ResourceChooser.chooseResource(graphicalEditor, + type, currentValue, null /* validator */); + // Returns null for cancel, "" for clear and otherwise a new value + if (resource != null) { + if (resource.length() > 0) { + property.setValue(resource); + } else { + property.setValue(null); + } + } + } + + return; + } + } + + // Fallback: Just use a plain string editor + StringXmlPropertyDialog dialog = + new StringXmlPropertyDialog(propertyTable.getShell(), property); + if (dialog.open() == Window.OK) { + // TODO: Do I need to activate? + } + } + + /** Qualified name for the per-project persistent property include-map */ + private final static QualifiedName CACHE_NAME = new QualifiedName(AdtPlugin.PLUGIN_ID, + "property-images");//$NON-NLS-1$ + + @NonNull + private static Map<String, Image> getImageCache(@NonNull Property property) { + XmlProperty xmlProperty = (XmlProperty) property; + GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor(); + IProject project = graphicalEditor.getProject(); + try { + Map<String, Image> cache = (Map<String, Image>) project.getSessionProperty(CACHE_NAME); + if (cache == null) { + cache = Maps.newHashMap(); + project.setSessionProperty(CACHE_NAME, cache); + } + + return cache; + } catch (CoreException e) { + AdtPlugin.log(e, null); + return Maps.newHashMap(); + } + } +} |