/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* 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:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.ui.swt.utils;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBufferByte;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.text.MessageFormat;
import java.util.Map;
import org.osgi.service.log.LogService;
import org.eclipse.core.runtime.Assert;
import org.eclipse.equinox.log.Logger;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.riena.core.Log4r;
import org.eclipse.riena.core.cache.LRUHashMap;
import org.eclipse.riena.internal.ui.swt.Activator;
import org.eclipse.riena.internal.ui.swt.utils.RcpUtilities;
import org.eclipse.riena.ui.swt.facades.GCFacade;
import org.eclipse.riena.ui.swt.lnf.LnfManager;
/**
* A collection of utility methods for SWT.
*/
public final class SwtUtilities {
private static final Logger LOGGER = Log4r.getLogger(Activator.getDefault(), SwtUtilities.class);
private static final String THREE_DOTS = "..."; //$NON-NLS-1$
private static final Map<GCString, Integer> TEXT_WIDTH_CACHE = LRUHashMap.createLRUHashMap(2048);
private static final Map<GCChar, Integer> CHAR_WIDTH_CACHE = LRUHashMap.createLRUHashMap(1024);
private final static float DEFAULT_DPI_X = 96.0f;
private final static float DEFAULT_DPI_Y = 96.0f;
private final static Point DEFAULT_DPI = new Point(Math.round(DEFAULT_DPI_X), Math.round(DEFAULT_DPI_Y));
private static float[] cachedDpiFactors = new float[] { 0.0f, 0.0f };
private static Point cachedDpi = null;
/**
* This class contains only static methods. So it is not necessary to create an instance.
*/
private SwtUtilities() {
throw new Error("SwtUtilities is just a container for static methods"); //$NON-NLS-1$
}
/**
* The text will be clipped, if the width of the given text is greater than the maximum width.<br>
* The clipped text always ends with three dots ("...").
*
* @param gc
* graphics context
* @param text
* text
* @param maxWidth
* maximum of the text
* @return truncated text
*/
public static String clipText(final GC gc, final String text, final int maxWidth) {
int textwidth = calcTextWidth(gc, text);
if (textwidth <= maxWidth) {
return text;
}
final int threeDotsWidth = calcTextWidth(gc, THREE_DOTS);
final StringBuilder shortenedText = new StringBuilder(text);
while (textwidth + threeDotsWidth > maxWidth && shortenedText.length() != 0) {
shortenedText.setLength(shortenedText.length() - 1);
textwidth = calcTextWidth(gc, shortenedText);
}
shortenedText.append(THREE_DOTS);
return shortenedText.toString();
}
/**
* Calculates the width of the given text based on the current settings of the given graphics context.
*
* @param gc
* graphics context
* @param text
* text
* @return width of text
*/
public static int calcTextWidth(final GC gc, final CharSequence text) {
if (text == null) {
return 0;
}
final GCString lookupKey = new GCString(gc, text);
Integer width = TEXT_WIDTH_CACHE.get(lookupKey);
if (width == null) {
int w = 0;
for (int i = 0; i < text.length(); i++) {
w += calcCharWidth(gc, text.charAt(i));
}
width = w;
TEXT_WIDTH_CACHE.put(lookupKey, width);
}
return width;
}
/**
* Calculates the width of the given char based on the current settings of the given graphics context.
*
* @param gc
* graphics context
* @param ch
* character
* @return width of character
*/
public static int calcCharWidth(final GC gc, final char ch) {
final GCChar lookupKey = new GCChar(gc, ch);
Integer width = CHAR_WIDTH_CACHE.get(lookupKey);
if (width == null) {
width = GCFacade.getDefault().getAdvanceWidth(gc, ch);
CHAR_WIDTH_CACHE.put(lookupKey, width);
}
return width;
}
/**
* Disposes the given resource, if the resource is not null and isn't already disposed.
*
* @param resource
* resource to dispose
*
* @since 3.0
*/
public static void dispose(final Resource resource) {
if (!isDisposed(resource)) {
resource.dispose();
}
}
/**
* Disposes the given widget, if the widget is not {@code null} and isn't already disposed.
*
* @param widget
* widget to dispose
*
* @since 3.0
*/
public static void dispose(final Widget widget) {
if (!isDisposed(widget)) {
widget.dispose();
}
}
/**
* Returns the 0 based index of the column at {@code pt}. The code can handle re-ordered columns. The index refers to the original ordering (as used by SWT
* API).
* <p>
* Will return -1 if no column could be computed -- this is the case when all columns are resized to have width 0.
*
* @since 5.0
*/
public static int findColumn(final Tree tree, final Point pt) {
int width = 0;
final int[] colOrder = tree.getColumnOrder();
// compute the current column ordering
final TreeColumn[] columns = new TreeColumn[colOrder.length];
for (int i = 0; i < colOrder.length; i++) {
final int idx = colOrder[i];
columns[i] = tree.getColumn(idx);
}
// find the column under Point pt
for (final TreeColumn col : columns) {
final int colWidth = col.getWidth();
if (width < pt.x && pt.x < width + colWidth) {
return tree.indexOf(col);
}
width += colWidth;
}
return -1;
}
/**
* Returns the 0 based index of the column at {@code pt}. The code can handle re-ordered columns. The index refers to the original ordering (as used by SWT
* API).
* <p>
* Will return -1 if no column could be computed -- this is the case when all columns are resized to have width 0.
*
* @since 3.0
*/
public static int findColumn(final Table table, final Point pt) {
int width = 0;
final int[] colOrder = table.getColumnOrder();
// compute the current column ordering
final TableColumn[] columns = new TableColumn[colOrder.length];
for (int i = 0; i < colOrder.length; i++) {
final int idx = colOrder[i];
columns[i] = table.getColumn(idx);
}
// find the column under Point pt
for (final TableColumn col : columns) {
final int colWidth = col.getWidth();
if (width < pt.x && pt.x < width + colWidth) {
return table.indexOf(col);
}
width += colWidth;
}
return -1;
}
/**
* Returns true if the given {@code styleBit} is turned on, on the given {@code widget} instance. Returns false otherwise.
* <p>
* Example:
*
* <pre>
* SwtUtilities.hasStyle(button, SWT.CHECK);
* </pre>
*
* @param widget
* a Widget instance; may be null.
* @param styleBit
* an SWT style bit value
* @since 3.0
*/
public static boolean hasStyle(final Widget widget, final int styleBit) {
return widget == null ? false : (widget.getStyle() & styleBit) == styleBit;
}
/**
* Returns {@code true}, if the given widget is disposed or {@code null}.
*
* @param widget
* widget to check
* @return {@code true}, if the widget is disposed or {@code null}; otherwise {@code false}.
*/
public static boolean isDisposed(final Widget widget) {
return widget == null || widget.isDisposed();
}
/**
* Returns {@code true}, if the given resource is disposed or {@code null}.
*
* @param resource
* resource to check
* @return {@code true}, if the resource is disposed or {@code null}; otherwise {@code false}.
*/
public static boolean isDisposed(final Resource resource) {
return !((resource != null) && (!resource.isDisposed()));
}
/**
* Returns a point whose x coordinate is the horizontal dots per inch of the (current or default) display, and whose y coordinate is the vertical dots per
* inch of the display.
*
* @return the horizontal and vertical DPI
* @since 6.0
*/
public static Point getDpi() {
return getDpi(null);
}
/**
* @since 6.1
*/
public static Point getDefaultDpi() {
return DEFAULT_DPI;
}
/**
* Returns a point whose x coordinate is the horizontal dots per inch of the display, and whose y coordinate is the vertical dots per inch of the display.
*
* @param widget
* if widget is not {@code null} return DPI of the display that's associated with the widget; otherwise return the DPI of the current or default
* display
* @return the horizontal and vertical DPI
* @since 6.0
*/
public static Point getDpi(final Widget widget) {
if (cachedDpi == null) {
Display display = null;
if (widget != null) {
display = widget.getDisplay();
}
if (display == null) {
display = getDisplay();
}
Assert.isNotNull(display, "No display exits"); //$NON-NLS-1$
final Display d = display;
display.syncExec(new Runnable() {
public void run() {
cachedDpi = d.getDPI();
}
});
}
return cachedDpi;
}
/**
* Returns whether scaling is enabled or disabled
*
* @return {@code true} scaling is enabled; otherwise {@code false}
* @since 6.1
*/
public static boolean isDpiScalingEnabled() {
return isDpiScalingEnabled(null);
}
/**
* Returns whether scaling is enabled or disabled
*
* @param widget
* if widget is not {@code null} the display that's associated with the widget is used; otherwise the current or default display
* @return {@code true} scaling is enabled; otherwise scaling disabled
* @since 6.1
*/
public static boolean isDpiScalingEnabled(final Widget widget) {
final float[] dpiFactors = getDpiFactors(widget);
for (final float factor : dpiFactors) {
if (factor > 1.0) {
return true;
}
}
return false;
}
/**
* Returns the Display.
*
* @return instance of Display or null if there is no display
* @since 6.1
*/
public static Display getDisplay() {
Display display = null;
if (display == null) {
try {
display = RcpUtilities.getDisplay();
} catch (final RuntimeException e) {
display = null;
}
}
if (display == null) {
display = Display.getCurrent();
}
if (display == null) {
display = Display.getDefault();
}
return display;
}
/**
* Returns the scaling factors which can be used to scale pixel values the same amount as the current DPI differs from the standard DPI (on Windows: 96
* DPI).
*
* @return x-factor and y-factors
* @since 6.0
*/
public static float[] getDpiFactors() {
return getDpiFactors(null);
}
/**
* Returns the scaling factors which can be used to scale pixel values the same amount as the current DPI differs from the standard DPI (on Windows: 96
* DPI).
* <p>
* The Riena Look&Feel can manipulate the scaling factors.
*
* @param widget
* if widget is not {@code null} the display that's associated with the widget is used; otherwise the current or default display
*
* @return x-factor and y-factors
* @since 6.0
*/
public static float[] getDpiFactors(final Widget widget) {
if ((cachedDpiFactors[0] <= 0.001f) || (cachedDpiFactors[1] <= 0.001f)) {
final Point dpi = getDpi(widget);
final float[] lnfDpiFactors = LnfManager.getLnf().getDpiFactors(dpi);
if (lnfDpiFactors.length == 2) {
cachedDpiFactors[0] = lnfDpiFactors[0];
cachedDpiFactors[1] = lnfDpiFactors[1];
}
}
if ((cachedDpiFactors[0] <= 0.001f) || (cachedDpiFactors[1] <= 0.001f)) {
final float[] factors = getCalculatedDpiFactors(widget);
cachedDpiFactors[0] = factors[0];
cachedDpiFactors[1] = factors[1];
}
return cachedDpiFactors;
}
/**
* Returns the scaling factors which can be used to scale pixel values the same amount as the current DPI differs from the standard DPI (on Windows: 96
* DPI).
*
* @return x-factor and y-factors
* @since 6.1
*/
public static float[] getCalculatedDpiFactors() {
return getCalculatedDpiFactors(null);
}
/**
* Returns the scaling factors which can be used to scale pixel values the same amount as the current DPI differs from the standard DPI (on Windows: 96
* DPI).
*
* @return x-factor and y-factors
* @since 6.1
*/
public static float[] getCalculatedDpiFactors(final Widget widget) {
final Point dpi = getDpi(widget);
final float[] factors = new float[2];
factors[0] = dpi.x / DEFAULT_DPI_X;
factors[1] = dpi.y / DEFAULT_DPI_Y;
return factors;
}
/**
* Scales the given value in X-direction.
* <p>
* Rounds to nearest int value.
*
* @param x
* value to scale
* @return scaled value
* @since 6.0
*/
public static int convertXToDpi(final int x) {
final float factorX = getDpiFactors()[0];
return convertPixelToDpi(x, factorX);
}
/**
* Scales the given value in Y-direction.
* <p>
* Rounds to nearest int value.
*
* @param y
* value to scale
* @return scaled value
* @since 6.0
*/
public static int convertYToDpi(final int y) {
final float factorY = getDpiFactors()[1];
return convertPixelToDpi(y, factorY);
}
/**
* @since 6.0
*/
public static Point convertPointToDpi(final Point pixelPoint) {
final int x = convertXToDpi(pixelPoint.x);
final int y = convertYToDpi(pixelPoint.y);
return new Point(x, y);
}
/**
* @since 6.0
*/
public static int convertPixelToDpi(final int pixel) {
final float factor = Math.min(getDpiFactors()[0], getDpiFactors()[1]);
return convertPixelToDpi(pixel, factor);
}
private static int convertPixelToDpi(final int px, final float factor) {
if (px < 0) {
return -(int) (-px * factor + 0.5);
}
return (int) (px * factor + 0.5);
}
/**
* Scales the given value in X-direction.
* <p>
* Truncates the decimal portion.
*
* @param y
* value to scale
* @return scaled value
* @since 6.0
*/
public static int convertXToDpiTruncate(final int x) {
final float factorX = getDpiFactors()[0];
return convertPixelToDpiTruncate(x, factorX);
}
/**
* Scales the given value in Y-direction.
* <p>
* Truncates the decimal portion.
*
* @param y
* value to scale
* @return scaled value
* @since 6.0
*/
public static int convertYToDpiTruncate(final int y) {
final float factorY = getDpiFactors()[1];
return convertPixelToDpiTruncate(y, factorY);
}
/**
* @since 6.0
*/
public static int convertPixelToDpiTruncate(final int pixel) {
final float factor = Math.min(getDpiFactors()[0], getDpiFactors()[1]);
return convertPixelToDpiTruncate(pixel, factor);
}
private static int convertPixelToDpiTruncate(final int px, final float factor) {
return (int) (px * factor);
}
/**
* Creates a new instance of <code>Color</code> that is a brighter version of the given color.
*
* @param color
* the color to make brighter; never null
* @param f
* the factor.
* @return a new <code>Color</code> object that is a brighter version of this given color.
*/
public static Color makeBrighter(final Color color, final float f) {
Assert.isNotNull(color);
Assert.isTrue(f >= 0.0);
final float[] hsb = color.getRGB().getHSB();
final float h = hsb[0];
final float s = hsb[1];
float b = hsb[2];
b = b * f;
if (b > 1.0f) {
b = 1.0f;
}
final RGB rgb = new RGB(h, s, b);
return new Color(color.getDevice(), rgb);
}
/**
* Converts the given AWT image to a SWT image ({@code ImageData}).
* <p>
* <i>Copy of org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet156.java</i>
*
* @param bufferedImage
* AWT image
* @return SWT image or {@code null} if conversion isn't possible
* @since 6.1
*/
public static ImageData convertAwtImageToImageData(final BufferedImage bufferedImage) {
if (bufferedImage.getColorModel() instanceof DirectColorModel) {
final DirectColorModel colorModel = (DirectColorModel) bufferedImage.getColorModel();
final PaletteData palette = convertColorModelToPalette(colorModel);
final ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
for (int y = 0; y < data.height; y++) {
for (int x = 0; x < data.width; x++) {
final int rgb = bufferedImage.getRGB(x, y);
final int pixel = palette.getPixel(new RGB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF));
data.setPixel(x, y, pixel);
if (colorModel.hasAlpha()) {
data.setAlpha(x, y, (rgb >> 24) & 0xFF);
}
}
}
return data;
} else if (bufferedImage.getColorModel() instanceof IndexColorModel) {
final IndexColorModel colorModel = (IndexColorModel) bufferedImage.getColorModel();
final PaletteData palette = convertColorModelToPalette(colorModel);
final ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
data.transparentPixel = colorModel.getTransparentPixel();
final WritableRaster raster = bufferedImage.getRaster();
final int[] pixelArray = new int[1];
for (int y = 0; y < data.height; y++) {
for (int x = 0; x < data.width; x++) {
raster.getPixel(x, y, pixelArray);
data.setPixel(x, y, pixelArray[0]);
}
}
return data;
} else if (bufferedImage.getColorModel() instanceof ComponentColorModel && bufferedImage.getType() == BufferedImage.TYPE_3BYTE_BGR) {
final ComponentColorModel colorModel = (ComponentColorModel) bufferedImage.getColorModel();
final PaletteData palette = convertColorModelToPalette(colorModel);
final WritableRaster raster = bufferedImage.getRaster();
final int scanlinePad = colorModel.getColorSpace().getNumComponents();
final byte[] pixels = ((DataBufferByte) raster.getDataBuffer()).getData();
final ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette, scanlinePad, pixels);
return data;
} else {
if (bufferedImage.getColorModel() != null) {
final String colorModelName = bufferedImage.getColorModel().getClass().getSimpleName();
final String msg = MessageFormat.format("The color model {0} is not supported!", colorModelName); //$NON-NLS-1$
LOGGER.log(LogService.LOG_ERROR, msg);
} else {
LOGGER.log(LogService.LOG_ERROR, "No color model!"); //$NON-NLS-1$
}
}
return null;
}
/**
* Converts the given color model of AWT to a SWT palette.
*
* @param colorModel
* AWT color model
* @return SWT palette
*/
private static PaletteData convertColorModelToPalette(final DirectColorModel colorModel) {
return new PaletteData(colorModel.getRedMask(), colorModel.getGreenMask(), colorModel.getBlueMask());
}
/**
* Converts the given color model of AWT to a SWT palette.
*
* @param colorModel
* AWT color model
* @return SWT palette
*/
private static PaletteData convertColorModelToPalette(final ComponentColorModel colorModel) {
return new PaletteData(0x0000FF, 0x00FF00, 0xFF0000);
}
/**
* Converts the given color model of AWT to a SWT palette.
*
* @param colorModel
* AWT color model
* @return SWT palette
*/
private static PaletteData convertColorModelToPalette(final IndexColorModel colorModel) {
final int size = colorModel.getMapSize();
final byte[] reds = new byte[size];
final byte[] greens = new byte[size];
final byte[] blues = new byte[size];
colorModel.getReds(reds);
colorModel.getGreens(greens);
colorModel.getBlues(blues);
final RGB[] rgbs = new RGB[size];
for (int i = 0; i < rgbs.length; i++) {
rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF);
}
final PaletteData palette = new PaletteData(rgbs);
return palette;
}
private final static class GCChar {
private final char ch;
private final Font font;
private GCChar(final GC gc, final char ch) {
this.font = gc.getFont();
this.ch = ch;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ch;
result = prime * result + ((font == null) ? 0 : font.hashCode());
return result;
}
// Note: There is no type check here because we do not mix types in the map!! (performance)
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
final GCChar other = (GCChar) obj;
if (ch != other.ch) {
return false;
}
if (font == null) {
if (other.font != null) {
return false;
}
} else if (!font.equals(other.font)) {
return false;
}
return true;
}
}
private final static class GCString {
private final String text;
private final Font font;
private GCString(final GC gc, final CharSequence seq) {
this.font = gc.getFont();
this.text = seq.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((font == null) ? 0 : font.hashCode());
result = prime * result + ((text == null) ? 0 : text.hashCode());
return result;
}
// Note: There is no type check here because we do not mix types in the map!! (performance)
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
final GCString other = (GCString) obj;
if (font == null) {
if (other.font != null) {
return false;
}
} else if (!font.equals(other.font)) {
return false;
}
if (text == null) {
if (other.text != null) {
return false;
}
} else if (!text.equals(other.text)) {
return false;
}
return true;
}
}
}