merge-update from trunk
[taxeditor.git] / eu.etaxonomy.taxeditor.store / src / main / java / org / eclipse / wb / swt / SWTResourceManager.java
1 /*******************************************************************************
2 * Copyright (c) 2011 Google, Inc.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Google, Inc. - initial API and implementation
10 *******************************************************************************/
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 }