Home | History | Annotate | Download | only in ui
      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    */
     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