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