aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
diff options
context:
space:
mode:
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.java548
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();
+ }
+ }
+}