| /******************************************************************************* |
| * 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; |
| } |
| } |