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 *******************************************************************************/
11package org.eclipse.wb.internal.core.utils.ui;
12
13import com.google.common.io.Closeables;
14
15import org.eclipse.swt.SWT;
16import org.eclipse.swt.graphics.Color;
17import org.eclipse.swt.graphics.Font;
18import org.eclipse.swt.graphics.FontData;
19import org.eclipse.swt.graphics.GC;
20import org.eclipse.swt.graphics.Image;
21import org.eclipse.swt.graphics.ImageData;
22import org.eclipse.swt.graphics.Point;
23import org.eclipse.swt.graphics.Rectangle;
24import org.eclipse.swt.widgets.Display;
25import org.eclipse.wb.draw2d.IColorConstants;
26
27import java.io.InputStream;
28import java.net.URL;
29
30/**
31 * Utilities for drawing on {@link GC}.
32 *
33 * @author scheglov_ke
34 * @coverage core.ui
35 */
36public 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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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
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
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   */
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   */
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   */
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   */
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   */
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   */
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