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