Java tutorial
/* * Copyright 2014 PRImA Research Lab, University of Salford, United Kingdom * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.primaresearch.web.gwt.client.ui; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.primaresearch.maths.geometry.Point; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.event.dom.client.HasAllMouseHandlers; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.dom.client.MouseWheelEvent; import com.google.gwt.event.dom.client.MouseWheelHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.Widget; /** * Scroll panel handling mouse events to enable panning by dragging the mouse.<br> * <br> * The first widget that is added to the panel is used for the scrolling. * * @author Christian Clausner * */ public class MouseScrollPanel extends AbsolutePanel implements HasAllMouseHandlers, MouseWheelHandler, MouseOverHandler, MouseOutHandler, MouseMoveHandler, MouseUpHandler, MouseDownHandler { /** Values close to 1 mean slow deceleration; higher values mean fast deceleration. Reasonable range: (1.1,5) */ private static double scrollingDecelerationFactor = 1.5; private Point mouseDownPoint = null; private Point scrollWidgetBasePosition; //Mouse move history private Point mouseMovePoint1 = null; private long mouseDownTime1; private Point mouseMovePoint2 = null; private long mouseDownTime2; //Auto scroll private double scrollingSpeedX; private double scrollingSpeedY; private Timer autoScrollTimer = null; private boolean isAutoScrolling = false; private Set<ScrollListener> scrollListeners = new HashSet<ScrollListener>(); private double targetScrollX = Double.MIN_VALUE; private double targetScrollY = Double.MIN_VALUE; private boolean enableMouseWheelScrolling; private boolean isDragged = false; private MouseHandlerExtension mouseHandlerExtention = null; /** * Constructor * @param enableMouseWheelScrolling Set to <code>true</code> to enable scrolling up/down via mouse wheel */ public MouseScrollPanel(boolean enableMouseWheelScrolling) { super(); this.enableMouseWheelScrolling = enableMouseWheelScrolling; this.addMouseWheelHandler(this); this.addMouseOverHandler(this); this.addMouseOutHandler(this); this.addMouseMoveHandler(this); this.addMouseUpHandler(this); this.addMouseDownHandler(this); getElement().getStyle().setProperty("overflow", "visible"); } public void setMouseHandlerExtension(MouseHandlerExtension mouseHandlerExtention) { this.mouseHandlerExtention = mouseHandlerExtention; } public void addScrollListener(ScrollListener listener) { this.scrollListeners.add(listener); } public void removeScrollListener(ScrollListener listener) { this.scrollListeners.remove(listener); } @Override public final void onMouseWheel(MouseWheelEvent event) { boolean handle = true; if (mouseHandlerExtention != null) handle = !mouseHandlerExtention.onMouseWheel(event); if (handle) { if (autoScrollTimer != null) autoScrollTimer.cancel(); if (!enableMouseWheelScrolling) return; //Ctrl + Mouse wheel if (event.isControlKeyDown()) { //Handled somewhere else } //Scrolling else { int dy = (int) confineVertically(-10 * event.getDeltaY()); if (dy == 0) dy = workaroundEventGetMouseWheelVelocityY(event.getNativeEvent()); if (dy != 0) { for (int i = 0; i < this.getWidgetCount(); i++) { Widget widget = this.getWidget(i); if (widget != null && mouseDownPoint == null) { int x = this.getWidgetLeft(widget); int y = this.getWidgetTop(widget); this.setWidgetPosition(widget, x, y + dy - 1); } } } } } if (mouseHandlerExtention != null) mouseHandlerExtention.postMouseWheel(event); } private static native int workaroundEventGetMouseWheelVelocityY(NativeEvent evt) /*-{ if (typeof evt.wheelDelta == "undefined") { return 0; } return Math.round(evt.wheelDelta / 40) || 0; }-*/; @Override public void onMouseOver(MouseOverEvent event) { boolean handle = true; if (mouseHandlerExtention != null) handle = !mouseHandlerExtention.onMouseOver(event); if (handle) { } if (mouseHandlerExtention != null) mouseHandlerExtention.postMouseOver(event); } @Override public final void onMouseOut(MouseOutEvent event) { boolean handle = true; if (mouseHandlerExtention != null) handle = !mouseHandlerExtention.onMouseOut(event); if (handle) { } isDragged = false; if (mouseHandlerExtention != null) mouseHandlerExtention.postMouseOut(event); //mouseDownPoint = null; //event.stopPropagation(); } @Override public final void onMouseMove(MouseMoveEvent event) { Event e = DOM.eventGetCurrentEvent(); e.preventDefault(); boolean handle = true; if (mouseHandlerExtention != null) handle = !mouseHandlerExtention.onMouseMove(event); if (mouseDownPoint != null) { mouseMovePoint2 = mouseMovePoint1; mouseDownTime2 = mouseDownTime1; mouseMovePoint1 = new Point(event.getX(), event.getY()); mouseDownTime1 = new Date().getTime(); if (handle) { Point refPos = getScrollWidgetPos(); int mouseDx = event.getClientX() - mouseDownPoint.x; int mouseDy = event.getClientY() - mouseDownPoint.y; int dx = (scrollWidgetBasePosition.x + mouseDx) - refPos.x; int dy = (scrollWidgetBasePosition.y + mouseDy) - refPos.y; moveScrollWidget(dx, dy); } event.stopPropagation(); isDragged = isDragged || dist(event.getClientX(), event.getClientY(), mouseDownPoint.x, mouseDownPoint.y) > 5.0; } if (mouseHandlerExtention != null) mouseHandlerExtention.postMouseMove(event); } private Point getScrollWidgetPos() { if (this.getWidgetCount() > 0) return new Point(this.getWidget(0).getElement().getOffsetLeft(), this.getWidget(0).getElement().getOffsetTop()); return new Point(); } private void moveScrollWidget(double dx, double dy) { dx = confineHorizontally(dx); dy = confineVertically(dy); //Logger logger = Logger.getLogger("CrowdSourcing"); //logger.log(Level.INFO, "autoScroll: "+dx+", "+dy); if (this.getWidgetCount() > 0) { Widget widget = this.getWidget(0); if (widget != null) { int x = widget.getElement().getOffsetLeft() + (int) (dx + 0.5); int y = widget.getElement().getOffsetTop() + (int) (dy + 0.5); //this.setWidgetPosition(widget, x, y); widget.getElement().getStyle().setPosition(Position.ABSOLUTE); widget.getElement().getStyle().setLeft(x, Style.Unit.PX); widget.getElement().getStyle().setTop(y, Style.Unit.PX); } } notifyScrollPositionChanged(); } private double confineHorizontally(double dx) { double confined = dx; if (this.getWidgetCount() <= 0) return confined; int panelWidth = this.getElement().getClientWidth(); Widget refWidget = this.getWidget(0); int refWidgetWidth = refWidget.getElement().getClientWidth(); //Maximum visible background around the reference widget int padding = Math.max(panelWidth - refWidgetWidth / 2, panelWidth / 2); double refLeft = refWidget.getElement().getOffsetLeft(); //Widget moving to the right if (dx > 0) { //Check if out of bounds if (refLeft + dx > padding) { return padding - refLeft; //return Math.max(0.0, padding - refLeft); } } //Widget moving to the left else if (dx < 0) { double refRight = refLeft + refWidgetWidth; //Check if out of bounds if (refRight + dx < panelWidth - padding) { return panelWidth - padding - refRight; //return Math.min(0.0, panelWidth-padding-refRight); } } return confined; } private double confineVertically(double dy) { double confined = dy; if (this.getWidgetCount() <= 0) return confined; int panelHeight = this.getElement().getClientHeight(); Widget refWidget = this.getWidget(0); int refWidgetHeight = refWidget.getElement().getClientHeight(); //Maximum visible background around the reference widget int padding = Math.max(panelHeight - refWidgetHeight / 2, panelHeight / 2); double refTop = refWidget.getElement().getOffsetTop(); //Widget moving to the bottom if (dy > 0) { //Check if out of bounds if (refTop + dy > padding) { return padding - refTop; //return Math.max(0.0, padding - refTop); } } //Widget moving to the top else if (dy < 0) { double refBottom = refTop + refWidgetHeight; //Check if out of bounds if (refBottom + dy < panelHeight - padding) { return panelHeight - padding - refBottom; //return Math.min(0.0, panelHeight-padding - refBottom); } } return confined; } @Override public final void onMouseUp(MouseUpEvent event) { boolean handle = true; if (mouseHandlerExtention != null) handle = !mouseHandlerExtention.onMouseUp(event); if (handle) { //Calculate speed if (mouseMovePoint2 != null) { long now = new Date().getTime(); int timePassed = (int) (now - mouseDownTime2); //double distance = dist(event.getX(), event.getY(), (int)mouseMovePoint2.getX(), (int)mouseMovePoint2.getY()); double moveX = -mouseMovePoint2.x + event.getX(); double moveY = -mouseMovePoint2.y + event.getY(); scrollingSpeedX = 0.0; scrollingSpeedY = 0.0; if (timePassed > 0) { scrollingSpeedX = moveX / (double) timePassed; scrollingSpeedY = moveY / (double) timePassed; } if (Math.abs(scrollingSpeedX) > 0.0 || Math.abs(scrollingSpeedY) > 0.0) { // Setup timer to refresh if (autoScrollTimer != null) autoScrollTimer.cancel(); startAutoScroll(); //inertia mode } } } isDragged = false; mouseMovePoint1 = null; mouseMovePoint2 = null; mouseDownPoint = null; event.stopPropagation(); DOM.releaseCapture(this.getElement()); if (mouseHandlerExtention != null) mouseHandlerExtention.postMouseUp(event); } private void startAutoScroll() { final int refreshTimespan = 30; autoScrollTimer = new Timer() { @Override public void run() { synchronized (this) { isAutoScrolling = true; autoScroll(refreshTimespan, this); } } @Override public void cancel() { super.cancel(); synchronized (this) { if (isAutoScrolling) { targetScrollX = Double.MIN_VALUE; targetScrollY = Double.MIN_VALUE; notifyAutoScrollFinished(); } isAutoScrolling = false; } } }; autoScrollTimer.scheduleRepeating(refreshTimespan); } public boolean isDragged() { return isDragged; } /** * Returns true if scrolling at the moment. */ public boolean isAutoScrolling() { return isAutoScrolling; } private void autoScroll(int refreshTimespan, Timer refreshTimer) { //There are two different modes: // 1: Inertia scrolling: Slowing down after mouse drag // 2: Smooth moving to target position //Mode 1 (inertia) if (targetScrollX == Double.MIN_VALUE) { double dx = scrollingSpeedX * refreshTimespan; double dy = scrollingSpeedY * refreshTimespan; moveScrollWidget(dx, dy); //Slow down scrollingSpeedX /= scrollingDecelerationFactor; scrollingSpeedY /= scrollingDecelerationFactor; //Stop timer? if (Math.abs(scrollingSpeedX) < 0.0001 && Math.abs(scrollingSpeedY) < 0.0001) { refreshTimer.cancel(); } } //Mode 2 (smooth moving to target) else { boolean reachedTargetX = false; boolean reachedTargetY = false; Point refPos = getScrollWidgetPos(); double dx = targetScrollX - refPos.x; double dy = targetScrollY - refPos.y; //X if (Math.abs(dx) <= 4.0) reachedTargetX = true; //Y if (Math.abs(dy) <= 4.0) reachedTargetY = true; //Finished? if (reachedTargetX && reachedTargetY) { refreshTimer.cancel(); } else { moveScrollWidget(dx / 3.0, dy / 3.0); } } } //Distance between two points private double dist(int x1, int y1, int x2, int y2) { return Math.sqrt(((double) ((x2 - x1) * (x2 - x1))) + ((double) ((y2 - y1) * (y2 - y1)))); } @Override public void onMouseDown(MouseDownEvent event) { DOM.setCapture(this.getElement()); Event e = DOM.eventGetCurrentEvent(); e.preventDefault(); boolean handle = true; if (mouseHandlerExtention != null) handle = !mouseHandlerExtention.onMouseDown(event); if (autoScrollTimer != null) autoScrollTimer.cancel(); if (handle) { mouseDownPoint = new Point(event.getClientX(), event.getClientY()); //Get widget positions scrollWidgetBasePosition = getScrollWidgetPos(); } //for (int i=0; i<this.getWidgetCount(); i++) { // Widget widget = this.getWidget(i); // DOM.setIntStyleAttribute(widget.getElement(), "zIndex", i); //} event.stopPropagation(); if (mouseHandlerExtention != null) mouseHandlerExtention.postMouseDown(event); } /** * Returns the view rectangle position in relation to the first child widget. */ public Point getScrollPosition() { if (this.getWidgetCount() > 0) { Point refPos = getScrollWidgetPos(); return new Point(-refPos.x, -refPos.y); } return new Point(); } /** * Scrolls the view rectangle to a position relative to the first child widget. */ public void scrollToPosition(int x, int y) { if (autoScrollTimer != null) autoScrollTimer.cancel(); if (this.getWidgetCount() > 0) { Widget widget = this.getWidget(0); int dx = -this.getWidgetLeft(widget) - x; int dy = -this.getWidgetTop(widget) - y; moveScrollWidget(dx, dy); } } /** * Scrolls relatively to the current position. */ public void scroll(int dx, int dy) { scroll(dx, dy, false); } /** * Scrolls relatively to the current position. */ public void scroll(int dx, int dy, boolean smooth) { dx = (int) confineHorizontally(dx); dy = (int) confineVertically(dy); if (autoScrollTimer != null) autoScrollTimer.cancel(); if (smooth) { //Smooth auto scrolling Point refPos = getScrollWidgetPos(); targetScrollX = refPos.x + dx; targetScrollY = refPos.y + dy; startAutoScroll(); } else { //Scroll immediately moveScrollWidget(dx, dy); } } public void scrollToCenter() { if (autoScrollTimer != null) autoScrollTimer.cancel(); if (this.getWidgetCount() > 0) { int clientWidth = this.getElement().getClientWidth(); int clientHeight = this.getElement().getClientHeight(); int widgetWidth = this.getWidget(0).getElement().getClientWidth(); int widgetHeight = this.getWidget(0).getElement().getClientHeight(); scrollToPosition(widgetWidth / 2 - clientWidth / 2, widgetHeight / 2 - clientHeight / 2); } } public void stopAutoScroll() { if (autoScrollTimer != null) autoScrollTimer.cancel(); } @Override public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { return addDomHandler(handler, MouseDownEvent.getType()); } @Override public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) { return addDomHandler(handler, MouseUpEvent.getType()); } @Override public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) { return addDomHandler(handler, MouseOutEvent.getType()); } @Override public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) { return addDomHandler(handler, MouseOverEvent.getType()); } @Override public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) { return addDomHandler(handler, MouseMoveEvent.getType()); } @Override public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) { return addDomHandler(handler, MouseWheelEvent.getType()); } private void notifyScrollPositionChanged() { for (Iterator<ScrollListener> it = scrollListeners.iterator(); it.hasNext();) it.next().scrollPositionChanged(); } private void notifyAutoScrollFinished() { List<ScrollListener> toRemove = new ArrayList<ScrollListener>(); for (Iterator<ScrollListener> it = scrollListeners.iterator(); it.hasNext();) { ScrollListener l = it.next(); if (l.autoScrollingFinished()) toRemove.add(l); } for (int i = 0; i < toRemove.size(); i++) scrollListeners.remove(toRemove.get(i)); } public static interface ScrollListener { public void scrollPositionChanged(); /** * Called when a smooth auto scrolling has finished. * @return Returning true removes the listener. */ public boolean autoScrollingFinished(); } /** * Mouse handling extension for MouseScrollPanel * * @author Christian Clausner * */ public static interface MouseHandlerExtension { /** * Mouse wheel handling * @return <code>true</code> if the event has been handled and the MouseScrollPanel should not handle it further. */ boolean onMouseWheel(MouseWheelEvent event); /** * Mouse over handling * @return <code>true</code> if the event has been handled and the MouseScrollPanel should not handle it further. */ boolean onMouseOver(MouseOverEvent event); /** * Mouse out handling * @return <code>true</code> if the event has been handled and the MouseScrollPanel should not handle it further. */ boolean onMouseOut(MouseOutEvent event); /** * Mouse down handling * @return <code>true</code> if the event has been handled and the MouseScrollPanel should not handle it further. */ boolean onMouseDown(MouseDownEvent event); /** * Mouse up handling * @return <code>true</code> if the event has been handled and the MouseScrollPanel should not handle it further. */ boolean onMouseUp(MouseUpEvent event); /** * Mouse move handling * @return <code>true</code> if the event has been handled and the MouseScrollPanel should not handle it further. */ boolean onMouseMove(MouseMoveEvent event); void postMouseWheel(MouseWheelEvent event); void postMouseOver(MouseOverEvent event); void postMouseOut(MouseOutEvent event); void postMouseDown(MouseDownEvent event); void postMouseUp(MouseUpEvent event); void postMouseMove(MouseMoveEvent event); } }