blob: dfbf1713c38cff644c15bafba1415acab01635c6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 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@gmail.com) - initial API and implementation
*******************************************************************************/
package org.mihalis.opal.starRating;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
/**
* Instances of this class provide a rating element.
* <p>
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>HORIZONTAL</dd>
* <dd>VERTICAL</dd> *
* <dt><b>Events:</b></dt>
* <dd>Selection</dd>
* </dl>
* </p>
*/
public class StarRating extends Canvas {
/** The Constant SIZE_SMALL. */
private static final int SIZE_SMALL = 16;
/** The Constant SIZE_BIG. */
private static final int SIZE_BIG = 32;
/**
* The Enum SIZE.
*/
public enum SIZE {
/** The small. */
SMALL,
/** The big. */
BIG
};
/** The size of stars. */
private SIZE sizeOfStars;
/** The max number of stars. */
private int maxNumberOfStars;
/** The current number of stars. */
private int currentNumberOfStars;
/** The Constant DEFAULT_MAX_NUMBERS_OF_STARS. */
private static final int DEFAULT_MAX_NUMBERS_OF_STARS = 5;
/** The stars. */
private final List<Star> stars;
/** The orientation. */
private int orientation;
/** The selection listeners. */
private final List<SelectionListener> selectionListeners;
/**
* 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 StarRating(final Composite parent, final int style) {
super(parent, checkStyle(style) | SWT.DOUBLE_BUFFERED);
sizeOfStars = SIZE.SMALL;
currentNumberOfStars = 0;
if ((style & SWT.VERTICAL) != 0) {
orientation = SWT.VERTICAL;
} else {
orientation = SWT.HORIZONTAL;
}
stars = new ArrayList<Star>();
selectionListeners = new ArrayList<SelectionListener>();
setMaxNumberOfStars(DEFAULT_MAX_NUMBERS_OF_STARS);
initListeners();
}
/**
* Check style.
*
* @param style the style
* @return the int
*/
private static int checkStyle(int style) {
if ((style & SWT.VERTICAL) != 0) {
style = style & ~SWT.VERTICAL;
}
if ((style & SWT.HORIZONTAL) != 0) {
style = style & ~SWT.HORIZONTAL;
}
return style;
}
/**
* Inits the listeners.
*/
private void initListeners() {
final Listener listener = new Listener() {
@Override
public void handleEvent(final Event event) {
switch (event.type) {
case SWT.MouseEnter:
case SWT.MouseMove:
onMouseEnterOrMove(event);
break;
case SWT.MouseExit:
onMouseExit(event);
break;
case SWT.MouseUp:
onMouseUp(event);
break;
case SWT.Paint:
onMousePaint(event);
break;
case SWT.Dispose:
onDispose(event);
break;
}
}
};
final int[] events = new int[] { SWT.MouseEnter, SWT.MouseMove, SWT.MouseExit, SWT.MouseUp, SWT.Paint, SWT.Dispose };
for (final int event : events) {
addListener(event, listener);
}
}
/**
* On mouse enter or move.
*
* @param event the event
*/
private void onMouseEnterOrMove(final Event event) {
for (final Star star : stars) {
star.hover = false;
}
for (final Star star : stars) {
final boolean mouseHover = star.bounds.contains(event.x, event.y);
star.hover = true;
if (mouseHover) {
break;
}
}
redraw();
update();
}
/**
* On mouse exit.
*
* @param event the event
*/
private void onMouseExit(final Event event) {
for (final Star star : stars) {
star.hover = false;
}
redraw();
update();
}
/**
* On mouse up.
*
* @param event the event
*/
private void onMouseUp(final Event event) {
for (int i = 0; i < maxNumberOfStars; i++) {
final Star star = stars.get(i);
final boolean selected = star.bounds.contains(event.x, event.y);
if (selected) {
setCurrentNumberOfStars(i + 1);
fireSelectionEvent();
redraw();
update();
break;
}
}
}
/**
* Fire selection event.
*/
private void fireSelectionEvent() {
final Event event = new Event();
event.widget = this;
event.display = getDisplay();
event.item = this;
event.type = SWT.Selection;
for (final SelectionListener selectionListener : selectionListeners) {
selectionListener.widgetSelected(new SelectionEvent(event));
}
}
/**
* On mouse paint.
*
* @param event the event
*/
private void onMousePaint(final Event event) {
final GC gc = event.gc;
int x = 0, y = 0;
for (final Star star : stars) {
star.draw(gc, x, y);
if (orientation == SWT.VERTICAL) {
y += sizeOfStars.equals(SIZE.BIG) ? SIZE_BIG : SIZE_SMALL;
} else {
x += sizeOfStars.equals(SIZE.BIG) ? SIZE_BIG : SIZE_SMALL;
}
}
}
/**
* On dispose.
*
* @param event the event
*/
private void onDispose(final Event event) {
for (final Star star : stars) {
star.dispose();
}
}
/**
* Adds the listener to the collection of listeners who will be notified
* when the control is selected by the user, by sending it one of the
* messages defined in the <code>SelectionListener</code> interface.
* <p>
* <code>widgetDefaultSelected</code> is not called.
* </p>
*
* @param listener the listener which should be notified when the control is
* selected by the user,
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void addSelectionListener(final SelectionListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
this.selectionListeners.add(listener);
}
/**
* Compute size.
*
* @param wHint the w hint
* @param hHint the h hint
* @param changed the changed
* @return the point
* @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
*/
@Override
public Point computeSize(final int wHint, final int hHint, final boolean changed) {
if (orientation == SWT.VERTICAL) {
return computeSizeVertical();
}
return computeSizeHorizontal();
}
/**
* Compute size vertical.
*
* @return the point
*/
private Point computeSizeVertical() {
final int width = sizeOfStars.equals(SIZE.BIG) ? SIZE_BIG : SIZE_SMALL;
final int height = maxNumberOfStars * width;
return new Point(width + getBorderWidth() * 2, height + getBorderWidth() * 2);
}
/**
* Compute size horizontal.
*
* @return the point
*/
private Point computeSizeHorizontal() {
final int height = sizeOfStars.equals(SIZE.BIG) ? SIZE_BIG : SIZE_SMALL;
final int width = maxNumberOfStars * height;
return new Point(width + getBorderWidth() * 2, height + getBorderWidth() * 2);
}
/**
* Gets the current number of stars.
*
* @return the number of selected stars
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public int getCurrentNumberOfStars() {
checkWidget();
return currentNumberOfStars;
}
/**
* Gets the max number of stars.
*
* @return the maximum number of stars that is displayed by this component
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public int getMaxNumberOfStars() {
checkWidget();
return maxNumberOfStars;
}
/**
* Gets the orientation.
*
* @return the orientation of this widget (SWT.VERTICAL or SWT.HORIZONTAL)
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public int getOrientation() {
checkWidget();
return orientation;
}
/**
* Gets the size of stars.
*
* @return the size of stars
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public SIZE getSizeOfStars() {
checkWidget();
return sizeOfStars;
}
/**
* Removes the listener from the collection of listeners who will be
* notified when the control is selected by the user.
*
* @param listener the listener which should no longer be notified
* @see SelectionListener
* @see #addSelectionListener
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void removeSelectionListener(final SelectionListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
this.selectionListeners.remove(listener);
}
/**
* Set the current number of stars.
*
* @param currentNumberOfStars current number of stars
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the number of star is
* negative or greater than the maximum number of stars</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void setCurrentNumberOfStars(final int currentNumberOfStars) {
checkWidget();
if (currentNumberOfStars < 0 || currentNumberOfStars > maxNumberOfStars) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
this.currentNumberOfStars = currentNumberOfStars;
for (final Star star : stars) {
star.marked = false;
}
for (int i = 0; i < currentNumberOfStars; i++) {
stars.get(i).marked = true;
}
}
/**
* Set the maximum number of stars.
*
* @param maxNumberOfStars the new max number of stars
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void setMaxNumberOfStars(final int maxNumberOfStars) {
this.maxNumberOfStars = maxNumberOfStars;
reinitStars();
}
/**
* Reinit stars.
*/
private void reinitStars() {
for (final Star star : stars) {
star.dispose();
}
stars.clear();
for (int i = 0; i < maxNumberOfStars; i++) {
if (sizeOfStars.equals(SIZE.BIG)) {
stars.add(Star.initBig(this));
} else {
stars.add(Star.initSmall(this));
}
}
}
/**
* Set the current size of stars.
*
* @param sizeOfStars current number of stars
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the number of star is
* negative or greater than the maximum number of stars</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void setSizeOfStars(final SIZE sizeOfStars) {
checkWidget();
this.sizeOfStars = sizeOfStars;
reinitStars();
}
}