• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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 
18 package com.android.ide.eclipse.adt.internal.editors;
19 
20 import com.android.annotations.NonNull;
21 import com.android.annotations.Nullable;
22 import com.android.ide.eclipse.adt.AdtPlugin;
23 import com.android.sdklib.SdkConstants;
24 
25 import org.eclipse.jface.resource.ImageDescriptor;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.graphics.Color;
28 import org.eclipse.swt.graphics.Font;
29 import org.eclipse.swt.graphics.FontData;
30 import org.eclipse.swt.graphics.GC;
31 import org.eclipse.swt.graphics.Image;
32 import org.eclipse.swt.graphics.ImageData;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.graphics.RGB;
35 import org.eclipse.swt.widgets.Display;
36 import org.eclipse.ui.plugin.AbstractUIPlugin;
37 
38 import java.net.URL;
39 import java.util.HashMap;
40 
41 /**
42  * Factory to generate icons for Android Editors.
43  * <p/>
44  * Icons are kept here and reused.
45  */
46 public class IconFactory {
47     public static final int COLOR_RED     = SWT.COLOR_DARK_RED;
48     public static final int COLOR_GREEN   = SWT.COLOR_DARK_GREEN;
49     public static final int COLOR_BLUE    = SWT.COLOR_DARK_BLUE;
50     public static final int COLOR_DEFAULT = SWT.COLOR_BLACK;
51 
52     public static final int SHAPE_CIRCLE  = 'C';
53     public static final int SHAPE_RECT    = 'R';
54     public static final int SHAPE_DEFAULT = SHAPE_CIRCLE;
55 
56     private static IconFactory sInstance;
57 
58     private HashMap<String, Image> mIconMap = new HashMap<String, Image>();
59     private HashMap<URL, Image> mUrlMap = new HashMap<URL, Image>();
60     private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>();
61 
IconFactory()62     private IconFactory() {
63     }
64 
getInstance()65     public static synchronized IconFactory getInstance() {
66         if (sInstance == null) {
67             sInstance = new IconFactory();
68         }
69         return sInstance;
70     }
71 
dispose()72     public void dispose() {
73         // Dispose icons
74         for (Image icon : mIconMap.values()) {
75             // The map can contain null values
76             if (icon != null) {
77                 icon.dispose();
78             }
79         }
80         mIconMap.clear();
81         for (Image icon : mUrlMap.values()) {
82             // The map can contain null values
83             if (icon != null) {
84                 icon.dispose();
85             }
86         }
87         mUrlMap.clear();
88     }
89 
90     /**
91      * Returns an Image for a given icon name.
92      * <p/>
93      * Callers should not dispose it.
94      *
95      * @param osName The leaf name, without the extension, of an existing icon in the
96      *        editor's "icons" directory. If it doesn't exists, a default icon will be
97      *        generated automatically based on the name.
98      */
getIcon(String osName)99     public Image getIcon(String osName) {
100         return getIcon(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
101     }
102 
103     /**
104      * Returns an Image for a given icon name.
105      * <p/>
106      * Callers should not dispose it.
107      *
108      * @param osName The leaf name, without the extension, of an existing icon in the
109      *        editor's "icons" directory. If it doesn't exist, a default icon will be
110      *        generated automatically based on the name.
111      * @param color The color of the text in the automatically generated icons,
112      *        one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
113      * @param shape The shape of the icon in the automatically generated icons,
114      *        one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
115      */
getIcon(String osName, int color, int shape)116     public Image getIcon(String osName, int color, int shape) {
117         String key = Character.toString((char) shape) + Integer.toString(color) + osName;
118         Image icon = mIconMap.get(key);
119         if (icon == null && !mIconMap.containsKey(key)) {
120             ImageDescriptor id = getImageDescriptor(osName, color, shape);
121             if (id != null) {
122                 icon = id.createImage();
123             }
124             // Note that we store null references in the icon map, to avoid looking them
125             // up every time. If it didn't exist once, it will not exist later.
126             mIconMap.put(key, icon);
127         }
128         return icon;
129     }
130 
131     /**
132      * Returns an ImageDescriptor for a given icon name.
133      * <p/>
134      * Callers should not dispose it.
135      *
136      * @param osName The leaf name, without the extension, of an existing icon in the
137      *        editor's "icons" directory. If it doesn't exists, a default icon will be
138      *        generated automatically based on the name.
139      */
getImageDescriptor(String osName)140     public ImageDescriptor getImageDescriptor(String osName) {
141         return getImageDescriptor(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
142     }
143 
144     /**
145      * Returns an ImageDescriptor for a given icon name.
146      * <p/>
147      * Callers should not dispose it.
148      *
149      * @param osName The leaf name, without the extension, of an existing icon in the
150      *        editor's "icons" directory. If it doesn't exists, a default icon will be
151      *        generated automatically based on the name.
152      * @param color The color of the text in the automatically generated icons.
153      *        one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
154      * @param shape The shape of the icon in the automatically generated icons,
155      *        one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
156      */
getImageDescriptor(String osName, int color, int shape)157     public ImageDescriptor getImageDescriptor(String osName, int color, int shape) {
158         String key = Character.toString((char) shape) + Integer.toString(color) + osName;
159         ImageDescriptor id = mImageDescMap.get(key);
160         if (id == null && !mImageDescMap.containsKey(key)) {
161             id = AbstractUIPlugin.imageDescriptorFromPlugin(
162                     AdtPlugin.PLUGIN_ID,
163                     String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$
164 
165             if (id == null) {
166                 id = new LetterImageDescriptor(osName.charAt(0), color, shape);
167             }
168 
169             // Note that we store null references in the icon map, to avoid looking them
170             // up every time. If it didn't exist once, it will not exist later.
171             mImageDescMap.put(key, id);
172         }
173         return id;
174     }
175 
176     /**
177      * Returns an Image for a given icon name.
178      * <p/>
179      * Callers should not dispose it.
180      *
181      * @param osName The leaf name, without the extension, of an existing icon
182      *            in the editor's "icons" directory. If it doesn't exist, the
183      *            fallback will be used instead.
184      * @param fallback the fallback icon name to use if the primary icon does
185      *            not exist, or null if the method should return null if the
186      *            image does not exist
187      * @return the icon, which should not be disposed by the caller, or null
188      * if the image does not exist *and*
189      */
190     @Nullable
getIcon(@onNull String osName, @Nullable String fallback)191     public Image getIcon(@NonNull String osName, @Nullable String fallback) {
192         String key = osName;
193         Image icon = mIconMap.get(key);
194         if (icon == null && !mIconMap.containsKey(key)) {
195             ImageDescriptor id = getImageDescriptor(osName, fallback);
196             if (id != null) {
197                 icon = id.createImage();
198             }
199             // Note that we store null references in the icon map, to avoid looking them
200             // up every time. If it didn't exist once, it will not exist later.
201             mIconMap.put(key, icon);
202         }
203         return icon;
204     }
205 
206     /**
207      * Returns an icon of the given name, or if that image does not exist and
208      * icon of the given fallback name.
209      *
210      * @param key the icon name
211      * @param fallbackKey the fallback image to use if the primary key does not
212      *            exist
213      * @return the image descriptor, or null if the image does not exist and the
214      *         fallbackKey is null
215      */
216     @Nullable
getImageDescriptor(@onNull String key, @Nullable String fallbackKey)217     public ImageDescriptor getImageDescriptor(@NonNull String key, @Nullable String fallbackKey) {
218         ImageDescriptor id = mImageDescMap.get(key);
219         if (id == null && !mImageDescMap.containsKey(key)) {
220             id = AbstractUIPlugin.imageDescriptorFromPlugin(
221                     AdtPlugin.PLUGIN_ID,
222                     String.format("/icons/%1$s.png", key)); //$NON-NLS-1$
223             if (id == null) {
224                 if (fallbackKey == null) {
225                     return null;
226                 }
227                 id = getImageDescriptor(fallbackKey);
228             }
229 
230             // Place the fallback image for this key as well such that we don't keep trying
231             // to load the failed image
232             mImageDescMap.put(key, id);
233         }
234 
235         return id;
236     }
237 
238     /**
239      * Returns the image indicated by the given URL
240      *
241      * @param url the url to the image resources
242      * @return the image for the url, or null if it cannot be initialized
243      */
getIcon(URL url)244     public Image getIcon(URL url) {
245         Image image = mUrlMap.get(url);
246         if (image == null) {
247             ImageDescriptor descriptor = ImageDescriptor.createFromURL(url);
248             image = descriptor.createImage();
249             mUrlMap.put(url, image);
250         }
251 
252         return image;
253     }
254 
255     /**
256      * A simple image description that generates a 16x16 image which consists
257      * of a colored letter inside a black & white circle.
258      */
259     private static class LetterImageDescriptor extends ImageDescriptor {
260 
261         private final char mLetter;
262         private final int mColor;
263         private final int mShape;
264 
LetterImageDescriptor(char letter, int color, int shape)265         public LetterImageDescriptor(char letter, int color, int shape) {
266             mLetter = Character.toUpperCase(letter);
267             mColor = color;
268             mShape = shape;
269         }
270 
271         @Override
getImageData()272         public ImageData getImageData() {
273 
274             final int SX = 15;
275             final int SY = 15;
276             final int RX = 4;
277             final int RY = 4;
278 
279             Display display = Display.getCurrent();
280             if (display == null) {
281                 return null;
282             }
283 
284             Image image = new Image(display, SX, SY);
285 
286             GC gc = new GC(image);
287             gc.setAdvanced(true);
288             gc.setAntialias(SWT.ON);
289             gc.setTextAntialias(SWT.ON);
290 
291             // image.setBackground() does not appear to have any effect; we must explicitly
292             // paint into the image the background color we want masked out later.
293             // HOWEVER, alpha transparency does not work; we only get to mark a single color
294             // as transparent. You might think we could pick a system color (to avoid having
295             // to allocate and dispose the color), or a wildly unique color (to make sure we
296             // don't accidentally pick up any extra pixels in the image as transparent), but
297             // this has the very unfortunate side effect of making neighbor pixels in the
298             // antialiased rendering of the circle pick up shades of that alternate color,
299             // which looks bad. Therefore we pick a color which is similar to one of our
300             // existing colors but hopefully different from most pixels. A visual check
301             // confirms that this seems to work pretty well:
302             RGB backgroundRgb = new RGB(254, 254, 254);
303             Color backgroundColor = new Color(display, backgroundRgb);
304             gc.setBackground(backgroundColor);
305             gc.fillRectangle(0, 0, SX, SY);
306 
307             gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
308             if (mShape == SHAPE_CIRCLE) {
309                 gc.fillOval(0, 0, SX - 1, SY - 1);
310             } else if (mShape == SHAPE_RECT) {
311                 gc.fillRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
312             }
313 
314             gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
315             gc.setLineWidth(1);
316             if (mShape == SHAPE_CIRCLE) {
317                 gc.drawOval(0, 0, SX - 1, SY - 1);
318             } else if (mShape == SHAPE_RECT) {
319                 gc.drawRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
320             }
321 
322             // Get a bold version of the default system font, if possible.
323             Font font = display.getSystemFont();
324             FontData[] fds = font.getFontData();
325             fds[0].setStyle(SWT.BOLD);
326             // use 3/4th of the circle diameter for the font size (in pixels)
327             // and convert it to "font points" (font points in SWT are hardcoded in an
328             // arbitrary 72 dpi and then converted in real pixels using whatever is
329             // indicated by getDPI -- at least that's how it works under Win32).
330             fds[0].setHeight((int) ((SY + 1) * 3./4. * 72./display.getDPI().y));
331             // Note: win32 implementation always uses fds[0] so we change just that one.
332             // getFontData indicates that the array of fd is really an unusual thing for X11.
333             font = new Font(display, fds);
334             gc.setFont(font);
335             gc.setForeground(display.getSystemColor(mColor));
336 
337             // Text measurement varies so slightly depending on the platform
338             int ofx = 0;
339             int ofy = 0;
340             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
341                 ofx = +1;
342                 ofy = -1;
343             } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
344                 // Tweak pixel positioning of some letters that don't look good on the Mac
345                 if (mLetter != 'T' && mLetter != 'V') {
346                     ofy = -1;
347                 }
348                 if (mLetter == 'I') {
349                     ofx = -2;
350                 }
351             }
352 
353             String s = Character.toString(mLetter);
354             Point p = gc.textExtent(s);
355             int tx = (SX + ofx - p.x) / 2;
356             int ty = (SY + ofy - p.y) / 2;
357             gc.drawText(s, tx, ty, true /* isTransparent */);
358 
359             font.dispose();
360             gc.dispose();
361 
362             ImageData data = image.getImageData();
363             image.dispose();
364             backgroundColor.dispose();
365 
366             // Set transparent pixel in the palette such that on paint (over palette,
367             // which has a background of SWT.COLOR_WIDGET_BACKGROUND, and over the tree
368             // which has a white background) we will substitute the background in for
369             // the backgroundPixel.
370             int backgroundPixel = data.palette.getPixel(backgroundRgb);
371             data.transparentPixel = backgroundPixel;
372 
373             return data;
374         }
375     }
376 }
377