blob: c22256e16cf32052e18d5f010806d86434aded92 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.wb.internal.core.utils.ui;
import com.google.common.io.Closeables;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.wb.draw2d.IColorConstants;
import java.io.InputStream;
import java.net.URL;
/**
* Utilities for drawing on {@link GC}.
*
* @author scheglov_ke
* @coverage core.ui
*/
public class DrawUtils {
private static final String DOTS = "...";
////////////////////////////////////////////////////////////////////////////
//
// Drawing
//
////////////////////////////////////////////////////////////////////////////
/**
* Draws given text clipped horizontally and centered vertically.
*/
public static final void drawStringCV(GC gc, String text, int x, int y, int width, int height) {
Rectangle oldClipping = gc.getClipping();
try {
gc.setClipping(new Rectangle(x, y, width, height));
//
int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2;
gc.drawString(clipString(gc, text, width), x, textStartY, true);
} finally {
gc.setClipping(oldClipping);
}
}
/**
* Draws given text clipped or centered horizontally and centered vertically.
*/
public static final void drawStringCHCV(GC gc, String text, int x, int y, int width, int height) {
int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2;
Point textSize = gc.stringExtent(text);
//
if (textSize.x > width) {
gc.drawString(clipString(gc, text, width), x, textStartY);
} else {
gc.drawString(text, x + (width - textSize.x) / 2, textStartY);
}
}
/**
* Draws image at given <code>x</code> and centered vertically.
*/
public static final void drawImageCV(GC gc, Image image, int x, int y, int height) {
if (image != null) {
Rectangle imageBounds = image.getBounds();
gc.drawImage(image, x, y + (height - imageBounds.height) / 2);
}
}
/**
* Draws image at given <code>x</code> and centered vertically.
*/
public static final void drawImageCHCV(GC gc, Image image, int x, int y, int width, int height) {
if (image != null) {
Rectangle imageBounds = image.getBounds();
int centerX = (width - imageBounds.width) / 2;
int centerY = y + (height - imageBounds.height) / 2;
gc.drawImage(image, x + centerX, centerY);
}
}
/**
* Draws {@link Image} on {@link GC} centered in given {@link Rectangle}. If {@link Image} is
* bigger that {@link Rectangle}, {@link Image} will be scaled down as needed with keeping
* proportions.
*/
public static void drawScaledImage(GC gc, Image image, Rectangle targetRectangle) {
int imageWidth = image.getBounds().width;
int imageHeight = image.getBounds().height;
// prepare scaled image size
int newImageWidth;
int newImageHeight;
if (imageWidth <= targetRectangle.width && imageHeight <= targetRectangle.height) {
newImageWidth = imageWidth;
newImageHeight = imageHeight;
} else {
// prepare minimal scale
double k;
{
double k_w = targetRectangle.width / (double) imageWidth;
double k_h = targetRectangle.height / (double) imageHeight;
k = Math.min(k_w, k_h);
}
// calculate scaled image size
newImageWidth = (int) (imageWidth * k);
newImageHeight = (int) (imageHeight * k);
}
// draw image centered in target rectangle
int destX = targetRectangle.x + (targetRectangle.width - newImageWidth) / 2;
int destY = targetRectangle.y + (targetRectangle.height - newImageHeight) / 2;
gc.drawImage(image, 0, 0, imageWidth, imageHeight, destX, destY, newImageWidth, newImageHeight);
}
/**
* @return the string clipped to have width less than given. Clipping is done as trailing "...".
*/
public static String clipString(GC gc, String text, int width) {
if (width <= 0) {
return "";
}
// check if text already fits in given width
if (gc.stringExtent(text).x <= width) {
return text;
}
// use average count of characters as base
int count = Math.min(width / gc.getFontMetrics().getAverageCharWidth(), text.length());
if (gc.stringExtent(text.substring(0, count) + DOTS).x > width) {
while (count > 0 && gc.stringExtent(text.substring(0, count) + DOTS).x > width) {
count--;
}
} else {
while (count < text.length() - 1
&& gc.stringExtent(text.substring(0, count + 1) + DOTS).x < width) {
count++;
}
}
return text.substring(0, count) + DOTS;
}
/**
* Draws {@link String} in rectangle, wraps at any character (not by words).
*/
public static void drawTextWrap(GC gc, String text, int x, int y, int width, int height) {
int y_ = y;
int x_ = x;
int lineHeight = 0;
for (int i = 0; i < text.length(); i++) {
String c = text.substring(i, i + 1);
Point extent = gc.stringExtent(c);
if (x_ + extent.x > x + width) {
y_ += lineHeight;
if (y_ > y + height) {
return;
}
x_ = x;
}
gc.drawText(c, x_, y_);
x_ += extent.x;
lineHeight = Math.max(lineHeight, extent.y);
}
}
/**
* Draws 3D highlight rectangle.
*/
public static void drawHighlightRectangle(GC gc, int x, int y, int width, int height) {
int right = x + width - 1;
int bottom = y + height - 1;
//
Color oldForeground = gc.getForeground();
try {
gc.setForeground(IColorConstants.buttonLightest);
gc.drawLine(x, y, right, y);
gc.drawLine(x, y, x, bottom);
//
gc.setForeground(IColorConstants.buttonDarker);
gc.drawLine(right, y, right, bottom);
gc.drawLine(x, bottom, right, bottom);
} finally {
gc.setForeground(oldForeground);
}
}
////////////////////////////////////////////////////////////////////////////
//
// Images
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the {@link Image} loaded relative to given {@link Class}.
*/
public static Image loadImage(Class<?> clazz, String path) {
try {
URL resource = clazz.getResource(path);
if (resource != null) {
InputStream stream = resource.openStream();
try {
return new Image(null, stream);
} finally {
Closeables.closeQuietly(stream);
}
}
} catch (Throwable e) {
}
return null;
}
/**
* @return the thumbnail {@link Image} of required size for given "big" {@link Image}, centered or
* scaled down.
*/
public static Image getThubmnail(Image image,
int minWidth,
int minHeight,
int maxWidth,
int maxHeight) {
Rectangle imageBounds = image.getBounds();
int imageWidth = imageBounds.width;
int imageHeight = imageBounds.height;
if (imageWidth < minWidth && imageHeight < minHeight) {
// create "thumbnail" Image with required size
Image thumbnail = new Image(null, minWidth, minHeight);
GC gc = new GC(thumbnail);
try {
drawImageCHCV(gc, image, 0, 0, minWidth, minHeight);
} finally {
gc.dispose();
}
// recreate "thumbnail" Image with transparent pixel
try {
ImageData thumbnailData = thumbnail.getImageData();
thumbnailData.transparentPixel = thumbnailData.getPixel(0, 0);
return new Image(null, thumbnailData);
} finally {
thumbnail.dispose();
}
} else if (imageWidth <= maxWidth && imageHeight <= maxHeight) {
return new Image(null, image, SWT.IMAGE_COPY);
} else {
double kX = (double) maxWidth / imageWidth;
double kY = (double) maxHeight / imageHeight;
double k = Math.max(kX, kY);
int dWidth = (int) (imageWidth * k);
int dHeight = (int) (imageHeight * k);
ImageData scaledImageData = image.getImageData().scaledTo(dWidth, dHeight);
return new Image(null, scaledImageData);
}
}
////////////////////////////////////////////////////////////////////////////
//
// Rotated images
//
////////////////////////////////////////////////////////////////////////////
/**
* Returns a new Image that is the given Image rotated left by 90 degrees. The client is
* responsible for disposing the returned Image. This method MUST be invoked from the
* user-interface (Display) thread.
*
* @param srcImage
* the Image that is to be rotated left
* @return the rotated Image (the client is responsible for disposing it)
*/
public static Image createRotatedImage(Image srcImage) {
// prepare Display
Display display = Display.getCurrent();
if (display == null) {
SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
}
// rotate ImageData
ImageData destData;
{
ImageData srcData = srcImage.getImageData();
if (srcData.depth < 8) {
destData = rotatePixelByPixel(srcData);
} else {
destData = rotateOptimized(srcData);
}
}
// create new image
return new Image(display, destData);
}
private static ImageData rotatePixelByPixel(ImageData srcData) {
ImageData destData =
new ImageData(srcData.height, srcData.width, srcData.depth, srcData.palette);
for (int y = 0; y < srcData.height; y++) {
for (int x = 0; x < srcData.width; x++) {
destData.setPixel(y, srcData.width - x - 1, srcData.getPixel(x, y));
}
}
return destData;
}
private static ImageData rotateOptimized(ImageData srcData) {
int bytesPerPixel = Math.max(1, srcData.depth / 8);
int destBytesPerLine =
((srcData.height * bytesPerPixel - 1) / srcData.scanlinePad + 1) * srcData.scanlinePad;
byte[] newData = new byte[destBytesPerLine * srcData.width];
for (int srcY = 0; srcY < srcData.height; srcY++) {
for (int srcX = 0; srcX < srcData.width; srcX++) {
int destX = srcY;
int destY = srcData.width - srcX - 1;
int destIndex = destY * destBytesPerLine + destX * bytesPerPixel;
int srcIndex = srcY * srcData.bytesPerLine + srcX * bytesPerPixel;
System.arraycopy(srcData.data, srcIndex, newData, destIndex, bytesPerPixel);
}
}
return new ImageData(srcData.height,
srcData.width,
srcData.depth,
srcData.palette,
srcData.scanlinePad,
newData);
}
////////////////////////////////////////////////////////////////////////////
//
// Colors
//
////////////////////////////////////////////////////////////////////////////
/**
* @return new {@link Color} based on given {@link Color} and shifted on given value to make it
* darker or lighter.
*/
public static Color getShiftedColor(Color color, int delta) {
int r = Math.max(0, Math.min(color.getRed() + delta, 255));
int g = Math.max(0, Math.min(color.getGreen() + delta, 255));
int b = Math.max(0, Math.min(color.getBlue() + delta, 255));
return new Color(color.getDevice(), r, g, b);
}
/**
* @return <code>true</code> if the given <code>color</code> is dark.
*/
public static boolean isDarkColor(Color c) {
int value =
(int) Math.sqrt(c.getRed()
* c.getRed()
* .241
+ c.getGreen()
* c.getGreen()
* .691
+ c.getBlue()
* c.getBlue()
* .068);
return value < 130;
}
////////////////////////////////////////////////////////////////////////////
//
// Fonts
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the bold version of given {@link Font}.
*/
public static Font getBoldFont(Font baseFont) {
FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD);
return new Font(Display.getCurrent(), boldData);
}
/**
* @return the italic version of given {@link Font}.
*/
public static Font getBoldItalicFont(Font baseFont) {
FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD | SWT.ITALIC);
return new Font(Display.getCurrent(), boldData);
}
/**
* @return the italic version of given {@link Font}.
*/
public static Font getItalicFont(Font baseFont) {
FontData[] boldData = getModifiedFontData(baseFont, SWT.ITALIC);
return new Font(Display.getCurrent(), boldData);
}
/**
* @return the array of {@link FontData} with the specified style.
*/
private static FontData[] getModifiedFontData(Font baseFont, int style) {
FontData[] baseData = baseFont.getFontData();
FontData[] styleData = new FontData[baseData.length];
for (int i = 0; i < styleData.length; i++) {
FontData base = baseData[i];
styleData[i] = new FontData(base.getName(), base.getHeight(), base.getStyle() | style);
}
return styleData;
}
}