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.page.tool.drawing; import org.primaresearch.maths.geometry.Point; import org.primaresearch.maths.geometry.Polygon; import org.primaresearch.web.gwt.client.page.PageLayoutC; import org.primaresearch.web.gwt.client.page.PageSyncManager; import org.primaresearch.web.gwt.client.ui.RenderStyles.RenderStyle; import org.primaresearch.web.gwt.client.ui.page.PageScrollView; import org.primaresearch.web.gwt.client.ui.page.SelectionManager; import org.primaresearch.web.gwt.client.ui.page.SelectionManager.SelectionListener; import org.primaresearch.web.gwt.client.ui.page.renderer.PageRenderer; import org.primaresearch.web.gwt.client.ui.page.renderer.PolygonRendererHelper; import org.primaresearch.web.gwt.client.ui.page.tool.controls.ContentObjectToolbarButton; import org.primaresearch.web.gwt.client.ui.page.tool.controls.ContentObjectToolbar; import org.primaresearch.web.gwt.shared.page.ContentObjectC; import com.google.gwt.canvas.dom.client.Context2d; import com.google.gwt.canvas.dom.client.CssColor; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; 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.MouseUpEvent; import com.google.gwt.user.client.ui.Image; /** * Tool for adding, moving and deleting polygon points for a page content object. * * @author Christian Clausner * */ public class EditOutlineTool extends BasePageViewTool implements SelectionListener { /** Defines how close the mouse cursor needs to be to 'catch' a polygon point. */ private static final int POINT_NEIGHBOURHOOD = 10; //Colour for add sign and move sign private static final CssColor POINT_HIGHLIGHT_LINE_COLOR = CssColor.make(0, 82, 117); private static final CssColor POINT_HIGHLIGHT_FILL_COLOR = CssColor.make(0, 162, 232); //Colour for delete sign private static final CssColor DELETE_POINT_LINE_COLOR = CssColor.make(128, 0, 0); private static final CssColor DELETE_POINT_FILL_COLOR = CssColor.make(255, 0, 0); private ContentObjectC contentObject; private Polygon polygon; private PageScrollView view; private Point currentPolygonPoint = null; private Point newPolygonPointCandidate = null; private int indexOfPointBeforeNewPolygonPoint; private Point referencePoint = null; private ContentObjectToolbarButton applyWidget; private ContentObjectToolbarButton cancelWidget; private ContentObjectToolbar toolbar; private Polygon originalPolygon; private Point mouseDownPoint = null; private SelectionManager selectionManager; private boolean deletePointsMode = false; /** * Constructor * * @param contentObject The page content object with the outline to edit * @param pageView The document page view * @param selectionManager Selection manager for adding a listener * @param syncManager Synchronisation manager for sending the changed outline to the server */ public EditOutlineTool(final ContentObjectC contentObject, final PageScrollView pageView, SelectionManager selectionManager, final PageSyncManager syncManager) { super(); polygon = contentObject.getCoords(); originalPolygon = polygon.clone(); this.view = pageView; this.contentObject = contentObject; this.selectionManager = selectionManager; //Hide currently active widgets on the page view pageView.hideHoverWidgets(); //Create a toolbar toolbar = new ContentObjectToolbar(selectionManager, -5); pageView.addHoverWidget(toolbar); //OK button applyWidget = new ContentObjectToolbarButton("icons/tick.png", "Done"); toolbar.add(applyWidget); applyWidget.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { pageView.removeHoverWidget(toolbar); notifyListenersToolFinished(true); syncManager.syncObjectOutline(contentObject); pageView.showHoverWidgets(); stopListeningForSelectionChanges(); toolbar.dispose(); view.getViewPanel().getElement().getStyle().setCursor(Cursor.AUTO); } }); //Cancel button cancelWidget = new ContentObjectToolbarButton("icons/cross.png", "Cancel"); toolbar.add(cancelWidget); cancelWidget.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { onCancel(true); } }); //Separator Image sep = new Image("icons/separator.png"); toolbar.add(sep); //Edit modes (add/move and erase) final ContentObjectToolbarButton addAndMoveMode = new ContentObjectToolbarButton("icons/pen.png", "Move and add points", true); toolbar.add(addAndMoveMode); addAndMoveMode.setDown(true); final ContentObjectToolbarButton deleteMode = new ContentObjectToolbarButton("icons/eraser.png", "Delete points", true); toolbar.add(deleteMode); addAndMoveMode.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { deletePointsMode = !deletePointsMode; deleteMode.setDown(deletePointsMode); chooseCursor(); } }); deleteMode.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { deletePointsMode = !deletePointsMode; addAndMoveMode.setDown(!deletePointsMode); chooseCursor(); } }); toolbar.refresh(); //Update toolbar position view.getRenderer().refresh(); selectionManager.addListener(this); } /** * Sets the cursor for the page view according to the current mode. */ private void chooseCursor() { if (deletePointsMode) view.getViewPanel().getElement().getStyle().setCursor(Cursor.POINTER); else view.getViewPanel().getElement().getStyle().setCursor(Cursor.AUTO); } private void stopListeningForSelectionChanges() { selectionManager.removeListener(this); } private void onCancel(boolean unhideOtherToolWidgets) { contentObject.setCoords(originalPolygon); view.removeHoverWidget(toolbar); notifyListenersToolFinished(false); if (unhideOtherToolWidgets) view.showHoverWidgets(); stopListeningForSelectionChanges(); toolbar.dispose(); view.getViewPanel().getElement().getStyle().setCursor(Cursor.AUTO); } @Override public boolean onMouseMove(MouseMoveEvent event) { //Mouse down? if (mouseDownPoint != null) { //Delete mode -> 'Hoover up' the polygon points if (deletePointsMode) { int x = view.clientToDocumentCoordsX(event.getRelativeX(view.asWidget().getElement())); int y = view.clientToDocumentCoordsY(event.getRelativeY(view.asWidget().getElement())); //Look for polygon point that is close by currentPolygonPoint = findNearestPolygonPointCloseby(x, y); if (currentPolygonPoint != null) { deleteCurrentPolygonPoint(); view.getRenderer().refresh(); refreshToolbar(); } } //Add and move mode -> Move polygon point else { if (currentPolygonPoint != null) { int diffX = (int) ((double) (event.getX() - mouseDownPoint.x) / view.getZoomFactor()); int diffY = (int) ((double) (event.getY() - mouseDownPoint.y) / view.getZoomFactor()); currentPolygonPoint.x = referencePoint.x + diffX; currentPolygonPoint.y = referencePoint.y + diffY; confinePointToDocument(currentPolygonPoint); polygon.setBoundingBoxOutdated(); refreshToolbar(); //Update icon position } } view.getRenderer().refresh(); return true; //Forbid scrolling } //Mouse hover -> Find nearby point else { int x = view.clientToDocumentCoordsX(event.getRelativeX(view.asWidget().getElement())); int y = view.clientToDocumentCoordsY(event.getRelativeY(view.asWidget().getElement())); //First look for polygon point that is close by Point oldPoint = currentPolygonPoint; currentPolygonPoint = findNearestPolygonPointCloseby(x, y); if (oldPoint != currentPolygonPoint) view.getRenderer().refresh(); //Add and move mode if (!deletePointsMode) { //Now look if we are close to a polygon line (to add a new point) if (currentPolygonPoint == null) { newPolygonPointCandidate = findNearestPointOnLines(x, y); view.getRenderer().refresh(); } else if (newPolygonPointCandidate != null) { newPolygonPointCandidate = null; view.getRenderer().refresh(); } } } return false; //Allow scrolling } @Override public boolean onMouseOut(MouseOutEvent event) { return false; } @Override public boolean onMouseUp(MouseUpEvent event) { if (currentPolygonPoint != null) { referencePoint = null; } mouseDownPoint = null; return true; } @Override public boolean onMouseDown(MouseDownEvent event) { //Delete points mode if (deletePointsMode) { if (currentPolygonPoint != null) { mouseDownPoint = new Point(event.getX(), event.getY()); deleteCurrentPolygonPoint(); return true; //Forbid scrolling } } //Add or move points mode else { //Nearby existing point? if (currentPolygonPoint != null) { mouseDownPoint = new Point(event.getX(), event.getY()); referencePoint = new Point(currentPolygonPoint.x, currentPolygonPoint.y); return true; //Forbid scrolling } //have a candidate for new point? else if (newPolygonPointCandidate != null) { mouseDownPoint = new Point(event.getX(), event.getY()); polygon.insertPoint(indexOfPointBeforeNewPolygonPoint, newPolygonPointCandidate); currentPolygonPoint = newPolygonPointCandidate; referencePoint = new Point(currentPolygonPoint.x, currentPolygonPoint.y); newPolygonPointCandidate = null; view.getRenderer().refresh(); return true; //Forbid scrolling } } return false; //Allow scrolling } /** * Deletes the currently highlighted point from the outline (if enough points left). */ private void deleteCurrentPolygonPoint() { if (polygon.getSize() > 3) { polygon.removePoint(currentPolygonPoint); currentPolygonPoint = null; view.getRenderer().refresh(); } } /** * Makes sure the given point is within the limits of the document page. */ private void confinePointToDocument(Point p) { PageLayoutC layout = view.getPageLayout(); if (p.x < 0) p.x = 0; else if (p.x >= layout.getWidth()) p.x = layout.getWidth() - 1; if (p.y < 0) p.y = 0; else if (p.y >= layout.getHeight()) p.y = layout.getHeight() - 1; } @Override public void render(PageRenderer renderer) { if (!isEnabled()) return; Context2d context = renderer.getContext(); //Draw the selected outline in red RenderStyle style = new RenderStyle("rgb(255,0,0)", "transparent", 1.0); PolygonRendererHelper.drawPolygon(context, polygon, style, renderer.getZoomFactor(), true, false); //Delete mode if (deletePointsMode) { if (currentPolygonPoint != null) { context.setFillStyle(DELETE_POINT_FILL_COLOR); context.setStrokeStyle(DELETE_POINT_LINE_COLOR); context.setLineWidth(1.0 / renderer.getZoomFactor()); //Cross context.beginPath(); int size = (int) (3.0 / view.getZoomFactor()); context.moveTo(currentPolygonPoint.x - 2 * size, currentPolygonPoint.y - 3 * size); context.lineTo(currentPolygonPoint.x, currentPolygonPoint.y - size); context.lineTo(currentPolygonPoint.x + 2 * size, currentPolygonPoint.y - 3 * size); context.lineTo(currentPolygonPoint.x + 3 * size, currentPolygonPoint.y - 2 * size); context.lineTo(currentPolygonPoint.x + size, currentPolygonPoint.y); context.lineTo(currentPolygonPoint.x + 3 * size, currentPolygonPoint.y + 2 * size); context.lineTo(currentPolygonPoint.x + 2 * size, currentPolygonPoint.y + 3 * size); context.lineTo(currentPolygonPoint.x, currentPolygonPoint.y + size); context.lineTo(currentPolygonPoint.x - 2 * size, currentPolygonPoint.y + 3 * size); context.lineTo(currentPolygonPoint.x - 3 * size, currentPolygonPoint.y + 2 * size); context.lineTo(currentPolygonPoint.x - size, currentPolygonPoint.y); context.lineTo(currentPolygonPoint.x - 3 * size, currentPolygonPoint.y - 2 * size); context.lineTo(currentPolygonPoint.x - 2 * size, currentPolygonPoint.y - 3 * size); context.fill(); context.stroke(); } } //Add or move mode else { //Move point if (currentPolygonPoint != null) { context.setFillStyle(POINT_HIGHLIGHT_FILL_COLOR); context.setStrokeStyle(POINT_HIGHLIGHT_LINE_COLOR); context.setLineWidth(1.0 / renderer.getZoomFactor()); int size = (int) (3.0 / view.getZoomFactor()); //Rect in centre context.beginPath(); context.rect(currentPolygonPoint.x - size, currentPolygonPoint.y - size, 2 * size + 1, 2 * size + 1); context.fill(); context.stroke(); //Arrows // Left context.beginPath(); context.moveTo(currentPolygonPoint.x - 2 * size, currentPolygonPoint.y + size); context.lineTo(currentPolygonPoint.x - 2 * size, currentPolygonPoint.y - size); context.lineTo(currentPolygonPoint.x - 3 * size, currentPolygonPoint.y); context.lineTo(currentPolygonPoint.x - 2 * size, currentPolygonPoint.y + size); context.fill(); context.stroke(); // Right context.beginPath(); context.moveTo(currentPolygonPoint.x + 2 * size, currentPolygonPoint.y + size); context.lineTo(currentPolygonPoint.x + 2 * size, currentPolygonPoint.y - size); context.lineTo(currentPolygonPoint.x + 3 * size, currentPolygonPoint.y); context.lineTo(currentPolygonPoint.x + 2 * size, currentPolygonPoint.y + size); context.fill(); context.stroke(); // Top context.beginPath(); context.moveTo(currentPolygonPoint.x + size, currentPolygonPoint.y - 2 * size); context.lineTo(currentPolygonPoint.x - size, currentPolygonPoint.y - 2 * size); context.lineTo(currentPolygonPoint.x, currentPolygonPoint.y - 3 * size); context.lineTo(currentPolygonPoint.x + size, currentPolygonPoint.y - 2 * size); context.fill(); context.stroke(); // Bottom context.beginPath(); context.moveTo(currentPolygonPoint.x + size, currentPolygonPoint.y + 2 * size); context.lineTo(currentPolygonPoint.x - size, currentPolygonPoint.y + 2 * size); context.lineTo(currentPolygonPoint.x, currentPolygonPoint.y + 3 * size); context.lineTo(currentPolygonPoint.x + size, currentPolygonPoint.y + 2 * size); context.fill(); context.stroke(); } //Add point if (newPolygonPointCandidate != null) { context.setFillStyle(POINT_HIGHLIGHT_FILL_COLOR); context.setStrokeStyle(POINT_HIGHLIGHT_LINE_COLOR); context.setLineWidth(1.0 / renderer.getZoomFactor()); //Plus sign context.beginPath(); int size = (int) (3.0 / view.getZoomFactor()); context.moveTo(newPolygonPointCandidate.x - size, newPolygonPointCandidate.y - 3 * size); context.lineTo(newPolygonPointCandidate.x + size, newPolygonPointCandidate.y - 3 * size); context.lineTo(newPolygonPointCandidate.x + size, newPolygonPointCandidate.y - size); context.lineTo(newPolygonPointCandidate.x + 3 * size, newPolygonPointCandidate.y - size); context.lineTo(newPolygonPointCandidate.x + 3 * size, newPolygonPointCandidate.y + size); context.lineTo(newPolygonPointCandidate.x + size, newPolygonPointCandidate.y + size); context.lineTo(newPolygonPointCandidate.x + size, newPolygonPointCandidate.y + 3 * size); context.lineTo(newPolygonPointCandidate.x - size, newPolygonPointCandidate.y + 3 * size); context.lineTo(newPolygonPointCandidate.x - size, newPolygonPointCandidate.y + size); context.lineTo(newPolygonPointCandidate.x - 3 * size, newPolygonPointCandidate.y + size); context.lineTo(newPolygonPointCandidate.x - 3 * size, newPolygonPointCandidate.y - size); context.lineTo(newPolygonPointCandidate.x - size, newPolygonPointCandidate.y - size); context.lineTo(newPolygonPointCandidate.x - size, newPolygonPointCandidate.y - 3 * size); context.fill(); context.stroke(); } } } /** * Returns the closest existing polygon point that is in the neighbourhood. * @param x Centre of neighbourhood * @param y Centre of neighbourhood * @return A point or <code>null</code> */ private Point findNearestPolygonPointCloseby(int x, int y) { double maxDist = POINT_NEIGHBOURHOOD / view.getZoomFactor(); double minDist = 100000.0, dist; int nearestPointIndex = -1; for (int i = 0; i < polygon.getSize(); i++) { dist = polygon.getPoint(i).calculateDistance(x, y); if (dist <= minDist && dist < maxDist) { minDist = dist; nearestPointIndex = i; } } if (nearestPointIndex >= 0) { return polygon.getPoint(nearestPointIndex); } return null; } /** * Returns the closest point on a polygon line that is in the neighbourhood. * @param x Centre of neighbourhood * @param y Centre of neighbourhood * @return A point or <code>null</code> */ private Point findNearestPointOnLines(int x, int y) { double maxDist = POINT_NEIGHBOURHOOD / view.getZoomFactor(); double minDist = 100000.0, dist; Point p; Point nearest = null; Point l1, l2; for (int i = 0; i < polygon.getSize(); i++) { l1 = polygon.getPoint(i); l2 = i < polygon.getSize() - 1 ? polygon.getPoint(i + 1) : polygon.getPoint(0); p = new Point(); dist = new Point(x, y).calculateDistance(l1.x, l1.y, l2.x, l2.y, p); if (dist <= minDist && dist < maxDist) { minDist = dist; nearest = p; indexOfPointBeforeNewPolygonPoint = i; } } return nearest; } /** Updates the toolbar position */ private void refreshToolbar() { toolbar.refresh(); } @Override public void selectionChanged(SelectionManager manager) { onCancel(false); } @Override public void cancel() { onCancel(false); } }