com.google.code.gwt.crop.client.GWTCropper.java Source code

Java tutorial

Introduction

Here is the source code for com.google.code.gwt.crop.client.GWTCropper.java

Source

/**
 * 
 * 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 com.google.code.gwt.crop.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.uibinder.client.UiConstructor;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.*;

/**
 * 
 * <h1>GWT Cropper</h1>
 * <p><b>GWT Cropper</b> - widget that allows you to select an area of a picture and get the coordinates of this selection. It is useful, if you want to crop a picture.</p>
 * 
 * <h1>Example</h1>
 * <p>Usage example:
 * <pre>
 * final GWTCropper crop = new GWTCropper("url/to/your/uncropped/image.jpg");
 * crop.setAspectRatio(1); // square selection (optional)
 * panel.add(crop);
 * </pre>
 * <br />
 * Or, from UI XML document:
 * <pre>
 *     <ui:UiBinder ...
 *     xmlns:my="urn:import:com.google.code.gwt.crop.client">
 *
 *     <g:HTMLPanel>
 *         <my:GWTCropper imageURL="my-image-url.jpg" aspectRatio='1.5' />
 *     </g:HTMLPanel>
 * </pre>
 * </p>
 *
 * 
 * @author ilja.hamalainen@gmail.com (Ilja Hmlinen)
 * 
 * @see <a href="http://code.google.com/p/gwt-cropper/">GWT Cropper home page</a>
 * 
 */
public class GWTCropper extends HTMLPanel
        implements MouseMoveHandler, MouseUpHandler, MouseOutHandler, TouchMoveHandler, TouchEndHandler {

    private final ICropperStyleSource bundleResources = GWT.create(ICropperStyleSource.class);

    // canvas sizes
    private int nOuterWidth = -1;
    private int nOuterHeight = -1;

    // selection coordinates
    private int nInnerX = -1;
    private int nInnerY = -1;
    private int nInnerWidth = -1;
    private int nInnerHeight = -1;

    private boolean isDown = false;
    private byte action = Constants.DRAG_NONE;

    // initials to provide crop actions
    private int initW = -1;
    private int initH = -1;

    // X and Y coordinates of cursor before dragging
    private int initX = -1;
    private int initY = -1;

    private int offsetX = -1;
    private int offsetY = -1;

    // instances to canvas and selection area, available for the cropper
    private final AbsolutePanelImpl _container;
    private AbsolutePanel handlesContainer;
    private HTML draggableBackground;
    private LoadHandler onCanvasLoadHandler;

    // settings
    private double aspectRatio = 0;

    // minimum size of height or width. Just to prevent selection area to be shrunk to a dot
    private final int HANDLE_SIZE = this.bundleResources.css().handleSize();
    private final int SELECTION_BORDER_SIZE = this.bundleResources.css().borderSize();
    private int MIN_WIDTH = this.HANDLE_SIZE;
    private int MIN_HEIGHT = this.HANDLE_SIZE;

    private IGWTCropperPreview previewWidget;

    private AbsolutePanelImpl selectionContainer = new AbsolutePanelImpl();

    // used by UIBuinder
    private final String imageURL;

    /**
     * Constructor with mandatory parameter of image's URL.
     *
     * @param imageURL - URL of an uncropped image
     */
    @UiConstructor
    public GWTCropper(String imageURL) {
        super("");
        this.imageURL = imageURL;

        bundleResources.css().ensureInjected();
        this._container = new AbsolutePanelImpl();
        this.addCanvas(imageURL);

        addDomHandler(this, MouseMoveEvent.getType());
        addDomHandler(this, MouseUpEvent.getType());
        addDomHandler(this, MouseOutEvent.getType());

        addDomHandler(this, TouchMoveEvent.getType());
        addDomHandler(this, TouchEndEvent.getType());
    }

    /**
     * <p></p>Used by UiBinder to instantiate GWTCropper</p>
     */
    @UiFactory
    GWTCropper createCropperInstanceFromUiBuilder() {
        return new GWTCropper(this.imageURL);
    }

    // ---------- Public API ------------------

    /**
     * <p>Sets the fixed aspect ratio (proportion of width to height) for the selection. 
     * User is able to change the size of a selected area, but the proportion of its 
     * dimension will be kept unchanged.</p>
     * 
     * <p> Examples:
     * <ul>
     * <li><b>Default</b> is 0 that means the selection can have any shape.</li>
     * 
     * <li>Ratio is 1/1=1 that means the selection has a square shape.<br />
     * <img width='185' height='130' src='doc-files/square.jpeg'/></li>
     * 
     * <li>Ratio 2/1=2 the selection has a rectangular shape where width is twice as longer as height<br />
     * <img width='184' height='130' src='doc-files/rec21.jpeg'/></li>
     * 
     * <li>Ratio 1/2=0.5 the selection has a rectangular shape where height is twice as higher as width<br />
     * <img width='188' height='131' src='doc-files/rec12.jpeg'/></li>
     * </ul>
     * </p>
     * 
     * <p><i>Usage example:</i> You can declare a side proportion in this way: <br/>
     * <pre> cropper.setAspectRatio( (double) 1/2); </pre>
     * </p>
     * 
     * @param aspectRatio - double value, proportion width/height
     */
    public void setAspectRatio(double aspectRatio) {
        this.aspectRatio = aspectRatio;
    }

    public double getAspectRatio() {
        return this.aspectRatio;
    }

    /**
     * <p>Get the X coordinate of the selection top left corner</p>
     * 
     * <p>
     * <img width='204' height='151' src='doc-files/selection-x-coordinate.jpeg'/>
     * </p>
     * 
     * @return X coordinate
     */
    public int getSelectionXCoordinate() {
        return (this.nInnerX + this.SELECTION_BORDER_SIZE);
    }

    /**
     * <p>Get the Y coordinate of the selection top left corner</p>
     * 
     * <p>
     * <img width='211' height='150' src='doc-files/selection-y-coordinate.jpeg'/>
     * </p>
     * 
     * @return Y coordinate
     */
    public int getSelectionYCoordinate() {
        return (this.nInnerY + this.SELECTION_BORDER_SIZE);
    }

    /**
     * <p>Get the width of the selection area</p>
     * 
     * <p>
     * <img width='192' height='133' src='doc-files/selection_width.jpeg'/>
     * </p>
     * 
     * @return width in pixels
     */
    public int getSelectionWidth() {
        return this.nInnerWidth;
    }

    /**
     * <p>Get the height of the selection area</p>
     * 
     * <p>
     * <img width='208' height='150' src='doc-files/selection_height.jpeg'/>
     * </p>
     * @return height in pixels
     */
    public int getSelectionHeight() {
        return this.nInnerHeight;
    }

    /**
     * <p>Get the canvas height (original image you wish to crop)</p>
     * 
     * <p>
     * <img width='252' height='175' src='doc-files/canvas_height.jpeg'/>
     * </p>
     * 
     * @return height in PX
     */
    public int getCanvasHeight() {
        return this.nOuterHeight;
    }

    /**
     * <p>Get the canvas width (original image you wish to crop)</p>
     * 
     * <p>
     * <img width='189' height='171' src='doc-files/canvas_width.jpeg'/>
     * </p>
     * 
     * @return width in PX
     */
    public int getCanvasWidth() {
        return this.nOuterWidth;
    }

    /**
     * Shortcut for the {@link com.google.code.gwt.crop.client.GWTCropper#setInitialSelection(int, int, int, int, boolean) setInitialSelection(x, y, width, height, boolean)}
     * method. This method sets aspect ratio 0, that means the selection may have any shape.
     * 
     * @param x initial X coordinate. Will be ignored if it is out of a canvas.
     * @param y initial Y coordinate. Will be ignored if it is out of a canvas.
     * @param width initial selection width in pixels (will be ignored, if bigger, than canvas width)
     * @param height initial selection height in pixels (will be ignored if higher, than canvas height) 
     */
    public void setInitialSelection(int x, int y, int width, int height) {
        this.setInitialSelection(x, y, width, height, false);
    }

    /**
     * <p>Sets the initial size and position for the selected area.</p>
     * 
     * <p><i>Note, that all the incoming data will be validated. Thus, these requirements *must* be fulfilled:</i><br/><br/>
     * <code>
     *       canvas width > (initial selection X + initial selection width) <br />
     *       canvas height > (initial selection Y + initial selection height)
     * </code>
     * <br /><br/>
     * Otherwise default values will be used.
     * </p>
     * 
     * @param x initial X coordinate. Will be ignored if it is out of a canvas.
     * @param y initial Y coordinate. Will be ignored if it is out of a canvas.
     * @param width initial selection width in pixels (will be ignored, if bigger, than canvas width)
     * @param height initial selection height in pixels (will be ignored if higher, than canvas height) 
     * @param shouldKeepAspectRatio if <code>true</code>, then initial aspect ratio will be used for the selection and it will keep it's shape;
     * if <code>false</code> then the selection could have any shape.
     */
    public void setInitialSelection(int x, int y, int width, int height, boolean shouldKeepAspectRatio) {

        if (shouldKeepAspectRatio)
            this.setAspectRatio(width / height);

        if (width > MIN_WIDTH)
            this.nInnerWidth = width;

        if (height > MIN_HEIGHT)
            this.nInnerHeight = height;

        if (x >= 0)
            this.nInnerX = x;

        if (y >= 0)
            this.nInnerY = y;
    }

    /**
     * <p>Sets the minimal width for the selection (default value is 30px). <br />
     * <i>Will be ignored if the value is greater than the initial selection width</i></p>
     * 
     * @param width in pixels
     */
    public void setMinimalWidth(int width) {
        if (width > 30)
            this.MIN_WIDTH = width;
    }

    /**
     * <p>Sets the minimal height for the selection (default value is 30px). <br />
     * <i>Will be ignored if the value is greater than the initial selection height.</i></p>
     * @param height in pixels
     */
    public void setMinimalHeight(int height) {
        if (height > 30)
            this.MIN_HEIGHT = height;
    }

    /**
     * Adds the {@link LoadEvent} handler to canvas. This event will be fired, when the canvas
     * image is loaded and its dimensions are available.
     * 
     * @param handler
     */
    public void addCanvasLoadHandler(LoadHandler handler) {
        this.onCanvasLoadHandler = handler;
    }

    /**
     * Sets the cropper's size. 
     * 
     * @param width integer in px
     * @param height integer in px
     */
    public void setSize(int width, int height) {

        // size of parent panel, that holds this widget
        super.setSize(width + "px", height + "px");

        this.nOuterWidth = width;
        this.nOuterHeight = height;
    };

    /**
     * <i><b>Deprecated.</b> This method sets the size only for parent element, but not for the whole widget.
     * Use method {@link com.google.code.gwt.crop.client.GWTCropper#setSize(int, int) setSize(int width, int height)} instead.</i>
     * 
     * <p />
     * 
     * {@inheritDoc}
     * 
     * @see com.google.gwt.user.client.ui.UIObject#setSize(java.lang.String, java.lang.String)
     */
    @Override
    @Deprecated
    public void setSize(String width, String height) {
        super.setSize(width, height);
    };

    /**
     * Registers the {@link com.google.code.gwt.crop.client.GWTCropperPreview} widget.
     * 
     * @param previewWidget
     */
    public void registerPreviewWidget(IGWTCropperPreview previewWidget) {
        this.previewWidget = previewWidget;
    };

    // --------- private methods ------------

    /**
     * Adds a canvas with background image.
     * 
     * @param src - image URL
     */
    private void addCanvas(final String src) {

        super.setStyleName(bundleResources.css().base());

        final Image image = new Image(src);
        image.setStyleName(bundleResources.css().imageCanvas());
        image.addLoadHandler(new LoadHandler() {

            public void onLoad(LoadEvent event) {

                //this is a bug in IE since v.8 - maxWidth collapse image 
                //and you cannot read its width - in some cases depends from CSS image extensions
                image.getElement().getStyle().setProperty("maxWidth", "none");

                // get original image size
                if (nOuterWidth == -1)
                    nOuterWidth = image.getWidth();
                if (nOuterHeight == -1)
                    nOuterHeight = image.getHeight();

                DOM.setElementProperty(image.getElement(), "width", nOuterWidth + "");
                DOM.setElementProperty(image.getElement(), "height", nOuterHeight + "");
                image.getElement().getStyle().setPropertyPx("maxWidth", nOuterWidth);
                image.getElement().getStyle().setPropertyPx("maxHeight", nOuterHeight);

                _container.setWidth(nOuterWidth + "px");
                _container.setHeight(nOuterHeight + "px");
                addSelection(src);

                setSize(nOuterWidth, nOuterHeight);

                if (null != onCanvasLoadHandler)
                    onCanvasLoadHandler.onLoad(event);

                if (null != previewWidget) {
                    previewWidget.init(src, nOuterWidth, nOuterHeight, aspectRatio);
                }

                updatePreviewWidget();
            }

        });
        this._container.add(image, 0, 0);
        this.add(this._container);
    }

    /**
     * Adds initial selection
     * 
     */
    private void addSelection(final String src) {

        selectionContainer.addStyleName(this.bundleResources.css().selection());

        this.validateInitialData();

        selectionContainer.setWidth(this.nInnerWidth + "px");
        selectionContainer.setHeight(this.nInnerHeight + "px");

        // add background image for the selection
        Image imgSelectionBg = new Image(src);
        if (nOuterWidth != -1) {
            DOM.setElementProperty(imgSelectionBg.getElement(), "width", nOuterWidth + "");
            imgSelectionBg.getElement().getStyle().setPropertyPx("maxWidth", nOuterWidth);
        }

        if (nOuterHeight != -1) {
            DOM.setElementProperty(imgSelectionBg.getElement(), "height", nOuterHeight + "");
            imgSelectionBg.getElement().getStyle().setPropertyPx("maxHeight", nOuterHeight);
        }

        selectionContainer.add(imgSelectionBg, -this.nInnerX - 1, -this.nInnerY - 1);
        this._container.add(selectionContainer, this.nInnerX, this.nInnerY);

        this.buildSelectionArea();

        this._container.add(this.handlesContainer, this.nInnerX, this.nInnerY);
    }

    /**
     * Validates all initial data. This method is called after the canvas image becomes loaded and we know, what are its actual 
     * dimensions. If any of data are incorrect, then set the default values.
     */
    private void validateInitialData() {

        final boolean isDefaultWidth = this.nInnerX == -1 && this.nInnerWidth == -1;
        final boolean isInvalidWidthAndX = this.nOuterWidth < (this.nInnerX + this.nInnerWidth);

        if (isDefaultWidth || isInvalidWidthAndX) {
            this.nInnerX = (int) (nOuterWidth * 0.2);
            this.nInnerWidth = (int) (nOuterWidth * 0.2);
        }

        // minimal width couldn't be more, than initial selection.
        if (this.MIN_WIDTH > this.nInnerWidth)
            this.MIN_WIDTH = this.HANDLE_SIZE;

        final boolean isDefaultHeight = this.nInnerY == -1 && this.nInnerHeight == -1;
        final boolean isInvalidHeightY = this.nOuterHeight < (this.nInnerY + this.nInnerHeight);

        if (isDefaultHeight || isInvalidHeightY) {
            this.nInnerY = (int) (nOuterHeight * 0.2);
            this.nInnerHeight = (int) ((this.aspectRatio == 0) ? (nOuterHeight * 0.2)
                    : (nInnerWidth / aspectRatio));
        }

        // minimal height couldn't be more, than initial selection.
        if (this.MIN_HEIGHT > this.nInnerHeight)
            this.MIN_HEIGHT = this.HANDLE_SIZE;
    }

    /**
     * Add special handles. User can drag these handle and change form of the selected area
     * 
     * @return container with handles and needed attached event listeners
     */
    private AbsolutePanel buildSelectionArea() {

        // add selection handles
        this.handlesContainer = new AbsolutePanel();

        this.handlesContainer.setWidth(this.nInnerWidth + "px");
        this.handlesContainer.setHeight(this.nInnerHeight + "px");

        this.handlesContainer.setStyleName(this.bundleResources.css().handlesContainer());
        this.handlesContainer.getElement().getStyle().setOverflow(Overflow.VISIBLE);

        // append background
        this.draggableBackground = this.appendDraggableBackground();

        // find the center of draggable handle to make an offset for the positioning
        final int h = this.HANDLE_SIZE / 2;

        /*
           1px and 2px below are correction because of 1px border. 
           We need to position handle exactly on the center of the selection corner.
        */

        // append top left corner handler. 
        this.appendHandle(Cursor.NW_RESIZE, Constants.DRAG_TOP_LEFT_CORNER, -h, 0, 0, -(h + 1));

        // append top right corner handler
        this.appendHandle(Cursor.NE_RESIZE, Constants.DRAG_TOP_RIGHT_CORNER, -h, -(h + 2), 0, 0);

        // append bottom left corner handler
        this.appendHandle(Cursor.SW_RESIZE, Constants.DRAG_BOTTOM_LEFT_CORNER, 0, 0, -(h + 2), -(h + 1));

        // append bottom right corner handler
        this.appendHandle(Cursor.SE_RESIZE, Constants.DRAG_BOTTOM_RIGHT_CORNER, 0, -(h + 2), -(h + 2), 0);

        return handlesContainer;
    }

    /**
     * Creates one small draggable selection handle and appends it to the corner of selection area. 
     * User can drag this handle to change shape of the selection area
     * 
     * @param cursor cursor type for the CSS
     * @param actionType action type for the event processor
     * @param top  top value in PX
     * @param right right value in PX
     * @param bottom bottom value in PX
     * @param left left value in PX
     */
    private void appendHandle(Cursor cursor, final byte actionType, int top, int right, int bottom, int left) {

        HTML handle = new HTML();
        handle.setStyleName(this.bundleResources.css().handle());
        handle.getElement().getStyle().setCursor(cursor);

        handle.addMouseDownHandler(new MouseDownHandler() {

            public void onMouseDown(MouseDownEvent event) {
                isDown = true;
                action = actionType;
            }
        });
        handle.addTouchStartHandler(new TouchStartHandler() {

            public void onTouchStart(TouchStartEvent event) {
                isDown = true;
                action = actionType;
            }
        });

        if (top != 0)
            handle.getElement().getStyle().setTop(top, Unit.PX);
        if (right != 0)
            handle.getElement().getStyle().setRight(right, Unit.PX);
        if (bottom != 0)
            handle.getElement().getStyle().setBottom(bottom, Unit.PX);
        if (left != 0)
            handle.getElement().getStyle().setLeft(left, Unit.PX);

        this.handlesContainer.add(handle);
    }

    /**
     * Append draggable background for the selection area
     */
    private HTML appendDraggableBackground() {

        final HTML backgroundHandle = new HTML();
        backgroundHandle.setWidth(this.nInnerWidth + "px");
        backgroundHandle.setHeight(this.nInnerHeight + "px");
        backgroundHandle.getElement().getStyle().setCursor(Cursor.MOVE);
        backgroundHandle.addStyleName(this.bundleResources.css().selectionDraggableBackground());

        backgroundHandle.addMouseDownHandler(new MouseDownHandler() {

            public void onMouseDown(MouseDownEvent event) {
                isDown = true;
                action = Constants.DRAG_BACKGROUND;
            }
        });
        backgroundHandle.addTouchStartHandler(new TouchStartHandler() {

            public void onTouchStart(TouchStartEvent event) {
                isDown = true;
                action = Constants.DRAG_BACKGROUND;
            }
        });

        this.handlesContainer.add(backgroundHandle, 0, 0);

        return backgroundHandle;
    }

    /**
     * provides dragging action
     * 
     * @param cursorX - cursor X-position relatively the canvas
     * @param cursorY - cursor Y-position relatively the canvas
     */
    private void provideDragging(int cursorX, int cursorY) {

        Element elH = null; // handle's container
        Element elS = null; // selection's container
        Element elImg = null;

        int futureWidth = 0;
        int futureHeight = 0;

        switch (this.action) {

        case Constants.DRAG_BACKGROUND:

            if (offsetX == -1) {
                offsetX = cursorX - _container.getWidgetLeft(this.handlesContainer);
            }
            if (offsetY == -1) {
                offsetY = cursorY - _container.getWidgetTop(this.handlesContainer);
            }

            elH = this.handlesContainer.getElement();

            this.nInnerX = cursorX - offsetX;
            this.nInnerY = cursorY - offsetY;

            // don't drag selection out of the canvas borders
            if (this.nInnerX < -SELECTION_BORDER_SIZE)
                this.nInnerX = -SELECTION_BORDER_SIZE;
            if (this.nInnerY < -SELECTION_BORDER_SIZE)
                this.nInnerY = -SELECTION_BORDER_SIZE;
            if (this.nInnerX + this.nInnerWidth > this.nOuterWidth + SELECTION_BORDER_SIZE)
                this.nInnerX = this.nOuterWidth - this.nInnerWidth + SELECTION_BORDER_SIZE;
            if (this.nInnerY + this.nInnerHeight > this.nOuterHeight + SELECTION_BORDER_SIZE)
                this.nInnerY = this.nOuterHeight - this.nInnerHeight + SELECTION_BORDER_SIZE;

            elH.getStyle().setLeft(this.nInnerX, Unit.PX);
            elH.getStyle().setTop(this.nInnerY, Unit.PX);

            elS = this.selectionContainer.getElement();
            elS.getStyle().setLeft(this.nInnerX, Unit.PX);
            elS.getStyle().setTop(this.nInnerY, Unit.PX);

            elImg = ((Image) this.selectionContainer.getWidget(0)).getElement();
            elImg.getStyle().setLeft(-this.nInnerX - SELECTION_BORDER_SIZE, Unit.PX);
            elImg.getStyle().setTop(-this.nInnerY - SELECTION_BORDER_SIZE, Unit.PX);
            break;

        case Constants.DRAG_TOP_LEFT_CORNER:

            if (initX == -1) {
                initX = _container.getWidgetLeft(this.handlesContainer);
                initW = nInnerWidth;
            }
            if (initY == -1) {
                initY = _container.getWidgetTop(this.handlesContainer);
                initH = nInnerHeight;
            }
            futureWidth = initW + (initX - cursorX);
            futureHeight = initH + (initY - cursorY);

            if (futureWidth < this.MIN_WIDTH || futureHeight < this.MIN_HEIGHT) {
                return;
            }

            this.nInnerWidth = futureWidth;
            this.nInnerHeight = futureHeight;

            this.nInnerX = cursorX;
            this.nInnerY = cursorY;

            // compensation for specified aspect ratio
            if (this.aspectRatio != 0) {
                if (abs(this.initX - this.nInnerX) > abs(this.initY - this.nInnerY)) {
                    int newHeight = (int) (this.nInnerWidth / this.aspectRatio);
                    this.nInnerY -= newHeight - this.nInnerHeight;

                    // to prevent resizing out of the canvas on the Y axes
                    if (this.nInnerY <= 0) {
                        this.nInnerY = 0;
                        newHeight = this.initY + this.initH;
                        this.nInnerWidth = (int) (newHeight * this.aspectRatio);
                        this.nInnerX = this.initX - (int) (this.initY * this.aspectRatio);
                    }

                    this.nInnerHeight = newHeight;
                } else {
                    int newWidth = (int) (this.nInnerHeight * this.aspectRatio);
                    this.nInnerX -= newWidth - this.nInnerWidth;

                    // to prevent resizing out of the canvas on the X axis
                    if (this.nInnerX < 0) {
                        this.nInnerX = 0;
                        newWidth = this.initX + this.initW;
                        this.nInnerHeight = (int) (newWidth / this.aspectRatio);
                        this.nInnerY = this.initY - (int) (this.initX / this.aspectRatio);
                    }

                    this.nInnerWidth = newWidth;
                }
            }

            elH = this.handlesContainer.getElement();

            elH.getStyle().setLeft(this.nInnerX, Unit.PX);
            elH.getStyle().setTop(this.nInnerY, Unit.PX);
            elH.getStyle().setWidth(nInnerWidth, Unit.PX);
            elH.getStyle().setHeight(nInnerHeight, Unit.PX);

            elS = this.selectionContainer.getElement();
            elS.getStyle().setLeft(this.nInnerX, Unit.PX);
            elS.getStyle().setTop(this.nInnerY, Unit.PX);
            elS.getStyle().setWidth(nInnerWidth, Unit.PX);
            elS.getStyle().setHeight(nInnerHeight, Unit.PX);

            elImg = ((Image) this.selectionContainer.getWidget(0)).getElement();
            elImg.getStyle().setLeft(-this.nInnerX - 1, Unit.PX);
            elImg.getStyle().setTop(-this.nInnerY - 1, Unit.PX);

            Element el3 = this.draggableBackground.getElement();
            el3.getStyle().setWidth(nInnerWidth, Unit.PX);
            el3.getStyle().setHeight(nInnerHeight, Unit.PX);
            break;

        case Constants.DRAG_TOP_RIGHT_CORNER:

            if (initX == -1) {
                initX = _container.getWidgetLeft(this.handlesContainer) + nInnerWidth;
                initW = nInnerWidth;
            }
            if (initY == -1) {
                initY = _container.getWidgetTop(this.handlesContainer);
                initH = nInnerHeight;
            }

            futureWidth = initW + (cursorX - initX);
            futureHeight = initH + (initY - cursorY);

            if (futureWidth < this.MIN_WIDTH || futureHeight < this.MIN_HEIGHT) {
                return;
            }

            nInnerWidth = futureWidth;
            nInnerHeight = futureHeight;

            // compensation for specified aspect ratio
            if (this.aspectRatio != 0) {
                if (abs(initX - cursorX) > abs(initY - cursorY)) {
                    // move cursor right, top side has been adjusted automatically

                    int newHeight = (int) (nInnerWidth / this.aspectRatio);
                    cursorY -= newHeight - nInnerHeight;

                    // to prevent resizing out of the canvas on the Y axes
                    if (cursorY <= 0) {
                        cursorY = 0;
                        newHeight = this.initY + this.initH;
                        this.nInnerWidth = (int) (newHeight * this.aspectRatio);
                    }

                    nInnerHeight = newHeight;
                } else {
                    // move cursor up, right side has been adjusted automatically

                    nInnerWidth = (int) (nInnerHeight * this.aspectRatio);

                    // to prevent resizing out of the canvas on the X axis
                    if ((this.nInnerWidth + this.nInnerX) >= this.nOuterWidth) {
                        this.nInnerWidth = this.nOuterWidth - this.nInnerX;
                        this.nInnerHeight = (int) (this.nInnerWidth / this.aspectRatio);
                        this.nInnerY = this.initY
                                - (int) ((this.nOuterWidth - this.nInnerX - this.initW) / this.aspectRatio);
                        cursorY = this.nInnerY;
                    }
                }
            }

            this.nInnerY = cursorY;

            elH = this.handlesContainer.getElement();

            elH.getStyle().setTop(cursorY, Unit.PX);
            elH.getStyle().setWidth(nInnerWidth, Unit.PX);
            elH.getStyle().setHeight(nInnerHeight, Unit.PX);

            elS = this.selectionContainer.getElement();
            elS.getStyle().setTop(cursorY, Unit.PX);
            elS.getStyle().setWidth(nInnerWidth, Unit.PX);
            elS.getStyle().setHeight(nInnerHeight, Unit.PX);

            elImg = ((Image) this.selectionContainer.getWidget(0)).getElement();
            elImg.getStyle().setTop(-cursorY - 1, Unit.PX);

            el3 = this.draggableBackground.getElement();
            el3.getStyle().setWidth(nInnerWidth, Unit.PX);
            el3.getStyle().setHeight(nInnerHeight, Unit.PX);
            break;

        case Constants.DRAG_BOTTOM_LEFT_CORNER:

            if (initX == -1) {
                initX = _container.getWidgetLeft(this.handlesContainer);
                initW = nInnerWidth;
            }
            if (initY == -1) {
                initY = _container.getWidgetTop(this.handlesContainer) + nInnerHeight;
                initH = nInnerHeight;
            }

            futureWidth = initW + (initX - cursorX);
            futureHeight = initH + (cursorY - initY);

            if (futureWidth < this.MIN_WIDTH || futureHeight < this.MIN_HEIGHT) {
                return;
            }

            nInnerWidth = futureWidth;
            nInnerHeight = futureHeight;

            // compensation for specified aspect ratio
            if (this.aspectRatio != 0) {
                if (abs(initX - cursorX) > abs(initY - cursorY)) {
                    // cursor goes left, bottom side goes down...

                    nInnerHeight = (int) (nInnerWidth / this.aspectRatio);

                    // to prevent resizing out of the canvas on the Y axis
                    if ((this.nInnerHeight + this.nInnerY) >= this.nOuterHeight) {
                        this.nInnerHeight = this.nOuterHeight - this.nInnerY;
                        this.nInnerWidth = (int) (this.nInnerHeight * this.aspectRatio);
                        this.nInnerY = this.nOuterHeight - this.nInnerHeight;
                        cursorX = this.initX - (int) ((this.nOuterHeight - this.initY) * this.aspectRatio);
                    }

                } else {
                    // cursor goes down, left side goes to left

                    int newWidth = (int) (nInnerHeight * this.aspectRatio);
                    cursorX -= newWidth - nInnerWidth;

                    // to prevent resizing out of the canvas on the X axis
                    if (cursorX <= 0) {
                        newWidth = this.nInnerWidth + this.initX;
                        this.nInnerHeight = (int) (newWidth / this.aspectRatio);
                        cursorX = 0;
                    }

                    nInnerWidth = newWidth;
                }
            }

            this.nInnerX = cursorX;

            elH = this.handlesContainer.getElement();

            elH.getStyle().setLeft(cursorX, Unit.PX);
            elH.getStyle().setWidth(nInnerWidth, Unit.PX);
            elH.getStyle().setHeight(nInnerHeight, Unit.PX);

            elS = this.selectionContainer.getElement();
            elS.getStyle().setLeft(cursorX, Unit.PX);
            elS.getStyle().setWidth(nInnerWidth, Unit.PX);
            elS.getStyle().setHeight(nInnerHeight, Unit.PX);

            elImg = ((Image) this.selectionContainer.getWidget(0)).getElement();
            elImg.getStyle().setLeft(-cursorX - 1, Unit.PX);

            el3 = this.draggableBackground.getElement();
            el3.getStyle().setWidth(nInnerWidth, Unit.PX);
            el3.getStyle().setHeight(nInnerHeight, Unit.PX);
            break;

        case Constants.DRAG_BOTTOM_RIGHT_CORNER:

            if (initX == -1) {
                initX = _container.getWidgetLeft(this.handlesContainer) + nInnerWidth;
                initW = nInnerWidth;
            }
            if (initY == -1) {
                initY = _container.getWidgetTop(this.handlesContainer) + nInnerHeight;
                initH = nInnerHeight;
            }

            futureWidth = initW + (cursorX - initX);
            futureHeight = initH + (cursorY - initY);

            if (futureWidth < this.MIN_WIDTH || futureHeight < this.MIN_HEIGHT) {
                return;
            }

            nInnerWidth = futureWidth;
            nInnerHeight = futureHeight;

            // compensation for specified aspect ratio
            if (this.aspectRatio != 0) {
                if (abs(initX - cursorX) > abs(initY - cursorY)) {
                    // cursor goes right, bottom side goes down...

                    nInnerHeight = (int) (nInnerWidth / this.aspectRatio);

                    // to prevent resizing out of the canvas on the Y axis
                    if ((this.nInnerHeight + this.nInnerY) >= this.nOuterHeight) {
                        this.nInnerHeight = this.nOuterHeight - this.nInnerY;
                        this.nInnerWidth = (int) (this.nInnerHeight * this.aspectRatio);
                        this.nInnerY = this.nOuterHeight - this.nInnerHeight;
                        cursorX = this.nOuterWidth;
                    }

                } else {
                    // cursor goes down, right side goes to right
                    nInnerWidth = (int) (nInnerHeight * this.aspectRatio);

                    // to prevent resizing out of the canvas on the X axis
                    if (this.nInnerWidth + this.nInnerX >= this.nOuterWidth) {
                        this.nInnerWidth = this.nOuterWidth - this.nInnerX;
                        this.nInnerHeight = (int) (this.nInnerWidth / this.aspectRatio);
                        cursorX = this.nOuterHeight;
                    }
                }
            }

            elH = this.handlesContainer.getElement();
            elH.getStyle().setWidth(nInnerWidth, Unit.PX);
            elH.getStyle().setHeight(nInnerHeight, Unit.PX);

            elS = this.selectionContainer.getElement();
            elS.getStyle().setWidth(nInnerWidth, Unit.PX);
            elS.getStyle().setHeight(nInnerHeight, Unit.PX);

            el3 = this.draggableBackground.getElement();
            el3.getStyle().setWidth(nInnerWidth, Unit.PX);
            el3.getStyle().setHeight(nInnerHeight, Unit.PX);
            break;

        default:
            break;
        }

    }

    /**
     * Resets all initial values.
     * 
     */
    private void reset() {

        this.initX = -1;
        this.initY = -1;
        this.initW = -1;
        this.initH = -1;
        this.offsetX = -1;
        this.offsetY = -1;
        this.action = Constants.DRAG_NONE;
    }

    /**
     * Returns absolute value
     * 
     * @param value
     * @return absolute value
     */
    private int abs(int value) {
        return value >= 0 ? value : -value;
    }

    // DOM HANDLERS

    /**
     * {@inheritDoc}
     */
    public void onMouseMove(MouseMoveEvent event) {

        if (this.isDown) {

            this.provideDragging(event.getRelativeX(this._container.getElement()),
                    event.getRelativeY(this._container.getElement()));

            this.updatePreviewWidget();
        }
    }

    /**
     * Update preview widget if needed.
     */
    private void updatePreviewWidget() {
        if (previewWidget != null) {
            previewWidget.updatePreview(this.getSelectionWidth(), this.getSelectionHeight(),
                    this.getSelectionXCoordinate(), this.getSelectionYCoordinate());
        }
    }

    /**
     * {@inheritDoc}
     */
    public void onTouchMove(TouchMoveEvent event) {
        if (this.isDown) {

            JsArray<Touch> touches = event.getTouches();
            if (touches.length() > 0) {

                int x = touches.get(0).getRelativeX(this._container.getElement());
                int y = touches.get(0).getRelativeY(this._container.getElement());

                if (x < 0 || y < 0 || x > nOuterWidth || y > nOuterHeight) {

                    /*
                     * There is no such method as "onMouseOut" for touching, so we can't rely
                     * on event handler. We should process manually these cases, when finger (i.e. cursor) is 
                     * out of the cropper area.
                     * 
                     * If user moves his finger out of the canvas, then "stick" the selection to the canvas edges
                     */
                    if (x < 0)
                        x = 0;
                    if (x > nOuterWidth)
                        x = nOuterWidth;
                    if (y < 0)
                        y = 0;
                    if (y > nOuterHeight)
                        y = nOuterHeight;
                }

                this.provideDragging(x, y);
            }

        }
    }

    /**
     * Resets the dragging state. After this method, the cropper
     * presumes that the dragging action is finished.
     */
    private void resetDraggingState() {
        if (this.isDown) {
            this.isDown = false;
            this.reset();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void onMouseUp(MouseUpEvent event) {
        this.resetDraggingState();
    }

    /**
     * {@inheritDoc}
     */
    public void onMouseOut(MouseOutEvent event) {

        /*
         * When the cursor is out of the canvas, we want to
         * snap the selection to appropriate canvas border. So, before
         * we reset the dragging action, let's drag handle last time.
         * 
         * @see Issue 12.
         */
        int x = event.getRelativeX(this._container.getElement());
        int y = event.getRelativeY(this._container.getElement());

        // correct coordinates, that are out of canvas
        if (x < 0)
            x = 0;
        if (x > this.nOuterWidth)
            x = this.nOuterWidth;
        if (y < 0)
            y = 0;
        if (y > this.nOuterHeight)
            x = this.nOuterHeight;

        this.provideDragging(x, y);

        /*
         * if cursor is out of canvas and the mouse button is pressed,
         * then we want to "unclick" the mouse button programmatically.
         * Otherwise the selection would become "sticky".
         */
        this.resetDraggingState();
    }

    /**
     * {@inheritDoc}
     */
    public void onTouchEnd(TouchEndEvent event) {
        this.resetDraggingState();
    }

    /**
     * Absolute panel with one overrided method, that prevents a static positioning
     * when a widget has (-1, -1) coordinates.
     * 
     * @author ilja.hamalainen@gmail.com (Ilja Hmlinen)
     *
     */
    private static class AbsolutePanelImpl extends AbsolutePanel {

        /**
         * Original method causes the child widget to be positioned statically,
         * if the left and top are (-1, -1). In our case the background could be positioned
         * (-1,-1), when the selected area is placed on the top left corner.
         * 
         * @See com.google.gwt.user.client.ui.AbsolutePanel.setWidgetPosition(Widget, int, int)
         */
        @Override
        protected void setWidgetPositionImpl(Widget w, int left, int top) {
            com.google.gwt.user.client.Element h = w.getElement();
            DOM.setStyleAttribute(h, "position", "absolute");
            DOM.setStyleAttribute(h, "left", left + "px");
            DOM.setStyleAttribute(h, "top", top + "px");
        }
    }
}