Initial commit.
[FinalKeyGui.git] / src / org / eclipse / wb / swt / SWTResourceManager.java
1 /*******************************************************************************\r
2  * Copyright (c) 2011 Google, Inc.\r
3  * All rights reserved. This program and the accompanying materials\r
4  * are made available under the terms of the Eclipse Public License v1.0\r
5  * which accompanies this distribution, and is available at\r
6  * http://www.eclipse.org/legal/epl-v10.html\r
7  *\r
8  * Contributors:\r
9  *    Google, Inc. - initial API and implementation\r
10  *******************************************************************************/\r
11 package org.eclipse.wb.swt;
12
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.HashMap;
17 import java.util.Map;
18
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.graphics.Color;
21 import org.eclipse.swt.graphics.Cursor;
22 import org.eclipse.swt.graphics.Font;
23 import org.eclipse.swt.graphics.FontData;
24 import org.eclipse.swt.graphics.GC;
25 import org.eclipse.swt.graphics.Image;
26 import org.eclipse.swt.graphics.ImageData;
27 import org.eclipse.swt.graphics.RGB;
28 import org.eclipse.swt.graphics.Rectangle;
29 import org.eclipse.swt.widgets.Display;
30
31 /**
32  * Utility class for managing OS resources associated with SWT controls such as colors, fonts, images, etc.
33  * <p>
34  * !!! IMPORTANT !!! Application code must explicitly invoke the <code>dispose()</code> method to release the
35  * operating system resources managed by cached objects when those objects and OS resources are no longer
36  * needed (e.g. on application shutdown)
37  * <p>
38  * This class may be freely distributed as part of any application or plugin.
39  * <p>
40  * @author scheglov_ke
41  * @author Dan Rubel
42  */
43 public class SWTResourceManager {
44         ////////////////////////////////////////////////////////////////////////////
45         //
46         // Color
47         //
48         ////////////////////////////////////////////////////////////////////////////
49         private static Map<RGB, Color> m_colorMap = new HashMap<RGB, Color>();
50         /**
51          * Returns the system {@link Color} matching the specific ID.
52          * 
53          * @param systemColorID
54          *            the ID value for the color
55          * @return the system {@link Color} matching the specific ID
56          */
57         public static Color getColor(int systemColorID) {
58                 Display display = Display.getCurrent();
59                 return display.getSystemColor(systemColorID);
60         }
61         /**
62          * Returns a {@link Color} given its red, green and blue component values.
63          * 
64          * @param r
65          *            the red component of the color
66          * @param g
67          *            the green component of the color
68          * @param b
69          *            the blue component of the color
70          * @return the {@link Color} matching the given red, green and blue component values
71          */
72         public static Color getColor(int r, int g, int b) {
73                 return getColor(new RGB(r, g, b));
74         }
75         /**
76          * Returns a {@link Color} given its RGB value.
77          * 
78          * @param rgb
79          *            the {@link RGB} value of the color
80          * @return the {@link Color} matching the RGB value
81          */
82         public static Color getColor(RGB rgb) {
83                 Color color = m_colorMap.get(rgb);
84                 if (color == null) {
85                         Display display = Display.getCurrent();
86                         color = new Color(display, rgb);
87                         m_colorMap.put(rgb, color);
88                 }
89                 return color;
90         }
91         /**
92          * Dispose of all the cached {@link Color}'s.
93          */
94         public static void disposeColors() {
95                 for (Color color : m_colorMap.values()) {
96                         color.dispose();
97                 }
98                 m_colorMap.clear();
99         }
100         ////////////////////////////////////////////////////////////////////////////
101         //
102         // Image
103         //
104         ////////////////////////////////////////////////////////////////////////////
105         /**
106          * Maps image paths to images.
107          */
108         private static Map<String, Image> m_imageMap = new HashMap<String, Image>();
109         /**
110          * Returns an {@link Image} encoded by the specified {@link InputStream}.
111          * 
112          * @param stream
113          *            the {@link InputStream} encoding the image data
114          * @return the {@link Image} encoded by the specified input stream
115          */
116         protected static Image getImage(InputStream stream) throws IOException {
117                 try {
118                         Display display = Display.getCurrent();
119                         ImageData data = new ImageData(stream);
120                         if (data.transparentPixel > 0) {
121                                 return new Image(display, data, data.getTransparencyMask());
122                         }
123                         return new Image(display, data);
124                 } finally {
125                         stream.close();
126                 }
127         }
128         /**
129          * Returns an {@link Image} stored in the file at the specified path.
130          * 
131          * @param path
132          *            the path to the image file
133          * @return the {@link Image} stored in the file at the specified path
134          */
135         public static Image getImage(String path) {
136                 Image image = m_imageMap.get(path);
137                 if (image == null) {
138                         try {
139                                 image = getImage(new FileInputStream(path));
140                                 m_imageMap.put(path, image);
141                         } catch (Exception e) {
142                                 image = getMissingImage();
143                                 m_imageMap.put(path, image);
144                         }
145                 }
146                 return image;
147         }
148         /**
149          * Returns an {@link Image} stored in the file at the specified path relative to the specified class.
150          * 
151          * @param clazz
152          *            the {@link Class} relative to which to find the image
153          * @param path
154          *            the path to the image file, if starts with <code>'/'</code>
155          * @return the {@link Image} stored in the file at the specified path
156          */
157         public static Image getImage(Class<?> clazz, String path) {
158                 String key = clazz.getName() + '|' + path;
159                 Image image = m_imageMap.get(key);
160                 if (image == null) {
161                         try {
162                                 image = getImage(clazz.getResourceAsStream(path));
163                                 m_imageMap.put(key, image);
164                         } catch (Exception e) {
165                                 image = getMissingImage();
166                                 m_imageMap.put(key, image);
167                         }
168                 }
169                 return image;
170         }
171         private static final int MISSING_IMAGE_SIZE = 10;
172         /**
173          * @return the small {@link Image} that can be used as placeholder for missing image.
174          */
175         private static Image getMissingImage() {
176                 Image image = new Image(Display.getCurrent(), MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE);
177                 //
178                 GC gc = new GC(image);
179                 gc.setBackground(getColor(SWT.COLOR_RED));
180                 gc.fillRectangle(0, 0, MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE);
181                 gc.dispose();
182                 //
183                 return image;
184         }
185         /**
186          * Style constant for placing decorator image in top left corner of base image.
187          */
188         public static final int TOP_LEFT = 1;
189         /**
190          * Style constant for placing decorator image in top right corner of base image.
191          */
192         public static final int TOP_RIGHT = 2;
193         /**
194          * Style constant for placing decorator image in bottom left corner of base image.
195          */
196         public static final int BOTTOM_LEFT = 3;
197         /**
198          * Style constant for placing decorator image in bottom right corner of base image.
199          */
200         public static final int BOTTOM_RIGHT = 4;
201         /**
202          * Internal value.
203          */
204         protected static final int LAST_CORNER_KEY = 5;
205         /**
206          * Maps images to decorated images.
207          */
208         @SuppressWarnings("unchecked")
209         private static Map<Image, Map<Image, Image>>[] m_decoratedImageMap = new Map[LAST_CORNER_KEY];
210         /**
211          * Returns an {@link Image} composed of a base image decorated by another image.
212          * 
213          * @param baseImage
214          *            the base {@link Image} that should be decorated
215          * @param decorator
216          *            the {@link Image} to decorate the base image
217          * @return {@link Image} The resulting decorated image
218          */
219         public static Image decorateImage(Image baseImage, Image decorator) {
220                 return decorateImage(baseImage, decorator, BOTTOM_RIGHT);
221         }
222         /**
223          * Returns an {@link Image} composed of a base image decorated by another image.
224          * 
225          * @param baseImage
226          *            the base {@link Image} that should be decorated
227          * @param decorator
228          *            the {@link Image} to decorate the base image
229          * @param corner
230          *            the corner to place decorator image
231          * @return the resulting decorated {@link Image}
232          */
233         public static Image decorateImage(final Image baseImage, final Image decorator, final int corner) {
234                 if (corner <= 0 || corner >= LAST_CORNER_KEY) {
235                         throw new IllegalArgumentException("Wrong decorate corner");
236                 }
237                 Map<Image, Map<Image, Image>> cornerDecoratedImageMap = m_decoratedImageMap[corner];
238                 if (cornerDecoratedImageMap == null) {
239                         cornerDecoratedImageMap = new HashMap<Image, Map<Image, Image>>();
240                         m_decoratedImageMap[corner] = cornerDecoratedImageMap;
241                 }
242                 Map<Image, Image> decoratedMap = cornerDecoratedImageMap.get(baseImage);
243                 if (decoratedMap == null) {
244                         decoratedMap = new HashMap<Image, Image>();
245                         cornerDecoratedImageMap.put(baseImage, decoratedMap);
246                 }
247                 //
248                 Image result = decoratedMap.get(decorator);
249                 if (result == null) {
250                         Rectangle bib = baseImage.getBounds();
251                         Rectangle dib = decorator.getBounds();
252                         //
253                         result = new Image(Display.getCurrent(), bib.width, bib.height);
254                         //
255                         GC gc = new GC(result);
256                         gc.drawImage(baseImage, 0, 0);
257                         if (corner == TOP_LEFT) {
258                                 gc.drawImage(decorator, 0, 0);
259                         } else if (corner == TOP_RIGHT) {
260                                 gc.drawImage(decorator, bib.width - dib.width, 0);
261                         } else if (corner == BOTTOM_LEFT) {
262                                 gc.drawImage(decorator, 0, bib.height - dib.height);
263                         } else if (corner == BOTTOM_RIGHT) {
264                                 gc.drawImage(decorator, bib.width - dib.width, bib.height - dib.height);
265                         }
266                         gc.dispose();
267                         //
268                         decoratedMap.put(decorator, result);
269                 }
270                 return result;
271         }
272         /**
273          * Dispose all of the cached {@link Image}'s.
274          */
275         public static void disposeImages() {
276                 // dispose loaded images
277                 {
278                         for (Image image : m_imageMap.values()) {
279                                 image.dispose();
280                         }
281                         m_imageMap.clear();
282                 }
283                 // dispose decorated images
284                 for (int i = 0; i < m_decoratedImageMap.length; i++) {
285                         Map<Image, Map<Image, Image>> cornerDecoratedImageMap = m_decoratedImageMap[i];
286                         if (cornerDecoratedImageMap != null) {
287                                 for (Map<Image, Image> decoratedMap : cornerDecoratedImageMap.values()) {
288                                         for (Image image : decoratedMap.values()) {
289                                                 image.dispose();
290                                         }
291                                         decoratedMap.clear();
292                                 }
293                                 cornerDecoratedImageMap.clear();
294                         }
295                 }
296         }
297         ////////////////////////////////////////////////////////////////////////////
298         //
299         // Font
300         //
301         ////////////////////////////////////////////////////////////////////////////
302         /**
303          * Maps font names to fonts.
304          */
305         private static Map<String, Font> m_fontMap = new HashMap<String, Font>();
306         /**
307          * Maps fonts to their bold versions.
308          */
309         private static Map<Font, Font> m_fontToBoldFontMap = new HashMap<Font, Font>();
310         /**
311          * Returns a {@link Font} based on its name, height and style.
312          * 
313          * @param name
314          *            the name of the font
315          * @param height
316          *            the height of the font
317          * @param style
318          *            the style of the font
319          * @return {@link Font} The font matching the name, height and style
320          */
321         public static Font getFont(String name, int height, int style) {
322                 return getFont(name, height, style, false, false);
323         }
324         /**
325          * Returns a {@link Font} based on its name, height and style. Windows-specific strikeout and underline
326          * flags are also supported.
327          * 
328          * @param name
329          *            the name of the font
330          * @param size
331          *            the size of the font
332          * @param style
333          *            the style of the font
334          * @param strikeout
335          *            the strikeout flag (warning: Windows only)
336          * @param underline
337          *            the underline flag (warning: Windows only)
338          * @return {@link Font} The font matching the name, height, style, strikeout and underline
339          */
340         public static Font getFont(String name, int size, int style, boolean strikeout, boolean underline) {
341                 String fontName = name + '|' + size + '|' + style + '|' + strikeout + '|' + underline;
342                 Font font = m_fontMap.get(fontName);
343                 if (font == null) {
344                         FontData fontData = new FontData(name, size, style);
345                         if (strikeout || underline) {
346                                 try {
347                                         Class<?> logFontClass = Class.forName("org.eclipse.swt.internal.win32.LOGFONT"); //$NON-NLS-1$
348                                         Object logFont = FontData.class.getField("data").get(fontData); //$NON-NLS-1$
349                                         if (logFont != null && logFontClass != null) {
350                                                 if (strikeout) {
351                                                         logFontClass.getField("lfStrikeOut").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$
352                                                 }
353                                                 if (underline) {
354                                                         logFontClass.getField("lfUnderline").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$
355                                                 }
356                                         }
357                                 } catch (Throwable e) {
358                                         System.err.println("Unable to set underline or strikeout" + " (probably on a non-Windows platform). " + e); //$NON-NLS-1$ //$NON-NLS-2$
359                                 }
360                         }
361                         font = new Font(Display.getCurrent(), fontData);
362                         m_fontMap.put(fontName, font);
363                 }
364                 return font;
365         }
366         /**
367          * Returns a bold version of the given {@link Font}.
368          * 
369          * @param baseFont
370          *            the {@link Font} for which a bold version is desired
371          * @return the bold version of the given {@link Font}
372          */
373         public static Font getBoldFont(Font baseFont) {
374                 Font font = m_fontToBoldFontMap.get(baseFont);
375                 if (font == null) {
376                         FontData fontDatas[] = baseFont.getFontData();
377                         FontData data = fontDatas[0];
378                         font = new Font(Display.getCurrent(), data.getName(), data.getHeight(), SWT.BOLD);
379                         m_fontToBoldFontMap.put(baseFont, font);
380                 }
381                 return font;
382         }
383         /**
384          * Dispose all of the cached {@link Font}'s.
385          */
386         public static void disposeFonts() {
387                 // clear fonts
388                 for (Font font : m_fontMap.values()) {
389                         font.dispose();
390                 }
391                 m_fontMap.clear();
392                 // clear bold fonts
393                 for (Font font : m_fontToBoldFontMap.values()) {
394                         font.dispose();
395                 }
396                 m_fontToBoldFontMap.clear();
397         }
398         ////////////////////////////////////////////////////////////////////////////
399         //
400         // Cursor
401         //
402         ////////////////////////////////////////////////////////////////////////////
403         /**
404          * Maps IDs to cursors.
405          */
406         private static Map<Integer, Cursor> m_idToCursorMap = new HashMap<Integer, Cursor>();
407         /**
408          * Returns the system cursor matching the specific ID.
409          * 
410          * @param id
411          *            int The ID value for the cursor
412          * @return Cursor The system cursor matching the specific ID
413          */
414         public static Cursor getCursor(int id) {
415                 Integer key = Integer.valueOf(id);
416                 Cursor cursor = m_idToCursorMap.get(key);
417                 if (cursor == null) {
418                         cursor = new Cursor(Display.getDefault(), id);
419                         m_idToCursorMap.put(key, cursor);
420                 }
421                 return cursor;
422         }
423         /**
424          * Dispose all of the cached cursors.
425          */
426         public static void disposeCursors() {
427                 for (Cursor cursor : m_idToCursorMap.values()) {
428                         cursor.dispose();
429                 }
430                 m_idToCursorMap.clear();
431         }
432         ////////////////////////////////////////////////////////////////////////////
433         //
434         // General
435         //
436         ////////////////////////////////////////////////////////////////////////////
437         /**
438          * Dispose of cached objects and their underlying OS resources. This should only be called when the cached
439          * objects are no longer needed (e.g. on application shutdown).
440          */
441         public static void dispose() {
442                 disposeColors();
443                 disposeImages();
444                 disposeFonts();
445                 disposeCursors();
446         }
447 }