blob: 7b16f6b291c0c92e4d36e8ae84ebc90c472a15b8 [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@gmail.com) - initial API and implementation
*******************************************************************************/
package org.mihalis.opal.angles;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
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;
import org.mihalis.opal.utils.SWTGraphicUtil;
/**
* Instances of this class provide a selectable user interface object that can
* be used to pick angles. Inspired by the Swing AngleSlider by Jeremy
* (http://javagraphics.blogspot.com/2008/05/angles-need-gui-widget-for-angles.
* html)
* <p>
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>BORDER</dd>
* <dt><b>Events:</b></dt>
* <dd>Selection</dd>
* </dl>
*/
public class AngleSlider extends Canvas {
/** The Constant WHOLE_RADIUS. */
private static final int WHOLE_RADIUS = 40;
/** The Constant BUTTON_RADIUS. */
private static final int BUTTON_RADIUS = 10;
/** The Constant STEP. */
private static final int STEP = 5;
/** The background image. */
private final Image backgroundImage;
/** The button focus. */
private final Image buttonFocus;
/** The button no focus. */
private final Image buttonNoFocus;
/** The selection. */
private int selection;
/** The selection listeners. */
private final List<SelectionListener> selectionListeners;
/** The mouse pressed. */
private boolean mousePressed;
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a widget which will be the parent of the new instance
* (cannot be null)
* @param style not used
*
* @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 AngleSlider(final Composite parent, final int style) {
super(parent, style | SWT.DOUBLE_BUFFERED);
final ClassLoader loader = org.mihalis.opal.angles.AngleSlider.class.getClassLoader();
backgroundImage = new Image(getDisplay(), loader.getResourceAsStream("images/angleBackground.png"));
buttonFocus = new Image(getDisplay(), loader.getResourceAsStream("images/angleButtonFocus.png"));
buttonNoFocus = new Image(getDisplay(), loader.getResourceAsStream("images/angleButtonFocusLost.png"));
addListeners();
selection = 0;
selectionListeners = new ArrayList<SelectionListener>();
}
/**
* Add listeners.
*/
private void addListeners() {
addListener(SWT.Paint, new Listener() {
@Override
public void handleEvent(final Event event) {
paintControl(event);
}
});
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent arg0) {
SWTGraphicUtil.safeDispose(backgroundImage);
SWTGraphicUtil.safeDispose(buttonFocus);
SWTGraphicUtil.safeDispose(buttonNoFocus);
}
});
final int[] listeners = new int[] { SWT.MouseDown, SWT.MouseUp, SWT.MouseMove };
for (final int listener : listeners) {
addListener(listener, createMouseListener());
}
addListener(SWT.KeyDown, createKeyListener());
}
/**
* Paint control.
*
* @param event the event
*/
private void paintControl(final Event event) {
final GC gc = event.gc;
gc.drawImage(backgroundImage, 0, 0);
float angle = selection / 360f;
angle = (float) (angle * 2 * Math.PI - 0.5 * Math.PI);
final float centerX = WHOLE_RADIUS / 2f;
final float centerY = WHOLE_RADIUS / 2f;
final float radius = BUTTON_RADIUS;
final float x = (float) (centerX - radius * Math.cos(angle));
final float y = (float) (centerY - radius * Math.sin(angle));
if (isFocusControl()) {
gc.drawImage(buttonFocus, (int) x - 2, (int) y - 2);
} else {
gc.drawImage(buttonNoFocus, (int) x - 2, (int) y - 2);
}
if (!isEnabled()) {
gc.setAlpha(127);
gc.setAntialias(SWT.ON);
gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
gc.fillOval(4, 4, WHOLE_RADIUS - 7, WHOLE_RADIUS - 7);
}
}
/**
* Creates the mouse listener.
*
* @return the listener
*/
private Listener createMouseListener() {
return new Listener() {
@Override
public void handleEvent(final Event event) {
if (!isEnabled()) {
return;
}
if (event.type == SWT.MouseDown) {
mousePressed = true;
}
if (event.type == SWT.MouseDown || event.type == SWT.MouseMove && mousePressed) {
final float deltaX = event.x - WHOLE_RADIUS / 2f;
final float deltaY = event.y - WHOLE_RADIUS / 2f;
final double angle = Math.atan2(deltaX, deltaY);
selection = 360 - (int) (360 * angle / (2 * Math.PI) + 360) % 360;
redraw();
}
if (event.type == SWT.MouseUp) {
mousePressed = false;
fireSelectionListeners(event);
}
}
};
}
/**
* Fire selection listeners.
*
* @param event the event
*/
private void fireSelectionListeners(final Event event) {
for (final SelectionListener selectionListener : selectionListeners) {
selectionListener.widgetSelected(new SelectionEvent(event));
}
}
/**
* Creates the key listener.
*
* @return the listener
*/
private Listener createKeyListener() {
return new Listener() {
@Override
public void handleEvent(final Event event) {
if (!isEnabled()) {
return;
}
if (event.type != SWT.KeyDown) {
return;
}
if (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_LEFT) {
setSelection(selection + STEP);
}
if (event.keyCode == SWT.ARROW_DOWN || event.keyCode == SWT.ARROW_RIGHT) {
setSelection(selection - STEP);
}
}
};
}
/**
* Adds the selection listener.
*
* @param selectionListener the selection listener
* @see org.eclipse.swt.widgets.Scale#addSelectionListener(org.eclipse.swt.events.SelectionListener)
*/
public void addSelectionListener(final SelectionListener selectionListener) {
checkWidget();
selectionListeners.add(selectionListener);
}
/**
* 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) {
checkWidget();
return new Point(WHOLE_RADIUS, WHOLE_RADIUS);
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
@Override
public void dispose() {
super.dispose();
backgroundImage.dispose();
buttonFocus.dispose();
buttonNoFocus.dispose();
}
/**
* Gets the selection.
*
* @return the selection
* @see org.eclipse.swt.widgets.Scale#getSelection()
*/
public int getSelection() {
checkWidget();
return selection;
}
/**
* Removes the selection listener.
*
* @param selectionListener the selection listener
* @see org.eclipse.swt.widgets.Scale#removeSelectionListener(org.eclipse.swt.events.SelectionListener)
*/
public void removeSelectionListener(final SelectionListener selectionListener) {
checkWidget();
selectionListeners.remove(selectionListener);
}
/**
* Sets the enabled.
*
* @param enabled the new enabled
* @see org.eclipse.swt.widgets.Control#setEnabled(boolean)
*/
@Override
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
redraw();
}
/**
* Sets the selection.
*
* @param selection the new selection
* @see org.eclipse.swt.widgets.Scale#setSelection(int)
*/
public void setSelection(final int selection) {
checkWidget();
if (selection < 0 || selection > 360) {
SWT.error(SWT.ERROR_CANNOT_SET_SELECTION);
}
this.selection = selection;
fireSelectionListeners(new Event());
redraw();
}
}