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.internal.core.utils.ui;
12 
13 import com.google.common.io.Closeables;
14 
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.graphics.Color;
17 import org.eclipse.swt.graphics.Font;
18 import org.eclipse.swt.graphics.FontData;
19 import org.eclipse.swt.graphics.GC;
20 import org.eclipse.swt.graphics.Image;
21 import org.eclipse.swt.graphics.ImageData;
22 import org.eclipse.swt.graphics.Point;
23 import org.eclipse.swt.graphics.Rectangle;
24 import org.eclipse.swt.widgets.Display;
25 import org.eclipse.wb.draw2d.IColorConstants;
26 
27 import java.io.InputStream;
28 import java.net.URL;
29 
30 /**
31  * Utilities for drawing on {@link GC}.
32  *
33  * @author scheglov_ke
34  * @coverage core.ui
35  */
36 public class DrawUtils {
37   private static final String DOTS = "...";
38 
39   ////////////////////////////////////////////////////////////////////////////
40   //
41   // Drawing
42   //
43   ////////////////////////////////////////////////////////////////////////////
44   /**
45    * Draws given text clipped horizontally and centered vertically.
46    */
drawStringCV(GC gc, String text, int x, int y, int width, int height)47   public static final void drawStringCV(GC gc, String text, int x, int y, int width, int height) {
48     Rectangle oldClipping = gc.getClipping();
49     try {
50       gc.setClipping(new Rectangle(x, y, width, height));
51       //
52       int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2;
53       gc.drawString(clipString(gc, text, width), x, textStartY, true);
54     } finally {
55       gc.setClipping(oldClipping);
56     }
57   }
58 
59   /**
60    * Draws given text clipped or centered horizontally and centered vertically.
61    */
drawStringCHCV(GC gc, String text, int x, int y, int width, int height)62   public static final void drawStringCHCV(GC gc, String text, int x, int y, int width, int height) {
63     int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2;
64     Point textSize = gc.stringExtent(text);
65     //
66     if (textSize.x > width) {
67       gc.drawString(clipString(gc, text, width), x, textStartY);
68     } else {
69       gc.drawString(text, x + (width - textSize.x) / 2, textStartY);
70     }
71   }
72 
73   /**
74    * Draws image at given <code>x</code> and centered vertically.
75    */
drawImageCV(GC gc, Image image, int x, int y, int height)76   public static final void drawImageCV(GC gc, Image image, int x, int y, int height) {
77     if (image != null) {
78       Rectangle imageBounds = image.getBounds();
79       gc.drawImage(image, x, y + (height - imageBounds.height) / 2);
80     }
81   }
82 
83   /**
84    * Draws image at given <code>x</code> and centered vertically.
85    */
drawImageCHCV(GC gc, Image image, int x, int y, int width, int height)86   public static final void drawImageCHCV(GC gc, Image image, int x, int y, int width, int height) {
87     if (image != null) {
88       Rectangle imageBounds = image.getBounds();
89       int centerX = (width - imageBounds.width) / 2;
90       int centerY = y + (height - imageBounds.height) / 2;
91       gc.drawImage(image, x + centerX, centerY);
92     }
93   }
94 
95   /**
96    * Draws {@link Image} on {@link GC} centered in given {@link Rectangle}. If {@link Image} is
97    * bigger that {@link Rectangle}, {@link Image} will be scaled down as needed with keeping
98    * proportions.
99    */
drawScaledImage(GC gc, Image image, Rectangle targetRectangle)100   public static void drawScaledImage(GC gc, Image image, Rectangle targetRectangle) {
101     int imageWidth = image.getBounds().width;
102     int imageHeight = image.getBounds().height;
103     // prepare scaled image size
104     int newImageWidth;
105     int newImageHeight;
106     if (imageWidth <= targetRectangle.width && imageHeight <= targetRectangle.height) {
107       newImageWidth = imageWidth;
108       newImageHeight = imageHeight;
109     } else {
110       // prepare minimal scale
111       double k;
112       {
113         double k_w = targetRectangle.width / (double) imageWidth;
114         double k_h = targetRectangle.height / (double) imageHeight;
115         k = Math.min(k_w, k_h);
116       }
117       // calculate scaled image size
118       newImageWidth = (int) (imageWidth * k);
119       newImageHeight = (int) (imageHeight * k);
120     }
121     // draw image centered in target rectangle
122     int destX = targetRectangle.x + (targetRectangle.width - newImageWidth) / 2;
123     int destY = targetRectangle.y + (targetRectangle.height - newImageHeight) / 2;
124     gc.drawImage(image, 0, 0, imageWidth, imageHeight, destX, destY, newImageWidth, newImageHeight);
125   }
126 
127   /**
128    * @return the string clipped to have width less than given. Clipping is done as trailing "...".
129    */
clipString(GC gc, String text, int width)130   public static String clipString(GC gc, String text, int width) {
131     if (width <= 0) {
132       return "";
133     }
134     // check if text already fits in given width
135     if (gc.stringExtent(text).x <= width) {
136       return text;
137     }
138     // use average count of characters as base
139     int count = Math.min(width / gc.getFontMetrics().getAverageCharWidth(), text.length());
140     if (gc.stringExtent(text.substring(0, count) + DOTS).x > width) {
141       while (count > 0 && gc.stringExtent(text.substring(0, count) + DOTS).x > width) {
142         count--;
143       }
144     } else {
145       while (count < text.length() - 1
146           && gc.stringExtent(text.substring(0, count + 1) + DOTS).x < width) {
147         count++;
148       }
149     }
150     return text.substring(0, count) + DOTS;
151   }
152 
153   /**
154    * Draws {@link String} in rectangle, wraps at any character (not by words).
155    */
drawTextWrap(GC gc, String text, int x, int y, int width, int height)156   public static void drawTextWrap(GC gc, String text, int x, int y, int width, int height) {
157     int y_ = y;
158     int x_ = x;
159     int lineHeight = 0;
160     for (int i = 0; i < text.length(); i++) {
161       String c = text.substring(i, i + 1);
162       Point extent = gc.stringExtent(c);
163       if (x_ + extent.x > x + width) {
164         y_ += lineHeight;
165         if (y_ > y + height) {
166           return;
167         }
168         x_ = x;
169       }
170       gc.drawText(c, x_, y_);
171       x_ += extent.x;
172       lineHeight = Math.max(lineHeight, extent.y);
173     }
174   }
175 
176   /**
177    * Draws 3D highlight rectangle.
178    */
drawHighlightRectangle(GC gc, int x, int y, int width, int height)179   public static void drawHighlightRectangle(GC gc, int x, int y, int width, int height) {
180     int right = x + width - 1;
181     int bottom = y + height - 1;
182     //
183     Color oldForeground = gc.getForeground();
184     try {
185       gc.setForeground(IColorConstants.buttonLightest);
186       gc.drawLine(x, y, right, y);
187       gc.drawLine(x, y, x, bottom);
188       //
189       gc.setForeground(IColorConstants.buttonDarker);
190       gc.drawLine(right, y, right, bottom);
191       gc.drawLine(x, bottom, right, bottom);
192     } finally {
193       gc.setForeground(oldForeground);
194     }
195   }
196 
197   ////////////////////////////////////////////////////////////////////////////
198   //
199   // Images
200   //
201   ////////////////////////////////////////////////////////////////////////////
202   /**
203    * @return the {@link Image} loaded relative to given {@link Class}.
204    */
loadImage(Class<?> clazz, String path)205   public static Image loadImage(Class<?> clazz, String path) {
206     try {
207       URL resource = clazz.getResource(path);
208       if (resource != null) {
209         InputStream stream = resource.openStream();
210         try {
211           return new Image(null, stream);
212         } finally {
213           Closeables.closeQuietly(stream);
214         }
215       }
216     } catch (Throwable e) {
217     }
218     return null;
219   }
220 
221   /**
222    * @return the thumbnail {@link Image} of required size for given "big" {@link Image}, centered or
223    *         scaled down.
224    */
getThubmnail(Image image, int minWidth, int minHeight, int maxWidth, int maxHeight)225   public static Image getThubmnail(Image image,
226       int minWidth,
227       int minHeight,
228       int maxWidth,
229       int maxHeight) {
230     Rectangle imageBounds = image.getBounds();
231     int imageWidth = imageBounds.width;
232     int imageHeight = imageBounds.height;
233     if (imageWidth < minWidth && imageHeight < minHeight) {
234       // create "thumbnail" Image with required size
235       Image thumbnail = new Image(null, minWidth, minHeight);
236       GC gc = new GC(thumbnail);
237       try {
238         drawImageCHCV(gc, image, 0, 0, minWidth, minHeight);
239       } finally {
240         gc.dispose();
241       }
242       // recreate "thumbnail" Image with transparent pixel
243       try {
244         ImageData thumbnailData = thumbnail.getImageData();
245         thumbnailData.transparentPixel = thumbnailData.getPixel(0, 0);
246         return new Image(null, thumbnailData);
247       } finally {
248         thumbnail.dispose();
249       }
250     } else if (imageWidth <= maxWidth && imageHeight <= maxHeight) {
251       return new Image(null, image, SWT.IMAGE_COPY);
252     } else {
253       double kX = (double) maxWidth / imageWidth;
254       double kY = (double) maxHeight / imageHeight;
255       double k = Math.max(kX, kY);
256       int dWidth = (int) (imageWidth * k);
257       int dHeight = (int) (imageHeight * k);
258       ImageData scaledImageData = image.getImageData().scaledTo(dWidth, dHeight);
259       return new Image(null, scaledImageData);
260     }
261   }
262 
263   ////////////////////////////////////////////////////////////////////////////
264   //
265   // Rotated images
266   //
267   ////////////////////////////////////////////////////////////////////////////
268   /**
269    * Returns a new Image that is the given Image rotated left by 90 degrees. The client is
270    * responsible for disposing the returned Image. This method MUST be invoked from the
271    * user-interface (Display) thread.
272    *
273    * @param srcImage
274    *          the Image that is to be rotated left
275    * @return the rotated Image (the client is responsible for disposing it)
276    */
createRotatedImage(Image srcImage)277   public static Image createRotatedImage(Image srcImage) {
278     // prepare Display
279     Display display = Display.getCurrent();
280     if (display == null) {
281       SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
282     }
283     // rotate ImageData
284     ImageData destData;
285     {
286       ImageData srcData = srcImage.getImageData();
287       if (srcData.depth < 8) {
288         destData = rotatePixelByPixel(srcData);
289       } else {
290         destData = rotateOptimized(srcData);
291       }
292     }
293     // create new image
294     return new Image(display, destData);
295   }
296 
rotatePixelByPixel(ImageData srcData)297   private static ImageData rotatePixelByPixel(ImageData srcData) {
298     ImageData destData =
299         new ImageData(srcData.height, srcData.width, srcData.depth, srcData.palette);
300     for (int y = 0; y < srcData.height; y++) {
301       for (int x = 0; x < srcData.width; x++) {
302         destData.setPixel(y, srcData.width - x - 1, srcData.getPixel(x, y));
303       }
304     }
305     return destData;
306   }
307 
rotateOptimized(ImageData srcData)308   private static ImageData rotateOptimized(ImageData srcData) {
309     int bytesPerPixel = Math.max(1, srcData.depth / 8);
310     int destBytesPerLine =
311         ((srcData.height * bytesPerPixel - 1) / srcData.scanlinePad + 1) * srcData.scanlinePad;
312     byte[] newData = new byte[destBytesPerLine * srcData.width];
313     for (int srcY = 0; srcY < srcData.height; srcY++) {
314       for (int srcX = 0; srcX < srcData.width; srcX++) {
315         int destX = srcY;
316         int destY = srcData.width - srcX - 1;
317         int destIndex = destY * destBytesPerLine + destX * bytesPerPixel;
318         int srcIndex = srcY * srcData.bytesPerLine + srcX * bytesPerPixel;
319         System.arraycopy(srcData.data, srcIndex, newData, destIndex, bytesPerPixel);
320       }
321     }
322     return new ImageData(srcData.height,
323         srcData.width,
324         srcData.depth,
325         srcData.palette,
326         srcData.scanlinePad,
327         newData);
328   }
329 
330   ////////////////////////////////////////////////////////////////////////////
331   //
332   // Colors
333   //
334   ////////////////////////////////////////////////////////////////////////////
335   /**
336    * @return new {@link Color} based on given {@link Color} and shifted on given value to make it
337    *         darker or lighter.
338    */
getShiftedColor(Color color, int delta)339   public static Color getShiftedColor(Color color, int delta) {
340     int r = Math.max(0, Math.min(color.getRed() + delta, 255));
341     int g = Math.max(0, Math.min(color.getGreen() + delta, 255));
342     int b = Math.max(0, Math.min(color.getBlue() + delta, 255));
343     return new Color(color.getDevice(), r, g, b);
344   }
345 
346   /**
347    * @return <code>true</code> if the given <code>color</code> is dark.
348    */
isDarkColor(Color c)349   public static boolean isDarkColor(Color c) {
350     int value =
351         (int) Math.sqrt(c.getRed()
352             * c.getRed()
353             * .241
354             + c.getGreen()
355             * c.getGreen()
356             * .691
357             + c.getBlue()
358             * c.getBlue()
359             * .068);
360     return value < 130;
361   }
362 
363   ////////////////////////////////////////////////////////////////////////////
364   //
365   // Fonts
366   //
367   ////////////////////////////////////////////////////////////////////////////
368   /**
369    * @return the bold version of given {@link Font}.
370    */
getBoldFont(Font baseFont)371   public static Font getBoldFont(Font baseFont) {
372     FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD);
373     return new Font(Display.getCurrent(), boldData);
374   }
375 
376   /**
377    * @return the italic version of given {@link Font}.
378    */
getBoldItalicFont(Font baseFont)379   public static Font getBoldItalicFont(Font baseFont) {
380     FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD | SWT.ITALIC);
381     return new Font(Display.getCurrent(), boldData);
382   }
383 
384   /**
385    * @return the italic version of given {@link Font}.
386    */
getItalicFont(Font baseFont)387   public static Font getItalicFont(Font baseFont) {
388     FontData[] boldData = getModifiedFontData(baseFont, SWT.ITALIC);
389     return new Font(Display.getCurrent(), boldData);
390   }
391 
392   /**
393    * @return the array of {@link FontData} with the specified style.
394    */
getModifiedFontData(Font baseFont, int style)395   private static FontData[] getModifiedFontData(Font baseFont, int style) {
396     FontData[] baseData = baseFont.getFontData();
397     FontData[] styleData = new FontData[baseData.length];
398     for (int i = 0; i < styleData.length; i++) {
399       FontData base = baseData[i];
400       styleData[i] = new FontData(base.getName(), base.getHeight(), base.getStyle() | style);
401     }
402     return styleData;
403   }
404 }
405