blob: a4a8e9b26a7ba3d9725af46ee335e17162258d8a [file] [log] [blame]
/*******************************************************************************
* 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.imageSelector;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.osbp.utils.themes.ui.VaaclipseUiTheme;
import org.eclipse.osbp.utils.themes.ui.VaaclipseUiTheme.ThemeList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.mihalis.opal.utils.SWTGraphicUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Instances of this class are controls that allow the user to select images.
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>(none)</dd>
* <dt><b>Events:</b></dt>
* <dd>(none)</dd>
* </dl>
* Work inspired by Romain Guy's work
* (http://www.curious-creature.org/2005/07/09/a-music-shelf-in-java2d/)
*/
public class ImageSelector extends Canvas {
/** The items. */
private List<ISItem> items;
/** The original items. */
private List<ISItem> originalItems;
/** The font. */
private Font font;
/** The Constant DEFAULT_WIDTH. */
private static final int DEFAULT_WIDTH = 148;
public static final int WIDTH_FACTOR = 3;
/** The max item width. */
private int maxItemWidth = DEFAULT_WIDTH;
/** The index. */
private int index = -1;
/** The sigma. */
private double sigma;
/** The rho. */
private double rho;
/** The exp multiplier. */
private double expMultiplier;
/** The exp member. */
private double expMember;
/** The spacing. */
private float spacing = 0.4f;
/** The gradient start. */
private Color gradientStart;
/** The gradient end. */
private Color gradientEnd;
/** The animation step. */
private double animationStep = -1d;
/** The Constant TIMER_INTERVAL. */
private static final int TIMER_INTERVAL = 50;
/** The page increment. */
private int pageIncrement = 5;
/** The cached image. */
private Image cachedImage;
/** The cached gc. */
private GC cachedGC;
private String imagePlatformPath;
private String imageName;
// to avoid stupid actions from xtext
boolean firstAction = true;
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger( ImageSelector.class );
/**
* Constructs a new instance of this class given its parent and a style
* value describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in class
* <code>SWT</code> which is applicable to instances of this class, or must
* be built by <em>bitwise OR</em>'ing together (that is, using the
* <code>int</code> "|" operator) two or more of those <code>SWT</code>
* style constants. The class description lists the style constants that are
* applicable to the class. Style bits are also inherited from superclasses.
* </p>
*
* @param parent a composite control which will be the parent of the new
* instance (cannot be null)
* @param style the style of control to construct
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the parent</li>
* </ul>
*
*/
public ImageSelector(final Composite parent, final int style, final int defaultWidth) {
super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
final Font defaultFont = new Font(getDisplay(), "Lucida Sans", 24, SWT.NONE);
font = defaultFont;
SWTGraphicUtil.addDisposer(this, defaultFont);
maxItemWidth = defaultWidth * WIDTH_FACTOR;
setSigma(0.5);
gradientStart = getDisplay().getSystemColor(SWT.COLOR_BLACK);
gradientEnd = SWTGraphicUtil.getDefaultColor(this, 110, 110, 110);
addListeners();
SWTGraphicUtil.addDisposer(this, cachedGC);
SWTGraphicUtil.addDisposer(this, cachedImage);
}
/**
* Adds the listeners.
*/
private void addListeners() {
addKeyListener();
addMouseListeners();
addPaintListener(new PaintListener() {
@Override
public void paintControl(final PaintEvent e) {
ImageSelector.this.paintControl(e);
}
});
addListener(SWT.Resize, new Listener() {
@Override
public void handleEvent(final Event event) {
if (cachedGC == null) {
return;
}
cachedGC.dispose();
cachedImage.dispose();
cachedImage = new Image(getDisplay(), getClientArea());
cachedGC = new GC(cachedImage);
cachedGC.setAntialias(SWT.ON);
}
});
}
/**
* Add the key listener.
*/
private void addKeyListener() {
this.addKeyListener(new KeyAdapter() {
/**
* @see org.eclipse.swt.events.KeyAdapter#keyReleased(org.eclipse.swt.events.KeyEvent)
*/
@Override
public void keyReleased(final KeyEvent e) {
switch (e.keyCode) {
case SWT.ARROW_LEFT:
case SWT.ARROW_UP:
scrollAndAnimateBy(-1);
break;
case SWT.ARROW_RIGHT:
case SWT.ARROW_DOWN:
scrollAndAnimateBy(1);
break;
case SWT.HOME:
scrollBy(-1 * ImageSelector.this.index);
break;
case SWT.END:
scrollBy(ImageSelector.this.index);
break;
case SWT.PAGE_UP:
scrollBy(-1 * ImageSelector.this.pageIncrement);
break;
case SWT.PAGE_DOWN:
scrollBy(ImageSelector.this.pageIncrement);
break;
case SWT.KEYPAD_CR:
case SWT.CR:
if (!firstAction) {
selectImage();
}
break;
default: scrollToItem(Character.toString(e.character));
}
firstAction = false;
}
});
}
/**
* Add mouse listeners.
*/
private void addMouseListeners() {
addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(final MouseEvent e) {
for (final ISItem item : items) {
if (item.getUpperLeftCorner() != null && item.getLowerRightCorner() != null && e.x >= item.getUpperLeftCorner().x && e.x <= item.getLowerRightCorner().x && e.y >= item.getUpperLeftCorner().y
&& e.y <= item.getLowerRightCorner().y) {
setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND));
return;
}
}
setCursor(getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
}
});
addMouseListener(new MouseAdapter() {
/**
* @see org.eclipse.swt.events.MouseAdapter#mouseUp(org.eclipse.swt.events.MouseEvent)
*/
@Override
public void mouseUp(final MouseEvent e) {
for (final ISItem item : items) {
if (item.getUpperLeftCorner() != null && item.getLowerRightCorner() != null && e.x >= item.getUpperLeftCorner().x && e.x <= item.getLowerRightCorner().x && e.y >= item.getUpperLeftCorner().y
&& e.y <= item.getLowerRightCorner().y) {
scrollAndAnimateBy(originalItems.indexOf(item) - index);
return;
}
}
}
});
addListener(SWT.MouseWheel, new Listener() {
@Override
public void handleEvent(final Event event) {
scrollBy(-1 * event.count);
}
});
}
/**
* Set the sigma value for the gaussian curve.
*
* @param sigma new sigma parameter
*/
public void setSigma(final double sigma) {
this.sigma = sigma;
rho = 1.0;
computeEquationParts();
rho = computeModifierUnbounded(0.0);
computeEquationParts();
redraw();
}
/**
* Compute the value of the modifier. The value is bounded between -1 and +1
*
* @param x input value
* @return the value of the modifier between -1 and +1
*/
private double computeModifierBounded(final double x) {
double result = computeModifierUnbounded(x);
if (result > 1.0) {
result = 1.0;
} else if (result < -1.0) {
result = -1.0;
}
return result;
}
/**
* Compute the value of the modifier.
*
* @param x input value
* @return the value of the function
*/
private double computeModifierUnbounded(final double x) {
return expMultiplier * Math.exp(-x * x / expMember);
}
/**
* Computer both members of the equation.
*/
private void computeEquationParts() {
expMultiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho;
expMember = 4.0 * sigma * sigma;
}
/**
* Draw the widget.
*
* @param e the paintEvent
*/
private void paintControl(final PaintEvent e) {
if (cachedImage == null) {
cachedImage = new Image(getDisplay(), getClientArea());
cachedGC = new GC(cachedImage);
cachedGC.setAntialias(SWT.ON);
}
// Draw gradient
drawBackground();
// Draw the items
drawItems();
// Draw the title
if (animationStep < 0d) {
drawTitle();
}
// Draw the offscreen buffer to the screen
e.gc.drawImage(cachedImage, 0, 0);
cachedGC.dispose();
cachedImage.dispose();
cachedImage = null;
}
/**
* Draw the background.
*/
private void drawBackground() {
final Rectangle rect = getClientArea();
cachedGC.setForeground(gradientStart);
cachedGC.setBackground(gradientEnd);
cachedGC.fillGradientRectangle(rect.x, rect.y, rect.width, rect.height / 2, true);
cachedGC.setForeground(gradientEnd);
cachedGC.setBackground(gradientStart);
cachedGC.fillGradientRectangle(rect.x, rect.height / 2, rect.width, rect.height / 2, true);
}
/**
* Draw the items.
*/
private void drawItems() {
if (animationStep < 0d) {
items.clear();
items.addAll(originalItems);
for (int i = 0; i < items.size(); i++) {
final ISItem item = items.get(i);
item.setzPosition((i - index) * spacing);
}
Collections.sort(items);
}
for (final ISItem item : items) {
drawItem(item);
}
}
/**
* Draw a given item.
*
* @param item item to draw
*/
private void drawItem(final ISItem item) {
final int size = computeSize(item);
final int centerX = computeZPosition(item);
final int centerY = getClientArea().height / 2;
if (size <= 0 || centerX < 0 || centerX > getBounds().width) {
item.resetCornerToNull();
return;
}
final int alpha = computeAlpha(item);
final Image newImage = SWTGraphicUtil.createReflectedResizedImage(item.getImage(), size, size);
cachedGC.setAlpha(alpha);
final int x = centerX - newImage.getBounds().width / 2;
final int y = centerY - newImage.getBounds().height / 2;
cachedGC.drawImage(newImage, x, y);
item.setUpperLeftCorner(x, y);
item.setLowerRightCorner(x + newImage.getBounds().width, (int) (y + newImage.getBounds().height / 1.5));
newImage.dispose();
}
/**
* Compute the z position for a given item.
*
* @param item item
* @return the z position of the item
*/
private int computeZPosition(final ISItem item) {
final int totalWidth = getClientArea().width / 2;
final int centerX = getClientArea().width / 2;
return (int) (centerX + item.getzPosition() * totalWidth);
}
/**
* Compute size for a given item.
*
* @param item item
* @return the size of the item
*/
private int computeSize(final ISItem item) {
return (int) (computeModifierBounded(item.getzPosition()) * maxItemWidth);
}
/**
* Compute the alpha value of a given item.
*
* @param item item
* @return the alpha value of the item
*/
private int computeAlpha(final ISItem item) {
return (int) (255 - 150 * Math.abs(item.getzPosition()));
}
/**
* Draw the title under the selected item.
*/
private void drawTitle() {
final String title = originalItems.get(index).getText();
if (title == null || title.trim().equals("")) {
return;
}
cachedGC.setFont(getFont());
final Point textSize = cachedGC.stringExtent(title);
cachedGC.setFont(getFont());
cachedGC.setForeground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
cachedGC.setAlpha(255);
final int centerX = getClientArea().width / 2;
final int centerY = (getClientArea().height + maxItemWidth) / 2;
cachedGC.drawString(title, centerX - textSize.x / 2, centerY - textSize.y / 2, true);
}
/**
* Scroll the selected item.
*
* @param increment increment value
*/
private void scrollBy(final int increment) {
index += increment;
if (index < 0) {
index = 0;
}
if (index >= items.size()) {
index = items.size() - 1;
}
redraw();
}
/**
* Scroll the selected item with an animation.
*
* @param increment increment value
*/
private void scrollAndAnimateBy(final int increment) {
if (increment == 0 || index == 0 && increment < 0 || index == items.size() - 1 && increment > 0) {
return;
}
final double step = Math.abs(increment) / (300d / TIMER_INTERVAL);
ImageSelector.this.animationStep = step;
setCursor(getDisplay().getSystemCursor(SWT.CURSOR_WAIT));
getDisplay().syncExec(new Runnable() {
@Override
public void run() {
startAnimation(increment, step);
}
private void startAnimation(final int increment, final double step) {
items.clear();
items.addAll(originalItems);
for (int i = 0; i < items.size(); i++) {
final ISItem item = items.get(i);
item.setzPosition((i - index + animationStep * (increment > 0 ? -1d : 1d)) * spacing);
}
Collections.sort(items);
if (!isDisposed()) {
redraw();
}
animationStep += step;
if (animationStep >= 1d) {
animationStep = -1d;
index += increment;
setCursor(getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
} else {
if (!isDisposed()) {
getDisplay().timerExec(TIMER_INTERVAL, this);
}
}
}
});
}
/**
* Scrolls to an item in {@link #items} whose name
* starts with the given prefix.
* @param prefix the prefix
*/
private void scrollToItem(String prefix){
int idx = findIndexOfFirstItemWithStartingName(prefix);
if(idx != -1) {
this.index = idx;
redraw();
}
}
/**
* Returns the index of the first item element whose names
* starts with the given prefix.
* @param prefix the prefix
* @return the item index or -1 if none is found
*/
private int findIndexOfFirstItemWithStartingName(String prefix){
for(ISItem item : ImageSelector.this.items){
if(item.getText().startsWith(prefix)){
return ImageSelector.this.items.indexOf(item);
}
}
return -1;
}
/**
* Gets the items.
*
* @return the items displayed by this widget
*/
public List<ISItem> getItems() {
return originalItems;
}
/**
* Sets the items.
*
* @param items the items that are displayed in this widget to set
*/
public void setItems(final List<ISItem> items) {
this.items = new ArrayList<ISItem>(items);
originalItems = items;
index = this.items.size() / 2;
redraw();
}
/**
* Sets the items.
*
* @param filterExtensions
* the new items
*/
public void setItems(final String [] filterExtensions) {
// Create the list of images
final List<ISItem> items = new LinkedList<ISItem>();
URI iconsUri = null;
Set<VaaclipseUiTheme> themes = VaaclipseUiTheme.values(ThemeList.forProduct);
for (VaaclipseUiTheme vaaclipseUiTheme : themes) {
if(vaaclipseUiTheme.hasIconsUri()) {
iconsUri = vaaclipseUiTheme.getIconsUri();
break;
}
}
if(iconsUri == null) {
LOGGER.error("no theme provider bundle found");
return;
}
try {
File pathFolder = new File(FileLocator.toFileURL(iconsUri.toURL()).getPath());
if (pathFolder.isDirectory()) {
File[] imgFiles = pathFolder.listFiles(new FilenameFilter() {
public boolean accept(File dir, String filename) {
for (int i = 0; i < filterExtensions.length; i++) {
boolean fileNameFound = filename.endsWith(filterExtensions[i]);
if (fileNameFound) return fileNameFound;
}
return false;
}
});
if ( imgFiles != null ) {
for (File imgFile : imgFiles) {
items.add( new ISItem(imgFile));
}
}
}
setItems(items);
} catch (IOException e) {
LOGGER.error(e.getLocalizedMessage()+e.getCause().getLocalizedMessage());
}
}
/**
* Disposes all SWT-items within CCISItem-list {@literal}items. Clears list
* afterwards.
*
* @return void
*/
public void clearItems () {
for ( ISItem item : this.items ) {
item.dispose();
}
this.items.clear();
}
/**
* Gets the font.
*
* @return the font used for the title
*/
@Override
public Font getFont() {
return font;
}
/**
* Sets the font.
*
* @param font the font used for the title to set
*/
@Override
public void setFont(final Font font) {
this.font = font;
redraw();
}
/**
* Gets the index.
*
* @return the index of the selected image
*/
public int getIndex() {
return index;
}
/**
* Sets the index.
*
* @param index the index of the selected image to set
*/
public void setIndex(final int index) {
this.index = index;
redraw();
}
/**
* Gets the max item width.
*
* @return the maximum items width
*/
public int getMaxItemWidth() {
return maxItemWidth;
}
/**
* Sets the max item width.
*
* @param maxItemWidth the the maximum items width to set
*/
public void setMaxItemWidth(final int maxItemWidth) {
this.maxItemWidth = maxItemWidth;
redraw();
}
/**
* Gets the sigma.
*
* @return the sigma value
*/
public double getSigma() {
return sigma;
}
/**
* Gets the spacing.
*
* @return the spacing between 2 items
*/
public float getSpacing() {
return spacing;
}
/**
* Sets the spacing.
*
* @param spacing the the spacing between 2 items to set
*/
public void setSpacing(final float spacing) {
this.spacing = spacing;
redraw();
}
/**
* Gets the gradient start.
*
* @return the gradient start color
*/
public Color getGradientStart() {
return gradientStart;
}
/**
* Sets the gradient start.
*
* @param gradientStart the the gradient start color to set
*/
public void setGradientStart(final Color gradientStart) {
this.gradientStart = gradientStart;
redraw();
}
/**
* Gets the gradient end.
*
* @return the the gradient end color
*/
public Color getGradientEnd() {
return gradientEnd;
}
/**
* Sets the gradient end.
*
* @param gradientEnd the the gradient end color to set
*/
public void setGradientEnd(final Color gradientEnd) {
this.gradientEnd = gradientEnd;
redraw();
}
/**
* Gets the page increment.
*
* @return the page increment when the user uses PgUp and PgDown
*/
public int getPageIncrement() {
return pageIncrement;
}
/**
* Sets the page increment.
*
* @param pageIncrement the page increment to set
*/
public void setPageIncrement(final int pageIncrement) {
this.pageIncrement = pageIncrement;
}
/**
* Select image.
*/
private void selectImage() {
int idx = ImageSelector.this.index;
try {
ISItem isItem = ImageSelector.this.originalItems.get(idx);
imagePlatformPath = isItem.getUrl().toString();
imageName = isItem.getText();
getParent().dispose();
} catch (IndexOutOfBoundsException e) {
// do nothing
e.printStackTrace(); // NOSONAR
LOGGER.warn( String.format( "selectImage(): no index %d in originalItems", idx ), e );
}
}
/**
* Gets the image platform path.
*
* @return the image platform path
*/
public String getImagePlatformPath() {
return imagePlatformPath;
}
/**
* Gets the image name.
*
* @return the image name
*/
public String getImageName() {
return imageName;
}
}