| /* |
| * 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(); |
| } |
| } |
| } |