| /******************************************************************************* |
| * Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency |
| * 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: |
| * Pierre Allard, |
| * Regent L'Archeveque, |
| * Sebastien Gemme - initial API and implementation |
| * |
| * SPDX-License-Identifier: EPL-1.0 |
| * |
| *******************************************************************************/ |
| package org.eclipse.apogy.common.widgets.ui; |
| |
| import java.net.URL; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Device; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Transform; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class ArrowButtonComposite extends Composite { |
| |
| private static final Logger Logger = LoggerFactory.getLogger(ArrowButtonComposite.class); |
| |
| public static enum ArrowDirection { |
| LEFT, RIGHT |
| }; // Direction of the button |
| |
| private static final int ANGLE_TO_GET_INVERSE = 180; // Angle in degree |
| private static final int NB_MILLIS_AUTO_REPEAT_DOWN_ACTION = 20; // Time in millis before the next repeat occur. |
| private static final int NB_MILLIS_BEFORE_AUTO_REPEAT = 400; // Time in millis before activate auto repeat effect. |
| |
| private static final String IMAGES_FOLDER = "platform:/plugin/org.eclipse.apogy.common.widgets.ui/images/"; // Image |
| // path |
| |
| private Image arrowImage; // Image of arrow. |
| private Image arrowImagePressed; // Image of pressed arrow. |
| |
| // Variables for the auto repeat function of the button. |
| private MouseListener mouseListener; |
| private Thread repeatEventThread; |
| private boolean repeatThreadCanRun; |
| private final boolean autoRepeatDownAction; |
| |
| private final ArrowDirection arrowDirection; // Direction of the arrow. |
| private boolean isPressed; // True if button is actually pressed. |
| |
| /** |
| * Constructor parameter information: |
| * |
| * @param arrowDirection Choose direction of the button. |
| * @param autoRepeatDownAction To true, it will call mouse down event in loop to |
| * all mouse listener until mouse up is called. If |
| * its false, you will receive only one event for |
| * the mouse down on the button. |
| */ |
| public ArrowButtonComposite(Composite parent, int style, ArrowDirection arrowDirection, |
| boolean autoRepeatDownAction) { |
| super(parent, style | SWT.DOUBLE_BUFFERED); |
| |
| // Initialize values |
| this.repeatThreadCanRun = false; |
| this.arrowDirection = arrowDirection; |
| this.isPressed = false; |
| this.repeatEventThread = null; |
| this.autoRepeatDownAction = autoRepeatDownAction; |
| |
| // Initialize images |
| initImages(); |
| |
| // Create listeners |
| createPaintListener(); |
| createDisposeListener(); |
| createMouseListener(); |
| } |
| |
| @Override |
| public void notifyListeners(int eventType, Event event) { |
| if (isEnabled()) |
| super.notifyListeners(eventType, event); |
| } |
| |
| /** |
| * Initialize images. |
| */ |
| private void initImages() { |
| try { |
| Device device = Display.getCurrent(); |
| URL url = new URL(IMAGES_FOLDER + "arrowButton.png"); |
| this.arrowImage = new Image(device, url.openStream()); |
| url = new URL(IMAGES_FOLDER + "arrowButtonPressed.png"); |
| this.arrowImagePressed = new Image(device, url.openStream()); |
| } catch (Exception e) { |
| Logger.error(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * This method draw the button. |
| */ |
| private void paintMeter(GC gc) { |
| |
| // If the arrow direction is right, the canvas is rotated to draw inverse |
| // images. |
| verifyOrientationOfCanvas(gc); |
| |
| // Draw image. |
| if (this.isPressed) { |
| if (this.arrowImagePressed != null) |
| drawPressedArrow(gc); |
| } else { |
| if (this.arrowImage != null) |
| drawArrow(gc); |
| else // Draw a black rectangle (This code is only useful in windows builder to see |
| // the widget without loading images). |
| drawBlackRectangle(gc); |
| } |
| } |
| |
| private void verifyOrientationOfCanvas(GC gc) { |
| Transform t = new Transform(gc.getDevice()); |
| t.rotate(ANGLE_TO_GET_INVERSE); |
| if (this.arrowDirection == ArrowDirection.RIGHT) |
| gc.setTransform(t); |
| t.dispose(); |
| } |
| |
| private void drawPressedArrow(GC gc) { |
| if (this.arrowDirection == ArrowDirection.RIGHT) { |
| gc.drawImage(this.arrowImagePressed, 0, 0, this.arrowImagePressed.getBounds().width, |
| this.arrowImagePressed.getBounds().height, -getBounds().width, -getBounds().height, |
| getBounds().width, getBounds().height); |
| } else { |
| gc.drawImage(this.arrowImagePressed, 0, 0, this.arrowImagePressed.getBounds().width, |
| this.arrowImagePressed.getBounds().height, 0, 0, getBounds().width, getBounds().height); |
| } |
| } |
| |
| private void drawArrow(GC gc) { |
| if (this.arrowDirection == ArrowDirection.RIGHT) { |
| gc.drawImage(this.arrowImage, 0, 0, this.arrowImage.getBounds().width, this.arrowImage.getBounds().height, |
| -getBounds().width, -getBounds().height, getBounds().width, getBounds().height); |
| } else { |
| gc.drawImage(this.arrowImage, 0, 0, this.arrowImage.getBounds().width, this.arrowImage.getBounds().height, |
| 0, 0, getBounds().width, getBounds().height); |
| } |
| } |
| |
| private void drawBlackRectangle(GC gc) { |
| if (this.arrowDirection == ArrowDirection.RIGHT) { |
| Color color = new Color(getDisplay(), 0, 0, 0); |
| gc.setForeground(color); |
| gc.setLineWidth(4); |
| gc.drawRectangle(-(getBounds().width - 2), -(getBounds().height - 2), getBounds().width - 4, |
| getBounds().height - 4); |
| color.dispose(); |
| } else { |
| Color color = new Color(getDisplay(), 0, 0, 0); |
| gc.setForeground(color); |
| gc.setLineWidth(4); |
| gc.drawRectangle(2, 2, getBounds().width - 4, getBounds().height - 4); |
| color.dispose(); |
| } |
| } |
| |
| private void createPaintListener() { |
| // paintMeter() method is called when the paint listener get a event. |
| addPaintListener(new PaintListener() { |
| @Override |
| public void paintControl(PaintEvent e) { |
| paintMeter(e.gc); |
| } |
| }); |
| } |
| |
| private void createDisposeListener() { |
| // Dispose all images when widget is disposed. |
| this.addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| if (ArrowButtonComposite.this.arrowImage != null) |
| ArrowButtonComposite.this.arrowImage.dispose(); |
| if (ArrowButtonComposite.this.arrowImagePressed != null) |
| ArrowButtonComposite.this.arrowImagePressed.dispose(); |
| |
| } |
| }); |
| } |
| |
| /** |
| * This listener react to auto repeat system. When autoRepeatDownAction is true |
| * and the user dont up is mouse button, all other mouse listener called in |
| * loop. |
| */ |
| private void createMouseListener() { |
| this.mouseListener = new MouseListener() { |
| @Override |
| public void mouseDown(MouseEvent e) { |
| if (ArrowButtonComposite.this.autoRepeatDownAction) { |
| if (ArrowButtonComposite.this.repeatEventThread == null) { |
| ArrowButtonComposite.this.isPressed = true; |
| redraw(); |
| ArrowButtonComposite.this.repeatThreadCanRun = true; |
| ArrowButtonComposite.this.repeatEventThread = new Thread() { |
| @Override |
| public void run() { |
| for (int i = 0; i < NB_MILLIS_BEFORE_AUTO_REPEAT; i++) { |
| try { |
| Thread.sleep(1); |
| } catch (InterruptedException e) { |
| Logger.error(e.getMessage(), e); |
| } |
| if (!ArrowButtonComposite.this.repeatThreadCanRun || isDisposed()) |
| return; |
| } |
| while (ArrowButtonComposite.this.repeatThreadCanRun && !isDisposed()) { |
| Display.getDefault().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (ArrowButtonComposite.this.repeatThreadCanRun && !isDisposed()) |
| notifyListeners(SWT.MouseDown, null); |
| } |
| }); |
| for (int i = 0; i < NB_MILLIS_AUTO_REPEAT_DOWN_ACTION; i++) { |
| try { |
| Thread.sleep(1); |
| } catch (InterruptedException e) { |
| Logger.error(e.getMessage(), e); |
| } |
| if (!ArrowButtonComposite.this.repeatThreadCanRun || isDisposed()) |
| return; |
| } |
| } |
| } |
| }; |
| ArrowButtonComposite.this.repeatEventThread.start(); |
| } |
| } else { |
| ArrowButtonComposite.this.isPressed = true; |
| redraw(); |
| } |
| } |
| |
| @Override |
| public void mouseUp(MouseEvent e) { |
| ArrowButtonComposite.this.isPressed = false; |
| redraw(); |
| ArrowButtonComposite.this.repeatThreadCanRun = false; |
| if (ArrowButtonComposite.this.repeatEventThread != null) { |
| ArrowButtonComposite.this.repeatEventThread = null; |
| } |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| } |
| }; |
| addMouseListener(this.mouseListener); |
| } |
| } |