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 60 private IconFactory() { 61 } 62 63 public static synchronized IconFactory getInstance() { 64 if (sInstance == null) { 65 sInstance = new IconFactory(); 66 } 67 return sInstance; 68 } 69 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 */ 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 */ 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 */ 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 */ 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 */ 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 201 public LetterImageDescriptor(char letter, int color, int shape) { 202 mLetter = Character.toUpperCase(letter); 203 mColor = color; 204 mShape = shape; 205 } 206 207 @Override 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