<commit_before>/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2013 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.puregwt.client.controller;

import org.geomajas.annotation.Api;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.gwt.client.map.RenderSpace;
import org.geomajas.puregwt.client.map.MapPresenter;
import org.geomajas.puregwt.client.map.ViewPort;

import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.HumanInputEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseWheelEvent;

/**
 * Generic navigation map controller. This controller allows for panning and zooming on the map in many different ways.
 * Options are the following:
 * <ul>
 * <li><b>Panning</b>: Drag the map to pan.</li>
 * <li><b>Double click</b>: Double clicking on some location will see the map zoom in to that location.</li>
 * <li><b>Zoom to rectangle</b>: By holding shift or ctrl and dragging at the same time, a rectangle will appear on the
 * map. On mouse up, the map will than zoom to that rectangle.</li>
 * <li></li>
 * </ul>
 * For zooming using the mouse wheel there are 2 options, defined by the {@link ScrollZoomType} enum. These options are
 * the following:
 * <ul>
 * <li><b>ScrollZoomType.ZOOM_CENTER</b>: Zoom in/out so that the current center of the map remains.</li>
 * <li><b>ScrollZoomType.ZOOM_POSITION</b>: Zoom in/out so that the mouse world position remains the same. Can be great
 * for many subsequent double clicks, to make sure you keep zooming to the same location (wherever the mouse points to).
 * </li>
 * </ul>
 * 
 * @author <NAME>
 * @since 1.0.0
 */
@Api(allMethods = true)
public class NavigationController extends AbstractMapController {

	/** Zooming types on mouse wheel scroll. */
	public static enum ScrollZoomType {
		/** When scroll zooming, retain the center of the map position. */
		ZOOM_CENTER,

		/** When scroll zooming, retain the mouse position. */
		ZOOM_POSITION
	}

	private final ZoomToRectangleController zoomToRectangleController;

	protected Coordinate dragOrigin;

	protected Coordinate lastClickPosition;

	protected boolean zooming;

	protected boolean dragging;

	private ScrollZoomType scrollZoomType = ScrollZoomType.ZOOM_POSITION;

	// ------------------------------------------------------------------------
	// Constructors:
	// ------------------------------------------------------------------------

	/**
	 * Create a controller.
	 */
	public NavigationController() {
		super(false);
		zoomToRectangleController = new ZoomToRectangleController();
	}

	// ------------------------------------------------------------------------
	// MapController implementation:
	// ------------------------------------------------------------------------

	/** {@inheritDoc} */
	public void onActivate(MapPresenter mapPresenter) {
		super.onActivate(mapPresenter);
		zoomToRectangleController.onActivate(mapPresenter);
	}

	/** {@inheritDoc} */
	public void onMouseDown(MouseDownEvent event) {
		super.onMouseDown(event);
		if (event.isControlKeyDown() || event.isShiftKeyDown()) {
			// Trigger the dragging on the zoomToRectangleController:
			zoomToRectangleController.onMouseDown(event);
		}
	}

	/** {@inheritDoc} */
	public void onDown(HumanInputEvent<?> event) {
		if (event.isControlKeyDown() || event.isShiftKeyDown()) {
			zooming = true;
		} else if (!isRightMouseButton(event)) {
			dragging = true;
			dragOrigin = getLocation(event, RenderSpace.SCREEN);
			mapPresenter.setCursor("move");
		}
		lastClickPosition = getLocation(event, RenderSpace.WORLD);
	}

	/** {@inheritDoc} */
	public void onUp(HumanInputEvent<?> event) {
		if (zooming) {
			zoomToRectangleController.onUp(event);
			zooming = false;
		} else if (dragging) {
			stopPanning(event);
		}
	}

	/** {@inheritDoc} */
	public void onMouseMove(MouseMoveEvent event) {
		if (zooming) {
			zoomToRectangleController.onMouseMove(event);
		} else if (dragging) {
			// updateView(event);
			super.onMouseMove(event);
		}
	}

	/** {@inheritDoc} */
	public void onDrag(HumanInputEvent<?> event) {
		updateView(event);
	}

	/** {@inheritDoc} */
	public void onMouseOut(MouseOutEvent event) {
		if (zooming) {
			zoomToRectangleController.onMouseOut(event);
		} else {
			stopPanning(null);
		}
	}

	/** {@inheritDoc} */
	public void onDoubleClick(DoubleClickEvent event) {
		// Zoom in on the event location:
		Bbox bounds = mapPresenter.getViewPort().getBounds();
		double x = lastClickPosition.getX() - (bounds.getWidth() / 4);
		double y = lastClickPosition.getY() - (bounds.getHeight() / 4);
		Bbox newBounds = new Bbox(x, y, bounds.getWidth() / 2, bounds.getHeight() / 2);
		mapPresenter.getViewPort().applyBounds(newBounds);
	}

	/** {@inheritDoc} */
	public void onMouseWheel(MouseWheelEvent event) {
		ViewPort viewPort = mapPresenter.getViewPort();
		int index = viewPort.getZoomStrategy().getZoomStepIndex(viewPort.getScale());
		if (event.isNorth()) {
			if (index > 0) {
				if (scrollZoomType == ScrollZoomType.ZOOM_POSITION) {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index - 1), viewPort.transform(
							new Coordinate(event.getX(), event.getY()), RenderSpace.SCREEN, RenderSpace.WORLD));
				} else {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index - 1));
				}
			}
		} else {
			if (index < viewPort.getZoomStrategy().getZoomStepCount() - 1) {
				if (scrollZoomType == ScrollZoomType.ZOOM_POSITION) {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index + 1), viewPort.transform(
							new Coordinate(event.getX(), event.getY()), RenderSpace.SCREEN, RenderSpace.WORLD));
				} else {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index + 1));
				}
			}
		}
	}<commit_msg>PURE-147: fix for bad scroll behaviour on chrome/OSX PURE-148: using relative event location for mouse wheel event<commit_after>/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2013 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.puregwt.client.controller;

import org.geomajas.annotation.Api;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.gwt.client.map.RenderSpace;
import org.geomajas.puregwt.client.map.MapPresenter;
import org.geomajas.puregwt.client.map.ViewPort;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.HumanInputEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseWheelEvent;

/**
 * Generic navigation map controller. This controller allows for panning and zooming on the map in many different ways.
 * Options are the following:
 * <ul>
 * <li><b>Panning</b>: Drag the map to pan.</li>
 * <li><b>Double click</b>: Double clicking on some location will see the map zoom in to that location.</li>
 * <li><b>Zoom to rectangle</b>: By holding shift or ctrl and dragging at the same time, a rectangle will appear on the
 * map. On mouse up, the map will than zoom to that rectangle.</li>
 * <li></li>
 * </ul>
 * For zooming using the mouse wheel there are 2 options, defined by the {@link ScrollZoomType} enum. These options are
 * the following:
 * <ul>
 * <li><b>ScrollZoomType.ZOOM_CENTER</b>: Zoom in/out so that the current center of the map remains.</li>
 * <li><b>ScrollZoomType.ZOOM_POSITION</b>: Zoom in/out so that the mouse world position remains the same. Can be great
 * for many subsequent double clicks, to make sure you keep zooming to the same location (wherever the mouse points to).
 * </li>
 * </ul>
 * 
 * @author <NAME>
 * @since 1.0.0
 */
@Api(allMethods = true)
public class NavigationController extends AbstractMapController {

	/** Zooming types on mouse wheel scroll. */
	public static enum ScrollZoomType {
		/** When scroll zooming, retain the center of the map position. */
		ZOOM_CENTER,

		/** When scroll zooming, retain the mouse position. */
		ZOOM_POSITION
	}

	private final ZoomToRectangleController zoomToRectangleController;

	protected Coordinate dragOrigin;

	protected Coordinate lastClickPosition;

	protected boolean zooming;

	protected boolean dragging;

	private ScrollZoomType scrollZoomType = ScrollZoomType.ZOOM_POSITION;

	// ------------------------------------------------------------------------
	// Constructors:
	// ------------------------------------------------------------------------

	/**
	 * Create a controller.
	 */
	public NavigationController() {
		super(false);
		zoomToRectangleController = new ZoomToRectangleController();
	}

	// ------------------------------------------------------------------------
	// MapController implementation:
	// ------------------------------------------------------------------------

	/** {@inheritDoc} */
	public void onActivate(MapPresenter mapPresenter) {
		super.onActivate(mapPresenter);
		zoomToRectangleController.onActivate(mapPresenter);
	}

	/** {@inheritDoc} */
	public void onMouseDown(MouseDownEvent event) {
		super.onMouseDown(event);
		if (event.isControlKeyDown() || event.isShiftKeyDown()) {
			// Trigger the dragging on the zoomToRectangleController:
			zoomToRectangleController.onMouseDown(event);
		}
	}

	/** {@inheritDoc} */
	public void onDown(HumanInputEvent<?> event) {
		if (event.isControlKeyDown() || event.isShiftKeyDown()) {
			zooming = true;
		} else if (!isRightMouseButton(event)) {
			dragging = true;
			dragOrigin = getLocation(event, RenderSpace.SCREEN);
			mapPresenter.setCursor("move");
		}
		lastClickPosition = getLocation(event, RenderSpace.WORLD);
	}

	/** {@inheritDoc} */
	public void onUp(HumanInputEvent<?> event) {
		if (zooming) {
			zoomToRectangleController.onUp(event);
			zooming = false;
		} else if (dragging) {
			stopPanning(event);
		}
	}

	/** {@inheritDoc} */
	public void onMouseMove(MouseMoveEvent event) {
		if (zooming) {
			zoomToRectangleController.onMouseMove(event);
		} else if (dragging) {
			// updateView(event);
			super.onMouseMove(event);
		}
	}

	/** {@inheritDoc} */
	public void onDrag(HumanInputEvent<?> event) {
		updateView(event);
	}

	/** {@inheritDoc} */
	public void onMouseOut(MouseOutEvent event) {
		if (zooming) {
			zoomToRectangleController.onMouseOut(event);
		} else {
			stopPanning(null);
		}
	}

	/** {@inheritDoc} */
	public void onDoubleClick(DoubleClickEvent event) {
		// Zoom in on the event location:
		Bbox bounds = mapPresenter.getViewPort().getBounds();
		double x = lastClickPosition.getX() - (bounds.getWidth() / 4);
		double y = lastClickPosition.getY() - (bounds.getHeight() / 4);
		Bbox newBounds = new Bbox(x, y, bounds.getWidth() / 2, bounds.getHeight() / 2);
		mapPresenter.getViewPort().applyBounds(newBounds);
	}

	/** {@inheritDoc} */
	@Override
	public void onMouseWheel(MouseWheelEvent event) {
		final boolean isNorth;
		if (event.getDeltaY() == 0) {
			isNorth = (getWheelDelta(event.getNativeEvent()) < 0);
		} else {
			isNorth = event.isNorth();
		}
		Coordinate location = getLocation(event, RenderSpace.WORLD);
		scrollZoomTo(isNorth, location);
	}

	protected native int getWheelDelta(NativeEvent evt) /*-{
		return Math.round(-evt.wheelDelta) || 0;
	}-*/;

	protected void scrollZoomTo(boolean isNorth, Coordinate location) {
		ViewPort viewPort = mapPresenter.getViewPort();
		int index = viewPort.getZoomStrategy().getZoomStepIndex(viewPort.getScale());
		if (isNorth) {
			if (index > 0) {
				if (scrollZoomType == ScrollZoomType.ZOOM_POSITION) {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index - 1), location);
				} else {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index - 1));
				}
			}
		} else {
			if (index < viewPort.getZoomStrategy().getZoomStepCount() - 1) {
				if (scrollZoomType == ScrollZoomType.ZOOM_POSITION) {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index + 1), location);
				} else {
					viewPort.applyScale(viewPort.getZoomStrategy().getZoomStepScale(index + 1));
				}
			}
		}
	}
{'fraction_non_alphanumeric': 0.16250770178681453, 'fraction_numerical': 0.004081947011706716, 'mean_word_length': 3.4061757719714962, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2503966', 'n_tokens_mistral': 4314, 'n_tokens_neox': 3916, 'n_words': 987}