| /******************************************************************************* |
| * Copyright (c) 2011 Laurent CARON |
| * 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: |
| * Laurent CARON (laurent.caron at gmail dot com) - Initial implementation and API |
| *******************************************************************************/ |
| package org.mihalis.opal.utils; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| 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.PaletteData; |
| import org.eclipse.swt.graphics.RGB; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.graphics.Resource; |
| import org.eclipse.swt.graphics.Transform; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Monitor; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Widget; |
| |
| /** |
| * This class is a singleton that provides useful methods. |
| */ |
| public class SWTGraphicUtil { |
| |
| /** |
| * Constructor. |
| */ |
| private SWTGraphicUtil() { |
| } |
| |
| /** |
| * Dispose safely any SWT resource when a widget is disposed. |
| * |
| * @param widget widget attached to the resource |
| * @param resource the resource to dispose |
| */ |
| public static void addDisposer(final Widget widget, final Resource resource) { |
| widget.addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(final DisposeEvent e) { |
| safeDispose(resource); |
| } |
| }); |
| } |
| |
| /** |
| * Dispose safely any SWT resource. |
| * |
| * @param resource the resource to dispose |
| */ |
| public static void safeDispose(final Resource resource) { |
| if (resource != null && !resource.isDisposed()) { |
| resource.dispose(); |
| } |
| } |
| |
| /** |
| * Create a color that is disposed automatically. |
| * |
| * @param r red component |
| * @param g green component |
| * @param b blue component |
| * @return the color |
| */ |
| public static Color getColorSafely(final int r, final int g, final int b) { |
| final Display display = Display.getCurrent(); |
| final Color color = new Color(display, r, g, b); |
| display.addListener(SWT.Dispose, new Listener() { |
| @Override |
| public void handleEvent(final Event event) { |
| if (!color.isDisposed()) { |
| color.dispose(); |
| } |
| } |
| }); |
| return color; |
| } |
| |
| /** |
| * Loads an image and create a SWT Image corresponding to this file. |
| * |
| * @param fileName file name of the image |
| * @return an image |
| * @see org.eclipse.swt.graphics.Image |
| */ |
| public static Image createImageFromFile(final String fileName) { |
| if (new File(fileName).exists()) { |
| return new Image(Display.getCurrent(), fileName); |
| } else { |
| return new Image(Display.getCurrent(), SWTGraphicUtil.class.getClassLoader().getResourceAsStream(fileName)); |
| } |
| } |
| |
| /** |
| * Create a reflected image of a source Inspired by Daniel Spiewak work |
| * (http://www.eclipsezone.com/eclipse/forums/t91013.html) |
| * |
| * @param source source to be reflected |
| * @return the source image with a reflection |
| */ |
| public static Image createReflectedImage(final Image source) { |
| if (source == null) { |
| return null; |
| } |
| |
| if (source.isDisposed()) { |
| SWT.error(SWT.ERROR_WIDGET_DISPOSED); |
| } |
| |
| // Create a new image |
| final Rectangle sourceBounds = source.getBounds(); |
| final Image newImage = new Image(source.getDevice(), new Rectangle(0, 0, sourceBounds.width, (int) (sourceBounds.height * 1.5))); |
| final GC gc = new GC(newImage); |
| gc.setAdvanced(true); |
| |
| gc.drawImage(source, 0, 0); |
| |
| // Add the reflection |
| final Transform t = new Transform(source.getDevice()); |
| t.setElements(1, 0, 0, -.5f, 0, sourceBounds.height + sourceBounds.height / 2); |
| gc.setTransform(t); |
| |
| gc.drawImage(source, 0, 0); |
| |
| t.dispose(); |
| gc.dispose(); |
| |
| // And add the alpha mask |
| final ImageData imgData = newImage.getImageData(); |
| final int width = imgData.width; |
| final int height = imgData.height; |
| final byte[] alphaData = new byte[height * width]; |
| |
| final byte[] noAlpha = new byte[width]; |
| for (int x = 0; x < width; x++) { |
| noAlpha[x] = (byte) 255; |
| } |
| |
| for (int y = 0; y < height; y++) { |
| final byte[] alphaRow = new byte[width]; |
| if (y < sourceBounds.height) { |
| System.arraycopy(noAlpha, 0, alphaData, y * width, width); |
| } else { |
| for (int x = 0; x < width; x++) { |
| alphaRow[x] = (byte) (255 - 255 * y / height); |
| } |
| System.arraycopy(alphaRow, 0, alphaData, y * width, width); |
| } |
| |
| } |
| imgData.alphaData = alphaData; |
| newImage.dispose(); |
| return new Image(source.getDevice(), imgData); |
| } |
| |
| /** |
| * Returns a new scaled image. |
| * |
| * @param source the image to be scaled |
| * @param newWidth new width of the image |
| * @param newHeight new height of the image |
| * @return a scaled image of the source |
| */ |
| public static Image resize(final Image source, final int newWidth, final int newHeight) { |
| if (source == null) { |
| return null; |
| } |
| |
| if (source.isDisposed()) { |
| SWT.error(SWT.ERROR_WIDGET_DISPOSED); |
| } |
| |
| final Image scaledImage = new Image(source.getDevice(), newWidth, newHeight); |
| final GC gc = new GC(scaledImage); |
| gc.setAntialias(SWT.ON); |
| gc.setInterpolation(SWT.HIGH); |
| gc.drawImage(source, 0, 0, source.getBounds().width, source.getBounds().height, 0, 0, newWidth, newHeight); |
| gc.dispose(); |
| |
| return scaledImage; |
| } |
| |
| /** |
| * Create a reflected and resized version of an image. |
| * |
| * @param source source image to be scaled and reflected |
| * @param newWidth new width of the scaled image |
| * @param newHeight new height of the scaled image |
| * @return the resized and reflected image |
| */ |
| public static Image createReflectedResizedImage(final Image source, final int newWidth, final int newHeight) { |
| if (source == null) { |
| return null; |
| } |
| |
| if (source.isDisposed()) { |
| SWT.error(SWT.ERROR_WIDGET_DISPOSED); |
| } |
| |
| final Image newImage = new Image(source.getDevice(), newWidth, (int) (newHeight * 1.5)); |
| final GC gc = new GC(newImage); |
| gc.setAntialias(SWT.ON); |
| gc.setInterpolation(SWT.HIGH); |
| gc.drawImage(source, 0, 0, source.getBounds().width, source.getBounds().height, 0, 0, newWidth, newHeight); |
| |
| // Add the reflection |
| final Transform t = new Transform(source.getDevice()); |
| t.setElements(1, 0, 0, -.5f, 0, (float) (newHeight * 1.5)); |
| gc.setTransform(t); |
| |
| gc.drawImage(source, 0, 0, source.getBounds().width, source.getBounds().height, 0, 0, newWidth, newHeight); |
| |
| t.dispose(); |
| gc.dispose(); |
| |
| // And add the alpha mask |
| final ImageData imgData = newImage.getImageData(); |
| final int width = imgData.width; |
| final int height = imgData.height; |
| final byte[] alphaData = new byte[height * width]; |
| |
| final byte[] noAlpha = new byte[width]; |
| for (int x = 0; x < width; x++) { |
| noAlpha[x] = (byte) 255; |
| } |
| |
| for (int y = 0; y < height; y++) { |
| final byte[] alphaRow = new byte[width]; |
| if (y < newHeight) { |
| System.arraycopy(noAlpha, 0, alphaData, y * width, width); |
| } else { |
| for (int x = 0; x < width; x++) { |
| alphaRow[x] = (byte) (255 - 255 * y / height); |
| } |
| System.arraycopy(alphaRow, 0, alphaData, y * width, width); |
| } |
| |
| } |
| imgData.alphaData = alphaData; |
| |
| newImage.dispose(); |
| |
| return new Image(source.getDevice(), imgData); |
| |
| } |
| |
| /** |
| * Center a shell on the primary monitor. |
| * |
| * @param shell shell to center |
| */ |
| public static void centerShell(final Shell shell) { |
| final Monitor primary = shell.getDisplay().getPrimaryMonitor(); |
| final Rectangle bounds = primary.getBounds(); |
| final Rectangle rect = shell.getBounds(); |
| final int x = bounds.x + (bounds.width - rect.width) / 2; |
| final int y = bounds.y + (bounds.height - rect.height) / 2; |
| shell.setLocation(x, y); |
| } |
| |
| /** |
| * Gets the bounds of monitor on which shell is displayed. |
| * |
| * @param shell the shell |
| * @return the bounds of the monitor on which the shell is running |
| */ |
| public static Rectangle getBoundsOfMonitorOnWhichShellIsDisplayed(final Shell shell) { |
| for (final Monitor monitor : shell.getDisplay().getMonitors()) { |
| final Rectangle monitorBounds = monitor.getBounds(); |
| final Rectangle shellBounds = shell.getBounds(); |
| if (monitorBounds.contains(shellBounds.x, shellBounds.y)) { |
| return monitorBounds; |
| } |
| } |
| final Monitor primary = shell.getDisplay().getPrimaryMonitor(); |
| return primary.getBounds(); |
| } |
| |
| /** |
| * Apply a very basic pseudo-HTML formating to a text stored in a StyledText |
| * widget. Supported tags are <b>, <i>, <u> , <COLOR>, <backgroundcolor>, |
| * <size> and <BbrR/> |
| * |
| * @param styledText styled text that contains an HTML text |
| */ |
| public static void applyHTMLFormating(final StyledText styledText) { |
| try { |
| new HTMLStyledTextParser(styledText).parse(); |
| } catch (final IOException e) { // NOSONAR |
| e.printStackTrace(); // NOSONAR |
| } |
| } |
| |
| /** |
| * Blur. |
| * |
| * @author Nicholas Rajendram |
| * @param originalImageData The ImageData to be average blurred. |
| * Transparency information will be ignored. |
| * @param radius the number of radius pixels to consider when blurring |
| * image. |
| * @return A blurred copy of the image data, or null if an error occurred. |
| * @see http://www.eclipse.org/articles/article.php?file=Article- |
| * SimpleImageEffectsForSWT/index.html |
| */ |
| public static ImageData blur(final ImageData originalImageData, int radius) { |
| |
| if (radius < 1) { |
| return originalImageData; |
| } |
| |
| // prepare new image data with 24-bit direct palette to hold blurred |
| // copy of image |
| final ImageData newImageData = new ImageData(originalImageData.width, originalImageData.height, 24, new PaletteData(0xFF, 0xFF00, 0xFF0000)); |
| if (radius >= newImageData.height || radius >= newImageData.width) { |
| radius = Math.min(newImageData.height, newImageData.width) - 1; |
| } |
| // initialize cache |
| final ArrayList<RGB[]> rowCache = new ArrayList<RGB[]>(); |
| final int cacheSize = radius * 2 + 1 > newImageData.height ? newImageData.height : radius * 2 + 1; // number |
| // of |
| // rows |
| // of |
| // imageData |
| // we |
| // cache |
| int cacheStartIndex = 0; // which row of imageData the cache begins with |
| for (int row = 0; row < cacheSize; row++) { |
| // row data is horizontally blurred before caching |
| rowCache.add(rowCache.size(), blurRow(originalImageData, row, radius)); |
| } |
| |
| // sum red, green, and blue values separately for averaging |
| final RGB[] rowRGBSums = new RGB[newImageData.width]; |
| final int[] rowRGBAverages = new int[newImageData.width]; |
| int topSumBoundary = 0; // current top row of summed values scope |
| int targetRow = 0; // row with RGB averages to be determined |
| int bottomSumBoundary = 0; // current bottom row of summed values scope |
| int numRows = 0; // number of rows included in current summing scope |
| for (int i = 0; i < newImageData.width; i++) { |
| rowRGBSums[i] = new RGB(0, 0, 0); |
| } |
| |
| while (targetRow < newImageData.height) { |
| if (bottomSumBoundary < newImageData.height) { |
| do { |
| // sum pixel RGB values for each column in our radius scope |
| for (int col = 0; col < newImageData.width; col++) { |
| rowRGBSums[col].red += rowCache.get(bottomSumBoundary - cacheStartIndex)[col].red; |
| rowRGBSums[col].green += rowCache.get(bottomSumBoundary - cacheStartIndex)[col].green; |
| rowRGBSums[col].blue += rowCache.get(bottomSumBoundary - cacheStartIndex)[col].blue; |
| } |
| numRows++; |
| bottomSumBoundary++; // move bottom scope boundary lower |
| if (bottomSumBoundary < newImageData.height && bottomSumBoundary - cacheStartIndex > radius * 2) { |
| // grow cache |
| rowCache.add(rowCache.size(), blurRow(originalImageData, bottomSumBoundary, radius)); |
| } |
| } while (bottomSumBoundary <= radius); // to initialize |
| // rowRGBSums at start |
| } |
| |
| if (targetRow - topSumBoundary > radius) { |
| // subtract values of top row from sums as scope of summed |
| // values moves down |
| for (int col = 0; col < newImageData.width; col++) { |
| rowRGBSums[col].red -= rowCache.get(topSumBoundary - cacheStartIndex)[col].red; |
| rowRGBSums[col].green -= rowCache.get(topSumBoundary - cacheStartIndex)[col].green; |
| rowRGBSums[col].blue -= rowCache.get(topSumBoundary - cacheStartIndex)[col].blue; |
| } |
| numRows--; |
| topSumBoundary++; // move top scope boundary lower |
| rowCache.remove(0); // remove top row which is out of summing |
| // scope |
| cacheStartIndex++; |
| } |
| |
| // calculate each column's RGB-averaged pixel |
| for (int col = 0; col < newImageData.width; col++) { |
| rowRGBAverages[col] = newImageData.palette.getPixel(new RGB(rowRGBSums[col].red / numRows, rowRGBSums[col].green / numRows, rowRGBSums[col].blue / numRows)); |
| } |
| |
| // replace original pixels |
| newImageData.setPixels(0, targetRow, newImageData.width, rowRGBAverages, 0); |
| targetRow++; |
| } |
| return newImageData; |
| } |
| |
| /** |
| * Average blurs a given row of image data. Returns the blurred row as a |
| * matrix of separated RGB values. |
| * |
| * @param originalImageData the original image data |
| * @param row the row |
| * @param radius the radius |
| * @return the RG b[] |
| */ |
| private static RGB[] blurRow(final ImageData originalImageData, final int row, final int radius) { |
| final RGB[] rowRGBAverages = new RGB[originalImageData.width]; // resulting |
| // rgb |
| // averages |
| final int[] lineData = new int[originalImageData.width]; |
| originalImageData.getPixels(0, row, originalImageData.width, lineData, 0); |
| int r = 0, g = 0, b = 0; // sum red, green, and blue values separately |
| // for averaging |
| int leftSumBoundary = 0; // beginning index of summed values scope |
| int targetColumn = 0; // column of RGB average to be determined |
| int rightSumBoundary = 0; // ending index of summed values scope |
| int numCols = 0; // number of columns included in current summing scope |
| RGB rgb; |
| while (targetColumn < lineData.length) { |
| if (rightSumBoundary < lineData.length) { |
| // sum RGB values for each pixel in our radius scope |
| do { |
| rgb = originalImageData.palette.getRGB(lineData[rightSumBoundary]); |
| r += rgb.red; |
| g += rgb.green; |
| b += rgb.blue; |
| numCols++; |
| rightSumBoundary++; |
| } while (rightSumBoundary <= radius); // to initialize summing |
| // scope at start |
| } |
| |
| // subtract sum of left pixel as summing scope moves right |
| if (targetColumn - leftSumBoundary > radius) { |
| rgb = originalImageData.palette.getRGB(lineData[leftSumBoundary]); |
| r -= rgb.red; |
| g -= rgb.green; |
| b -= rgb.blue; |
| numCols--; |
| leftSumBoundary++; |
| } |
| |
| // calculate RGB averages |
| rowRGBAverages[targetColumn] = new RGB(r / numCols, g / numCols, b / numCols); |
| targetColumn++; |
| } |
| return rowRGBAverages; |
| } |
| |
| /** |
| * Enable all widgets of a control. |
| * |
| * @param control control to enable/disable |
| */ |
| public static void enableAllChildrenWidgets(final Control control) { |
| if (control instanceof Composite) { |
| for (final Control c : ((Composite) control).getChildren()) { |
| enableAllChildrenWidgets(c); |
| } |
| } |
| boolean enable = true; |
| final Boolean previousState = (Boolean) control.getData(SWTGraphicUtil.class.toString() + "_enableState"); |
| if (previousState != null) { |
| enable = previousState; |
| } |
| control.setEnabled(enable); |
| } |
| |
| /** |
| * Disable all widgets of a control. |
| * |
| * @param control control to enable/disable |
| */ |
| public static void disableAllChildrenWidgets(final Control control) { |
| if (control instanceof Composite) { |
| for (final Control c : ((Composite) control).getChildren()) { |
| disableAllChildrenWidgets(c); |
| } |
| } |
| control.setData(SWTGraphicUtil.class.toString() + "_enableState", control.isEnabled()); |
| control.setEnabled(false); |
| } |
| |
| /** |
| * Build a font from a given control. Useful if we just want a bold label |
| * for example |
| * |
| * @param control control that handle the default font |
| * @param style new style |
| * @return a font with the given style |
| */ |
| public static Font buildFontFrom(final Control control, final int style) { |
| final Font temp = control.getFont(); |
| final FontData[] fontData = temp.getFontData(); |
| if (fontData == null || fontData.length == 0) { |
| return temp; |
| } |
| return new Font(control.getDisplay(), fontData[0].getName(), fontData[0].getHeight(), style); |
| } |
| |
| /** |
| * Build a font from a given control. Useful if we just want a bold label |
| * for example |
| * |
| * @param control control that handle the default font |
| * @param style new style |
| * @param size the size |
| * @return a font with the given style |
| */ |
| public static Font buildFontFrom(final Control control, final int style, final int size) { |
| final Font temp = control.getFont(); |
| final FontData[] fontData = temp.getFontData(); |
| if (fontData == null || fontData.length == 0) { |
| return temp; |
| } |
| return new Font(control.getDisplay(), fontData[0].getName(), size, style); |
| } |
| |
| /** |
| * Checks if is mac os. |
| * |
| * @return <code>true</code> if the operating system is MacOS, false |
| * otherwise |
| */ |
| public static boolean isMacOS() { |
| final String OS = System.getProperty("os.name").toLowerCase(); |
| return OS.indexOf("mac") >= 0; |
| } |
| |
| /** |
| * Gets the default color. |
| * |
| * @param control the control |
| * @param red the red |
| * @param green the green |
| * @param blue the blue |
| * @return a color that will be disposed when <code>control</code> is |
| * disposed |
| */ |
| public static Color getDefaultColor(final Control control, final int red, final int green, final int blue) { |
| final Color defaultColor = new Color(control.getDisplay(), red, green, blue); |
| addDisposer(control, defaultColor); |
| return defaultColor; |
| } |
| |
| /** |
| * Compute width. |
| * |
| * @param text the text |
| * @return the width of text |
| */ |
| public static int computeWidth(final String text) { |
| final GC gc = new GC(Display.getDefault()); |
| final int width = gc.textExtent(text).x; |
| gc.dispose(); |
| return width; |
| } |
| |
| } |