• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.layout.properties;
18 
19 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
20 import static com.android.ide.eclipse.adt.AdtConstants.DOT_PNG;
21 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
22 
23 import com.android.annotations.NonNull;
24 import com.android.ide.common.api.IAttributeInfo;
25 import com.android.ide.common.api.IAttributeInfo.Format;
26 import com.android.ide.common.rendering.api.ResourceValue;
27 import com.android.ide.common.resources.ResourceRepository;
28 import com.android.ide.common.resources.ResourceResolver;
29 import com.android.ide.eclipse.adt.AdtPlugin;
30 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
31 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
32 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils;
33 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
34 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils;
35 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
36 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
37 import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog;
38 import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
39 import com.android.ide.eclipse.adt.internal.ui.ResourcePreviewHelper;
40 import com.android.resources.ResourceType;
41 import com.google.common.collect.Maps;
42 
43 import org.eclipse.core.resources.IProject;
44 import org.eclipse.core.runtime.CoreException;
45 import org.eclipse.core.runtime.QualifiedName;
46 import org.eclipse.jface.window.Window;
47 import org.eclipse.swt.graphics.Color;
48 import org.eclipse.swt.graphics.GC;
49 import org.eclipse.swt.graphics.Image;
50 import org.eclipse.swt.graphics.ImageData;
51 import org.eclipse.swt.graphics.Point;
52 import org.eclipse.swt.graphics.RGB;
53 import org.eclipse.swt.widgets.Shell;
54 import org.eclipse.wb.draw2d.IColorConstants;
55 import org.eclipse.wb.internal.core.model.property.Property;
56 import org.eclipse.wb.internal.core.model.property.editor.AbstractTextPropertyEditor;
57 import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
58 import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
59 import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
60 import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
61 
62 import java.awt.image.BufferedImage;
63 import java.io.File;
64 import java.io.IOException;
65 import java.util.ArrayList;
66 import java.util.EnumSet;
67 import java.util.List;
68 import java.util.Map;
69 
70 import javax.imageio.ImageIO;
71 
72 /**
73  * Special property editor used for the {@link XmlProperty} instances which handles
74  * editing the XML properties, rendering defaults by looking up the actual colors and images,
75  */
76 class XmlPropertyEditor extends AbstractTextPropertyEditor {
77     public static final XmlPropertyEditor INSTANCE = new XmlPropertyEditor();
78     private static final int SAMPLE_SIZE = 10;
79     private static final int SAMPLE_MARGIN = 3;
80 
XmlPropertyEditor()81     protected XmlPropertyEditor() {
82     }
83 
84     private final PropertyEditorPresentation mPresentation =
85             new ButtonPropertyEditorPresentation() {
86         @Override
87         protected void onClick(PropertyTable propertyTable, Property property) throws Exception {
88             openDialog(propertyTable, property);
89         }
90     };
91 
92     @Override
getPresentation()93     public PropertyEditorPresentation getPresentation() {
94         return mPresentation;
95     }
96 
97     @Override
getText(Property property)98     public String getText(Property property) throws Exception {
99         Object value = property.getValue();
100         if (value instanceof String) {
101             return (String) value;
102         }
103         return null;
104     }
105 
106     @Override
getEditorText(Property property)107     protected String getEditorText(Property property) throws Exception {
108         return getText(property);
109     }
110 
111     @Override
paint(Property property, GC gc, int x, int y, int width, int height)112     public void paint(Property property, GC gc, int x, int y, int width, int height)
113             throws Exception {
114         String text = getText(property);
115         if (text != null) {
116             ResourceValue resValue = null;
117             String resolvedText = null;
118 
119             // TODO: Use the constants for @, ?, @android: etc
120             if (text.startsWith("@") || text.startsWith("?")) { //$NON-NLS-1$ //$NON-NLS-2$
121                 // Yes, try to resolve it in order to show better info
122                 XmlProperty xmlProperty = (XmlProperty) property;
123                 ResourceResolver resolver = xmlProperty.getGraphicalEditor().getResourceResolver();
124                 boolean isFramework = text.startsWith("@android:") || text.startsWith("?android:");
125                 resValue = resolver.findResValue(text, isFramework);
126                 while (resValue != null && resValue.getValue() != null) {
127                     String value = resValue.getValue();
128                     if (value.startsWith("@") || value.startsWith("?")) {
129                         // TODO: do I have to strip off the @ too?
130                         isFramework = isFramework || value.startsWith("@android:") || value.startsWith("?android:");;
131                         ResourceValue v = resolver.findResValue(text, isFramework);
132                         if (v != null && !value.equals(v.getValue())) {
133                             resValue = v;
134                         } else {
135                             break;
136                         }
137                     } else {
138                         break;
139                     }
140                 }
141             } else if (text.startsWith("#") && text.matches("#\\p{XDigit}+")) { //$NON-NLS-1$
142                 resValue = new ResourceValue(ResourceType.COLOR, property.getName(), text, false);
143             }
144 
145             if (resValue != null && resValue.getValue() != null) {
146                 String value = resValue.getValue();
147                 // Decide whether it's a color, an image, a nine patch etc
148                 // and decide how to render it
149                 if (value.startsWith("#") || value.endsWith(DOT_XML) //$NON-NLS-1$
150                         && value.contains("res/color")) { //$NON-NLS-1$ // TBD: File.separator?
151                     XmlProperty xmlProperty = (XmlProperty) property;
152                     ResourceResolver resolver =
153                             xmlProperty.getGraphicalEditor().getResourceResolver();
154                     RGB rgb = ResourceHelper.resolveColor(resolver, resValue);
155                     if (rgb != null) {
156                         Color color = new Color(gc.getDevice(), rgb);
157                         // draw color sample
158                         Color oldBackground = gc.getBackground();
159                         Color oldForeground = gc.getForeground();
160                         try {
161                             int width_c = SAMPLE_SIZE;
162                             int height_c = SAMPLE_SIZE;
163                             int x_c = x;
164                             int y_c = y + (height - height_c) / 2;
165                             // update rest bounds
166                             int delta = SAMPLE_SIZE + SAMPLE_MARGIN;
167                             x += delta;
168                             width -= delta;
169                             // fill
170                             gc.setBackground(color);
171                             gc.fillRectangle(x_c, y_c, width_c, height_c);
172                             // draw line
173                             gc.setForeground(IColorConstants.gray);
174                             gc.drawRectangle(x_c, y_c, width_c, height_c);
175                         } finally {
176                             gc.setBackground(oldBackground);
177                             gc.setForeground(oldForeground);
178                         }
179                         color.dispose();
180                     }
181                 } else {
182                     Image swtImage = null;
183                     if (value.endsWith(DOT_XML) && value.contains("res/drawable")) { // TBD: Filesep?
184                         Map<String, Image> cache = getImageCache(property);
185                         swtImage = cache.get(value);
186                         if (swtImage == null) {
187                             XmlProperty xmlProperty = (XmlProperty) property;
188                             GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
189                             RenderService service = RenderService.create(graphicalEditor);
190                             service.setSize(SAMPLE_SIZE, SAMPLE_SIZE);
191                             BufferedImage drawable = service.renderDrawable(resValue);
192                             if (drawable != null) {
193                                 swtImage = SwtUtils.convertToSwt(gc.getDevice(), drawable,
194                                         true /*transferAlpha*/, -1);
195                                 cache.put(value, swtImage);
196                             }
197                         }
198                     } else if (value.endsWith(DOT_PNG)) {
199                         // TODO: 9-patch handling?
200                         //if (text.endsWith(DOT_9PNG)) {
201                         //    // 9-patch image: How do we paint this?
202                         //    URL url = new File(text).toURI().toURL();
203                         //    NinePatch ninepatch = NinePatch.load(url, false /* ?? */);
204                         //    BufferedImage image = ninepatch.getImage();
205                         //}
206                         Map<String, Image> cache = getImageCache(property);
207                         swtImage = cache.get(value);
208                         if (swtImage == null) {
209                             File file = new File(value);
210                             if (file.exists()) {
211                                 try {
212                                     BufferedImage awtImage = ImageIO.read(file);
213                                     if (awtImage != null && awtImage.getWidth() > 0
214                                             && awtImage.getHeight() > 0) {
215                                         awtImage = ImageUtils.cropBlank(awtImage, null);
216                                         if (awtImage != null) {
217                                             // Scale image
218                                             int imageWidth = awtImage.getWidth();
219                                             int imageHeight = awtImage.getHeight();
220                                             int maxWidth = 3 * height;
221 
222                                             if (imageWidth > maxWidth || imageHeight > height) {
223                                                 double scale = height / (double) imageHeight;
224                                                 int scaledWidth = (int) (imageWidth * scale);
225                                                 if (scaledWidth > maxWidth) {
226                                                     scale = maxWidth / (double) imageWidth;
227                                                 }
228                                                 awtImage = ImageUtils.scale(awtImage, scale,
229                                                         scale);
230                                             }
231                                             swtImage = SwtUtils.convertToSwt(gc.getDevice(),
232                                                     awtImage, true /*transferAlpha*/, -1);
233                                         }
234                                     }
235                                 } catch (IOException e) {
236                                     AdtPlugin.log(e, value);
237                                 }
238                             }
239                             cache.put(value, swtImage);
240                         }
241 
242                     } else if (value != null) {
243                         // It's a normal string: if different from the text, paint
244                         // it in parentheses, e.g.
245                         //   @string/foo: Foo Bar (probably cropped)
246                         if (!value.equals(text) && !value.equals("@null")) { //$NON-NLS-1$
247                             resolvedText = value;
248                         }
249                     }
250 
251                     if (swtImage != null) {
252                         // Make a square the size of the height
253                         ImageData imageData = swtImage.getImageData();
254                         int imageWidth = imageData.width;
255                         int imageHeight = imageData.height;
256                         if (imageWidth > 0 && imageHeight > 0) {
257                             gc.drawImage(swtImage, x, y + (height - imageHeight) / 2);
258                             int delta = imageWidth + SAMPLE_MARGIN;
259                             x += delta;
260                             width -= delta;
261                         }
262                     }
263                 }
264             }
265 
266             DrawUtils.drawStringCV(gc, text, x, y, width, height);
267 
268             if (resolvedText != null && resolvedText.length() > 0) {
269                 Point size = gc.stringExtent(text);
270                 x += size.x;
271                 width -= size.x;
272 
273                 x += SAMPLE_MARGIN;
274                 width -= SAMPLE_MARGIN;
275 
276                 if (width > 0) {
277                     Color oldForeground = gc.getForeground();
278                     try {
279                         gc.setForeground(PropertyTable.COLOR_PROPERTY_FG_DEFAULT);
280                         DrawUtils.drawStringCV(gc, '(' + resolvedText + ')', x, y, width, height);
281                     } finally {
282                         gc.setForeground(oldForeground);
283                     }
284                 }
285             }
286         }
287     }
288 
289     @Override
setEditorText(Property property, String text)290     protected boolean setEditorText(Property property, String text) throws Exception {
291         property.setValue(text);
292         return true;
293     }
294 
openDialog(PropertyTable propertyTable, Property property)295     private void openDialog(PropertyTable propertyTable, Property property) throws Exception {
296         XmlProperty xmlProperty = (XmlProperty) property;
297         IAttributeInfo attributeInfo = xmlProperty.getDescriptor().getAttributeInfo();
298 
299         boolean isId = xmlProperty.getDescriptor().getXmlLocalName().equals(ATTR_ID);
300         if (isId) {
301             // When editing the id attribute, don't offer a resource chooser: usually
302             // you want to enter a *new* id here
303             attributeInfo = null;
304         }
305 
306         boolean referenceAllowed = false;
307         if (attributeInfo != null) {
308             EnumSet<Format> formats = attributeInfo.getFormats();
309             ResourceType type = null;
310             List<ResourceType> types = null;
311             if (formats.contains(Format.FLAG)) {
312                 FlagXmlPropertyDialog dialog =
313                         new FlagXmlPropertyDialog(propertyTable.getShell(),
314                                 "Select Flag Values", false /* radio */,
315                                 attributeInfo.getFlagValues(), xmlProperty);
316 
317                 dialog.open();
318                 return;
319 
320             } else if (formats.contains(Format.ENUM)) {
321                 FlagXmlPropertyDialog dialog =
322                         new FlagXmlPropertyDialog(propertyTable.getShell(),
323                                 "Select Enum Value", true /* radio */,
324                                 attributeInfo.getEnumValues(), xmlProperty);
325                 dialog.open();
326                 return;
327             } else {
328                 for (Format format : formats) {
329                     ResourceType t = format.getResourceType();
330                     if (t != null) {
331                         if (type != null) {
332                             if (types == null) {
333                                 types = new ArrayList<ResourceType>();
334                                 types.add(type);
335                             }
336                             types.add(t);
337                         }
338                         type = t;
339                     } else if (format == Format.REFERENCE) {
340                         referenceAllowed = true;
341                     }
342                 }
343             }
344             if (types != null || referenceAllowed) {
345                 // Multiple resource types (such as string *and* boolean):
346                 // just use a reference chooser
347                 GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
348                 LayoutEditorDelegate delegate = graphicalEditor.getEditorDelegate();
349                 IProject project = delegate.getEditor().getProject();
350                 if (project != null) {
351                     // get the resource repository for this project and the system resources.
352                     ResourceRepository projectRepository =
353                         ResourceManager.getInstance().getProjectResources(project);
354                     Shell shell = AdtPlugin.getDisplay().getActiveShell();
355                     ReferenceChooserDialog dlg = new ReferenceChooserDialog(
356                             project,
357                             projectRepository,
358                             shell);
359                     dlg.setPreviewHelper(new ResourcePreviewHelper(dlg, graphicalEditor));
360 
361                     String currentValue = (String) property.getValue();
362                     dlg.setCurrentResource(currentValue);
363 
364                     if (dlg.open() == Window.OK) {
365                         String resource = dlg.getCurrentResource();
366                         if (resource != null) {
367                             // Returns null for cancel, "" for clear and otherwise a new value
368                             if (resource.length() > 0) {
369                                 property.setValue(resource);
370                             } else {
371                                 property.setValue(null);
372                             }
373                         }
374                     }
375 
376                     return;
377                 }
378 
379             } else if (type != null) {
380                 // Single resource type: use a resource chooser
381                 GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
382                 String currentValue = (String) property.getValue();
383                 // TODO: Add validator factory?
384                 String resource = ResourceChooser.chooseResource(graphicalEditor,
385                         type, currentValue, null /* validator */);
386                 // Returns null for cancel, "" for clear and otherwise a new value
387                 if (resource != null) {
388                     if (resource.length() > 0) {
389                         property.setValue(resource);
390                     } else {
391                         property.setValue(null);
392                     }
393                 }
394 
395                 return;
396             }
397         }
398 
399         // Fallback: Just use a plain string editor
400         StringXmlPropertyDialog dialog =
401                 new StringXmlPropertyDialog(propertyTable.getShell(), property);
402         if (dialog.open() == Window.OK) {
403             // TODO: Do I need to activate?
404         }
405     }
406 
407     /** Qualified name for the per-project persistent property include-map */
408     private final static QualifiedName CACHE_NAME = new QualifiedName(AdtPlugin.PLUGIN_ID,
409             "property-images");//$NON-NLS-1$
410 
411     @NonNull
getImageCache(@onNull Property property)412     private static Map<String, Image> getImageCache(@NonNull Property property) {
413         XmlProperty xmlProperty = (XmlProperty) property;
414         IProject project = xmlProperty.getGraphicalEditor().getProject();
415         try {
416             Map<String, Image> cache = (Map<String, Image>) project.getSessionProperty(CACHE_NAME);
417             if (cache == null) {
418                 cache = Maps.newHashMap();
419                 project.setSessionProperty(CACHE_NAME, cache);
420             }
421 
422             return cache;
423         } catch (CoreException e) {
424             AdtPlugin.log(e, null);
425             return Maps.newHashMap();
426         }
427     }
428 }
429