org.primaresearch.web.gwt.client.ui.page.tool.drawing.EditOutlineTool.java Source code

Java tutorial

Introduction

Here is the source code for org.primaresearch.web.gwt.client.ui.page.tool.drawing.EditOutlineTool.java

Source

/*
 * 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);
    }

}