diff options
Diffstat (limited to 'org.mihalis.opal/src/main/java/org/mihalis/opal/imageSelector/ImageSelector.java')
-rw-r--r-- | org.mihalis.opal/src/main/java/org/mihalis/opal/imageSelector/ImageSelector.java | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/org.mihalis.opal/src/main/java/org/mihalis/opal/imageSelector/ImageSelector.java b/org.mihalis.opal/src/main/java/org/mihalis/opal/imageSelector/ImageSelector.java new file mode 100644 index 0000000..8cf7d58 --- /dev/null +++ b/org.mihalis.opal/src/main/java/org/mihalis/opal/imageSelector/ImageSelector.java @@ -0,0 +1,679 @@ +/******************************************************************************* + * 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.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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; + +/** + * 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; + + /** 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; + + /** + * 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) { + 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); + + 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 * index); + break; + case SWT.END: + scrollBy(index); + break; + case SWT.PAGE_UP: + scrollBy(-1 * pageIncrement); + break; + case SWT.PAGE_DOWN: + scrollBy(pageIncrement); + break; + } + } + }); + } + + /** + * 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); + } + + /** + * 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); + } + } + } + }); + + } + + /** + * 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(); + } + + /** + * 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; + } + +} |