//  LabelEditor.cpp
//
//  Copyright (C) 2013-2016  Jaye Evins <evins@snaught.com>
//
//  This file is part of gLabels-qt.
//
//  gLabels-qt is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  gLabels-qt is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with gLabels-qt.  If not, see <http://www.gnu.org/licenses/>.
//


#include "LabelEditor.hpp"

#include "Cursors.hpp"
#include "UndoRedoModel.hpp"

#include "model/FrameCd.hpp"
#include "model/FrameEllipse.hpp"
#include "model/FrameRect.hpp"
#include "model/FrameRound.hpp"
#include "model/Model.hpp"
#include "model/ModelObject.hpp"
#include "model/ModelBarcodeObject.hpp"
#include "model/ModelBoxObject.hpp"
#include "model/ModelEllipseObject.hpp"
#include "model/ModelImageObject.hpp"
#include "model/ModelLineObject.hpp"
#include "model/ModelTextObject.hpp"
#include "model/Markup.hpp"
#include "model/Settings.hpp"

#include <QDebug>
#include <QMimeData>
#include <QMouseEvent>
#include <QtMath>


//
// Private
//
namespace
{
        const int     nZoomLevels = 11;
        const double  zoomLevels[nZoomLevels] = { 8, 6, 4, 3, 2, 1.5, 1, 0.75, 0.67, 0.50, 0.33 };

        const double  ZOOM_TO_FIT_PAD = 16.0;

        const QColor  backgroundColor( 192, 192, 192 );

        const QColor  shadowColor( 64, 64, 64, 128 );
        const double  shadowOffsetPixels = 4;

        const QColor  labelColor( 255, 255, 255 );
        const QColor  labelOutlineColor( 0, 0, 0 );
        const double  labelOutlineWidthPixels = 1;

        const QColor  gridLineColor( 192, 192, 192 );
        const double  gridLineWidthPixels = 1;

        const QColor  markupLineColor( 240, 99, 99 );
        const double  markupLineWidthPixels = 1;

        const QColor  selectRegionFillColor( 192, 192, 255, 128 );
        const QColor  selectRegionOutlineColor( 0, 0, 255, 128 );
        const double  selectRegionOutlineWidthPixels = 3;
}


namespace glabels
{

        ///
        /// Constructor
        ///
        LabelEditor::LabelEditor( QScrollArea* scrollArea, QWidget* parent )
                : QWidget(parent), mScrollArea(scrollArea)
        {
                mModel              = nullptr;
                mUndoRedoModel      = nullptr;

                mZoom               = 1;
                mZoomToFitFlag      = false;
                mScale              = 1;
                mMarkupVisible      = true;
                mGridVisible        = true;

                mState = IdleState;

                mSelectRegionVisible  = false;
                mResizeObject         = nullptr;
                mResizeHandleLocation = model::Handle::NULL_HANDLE;
                mResizeHonorAspect    = false;
                mCreateObjectType     = Box;
                mCreateObject         = nullptr;

                setMouseTracking( true );
                setFocusPolicy(Qt::StrongFocus);
                setAcceptDrops( true );

                connect( model::Settings::instance(), SIGNAL(changed()), this, SLOT(onSettingsChanged()) );
                onSettingsChanged();
        }


        ///
        /// Zoom property
        ///
        double
        LabelEditor::zoom() const
        {
                return mZoom;
        }


        ///
        /// Markup visible? property
        ///
        bool
        LabelEditor::markupVisible() const
        {
                return mMarkupVisible;
        }


        ///
        /// Grid visible? property
        ///
        bool
        LabelEditor::qridVisible() const
        {
                return mGridVisible;
        }


        ///
        /// Model Parameter Setter
        ///
        void
        LabelEditor::setModel( model::Model* model, UndoRedoModel* undoRedoModel )
        {
                mModel = model;
                mUndoRedoModel = undoRedoModel;

                if ( model )
                {
                        zoomToFit();

                        connect( model, SIGNAL(changed()), this, SLOT(update()) );
                        connect( model, SIGNAL(selectionChanged()), this, SLOT(update()) );
                        connect( model, SIGNAL(sizeChanged()), this, SLOT(onModelSizeChanged()) );

                        update();
                }
        }


        ///
        /// Grid Visibility Parameter Setter
        ///
        void
        LabelEditor::setGridVisible( bool visibleFlag )
        {
                mGridVisible = visibleFlag;
                update();
        }


        ///
        /// Markup Visibility Parameter Setter
        ///
        void
        LabelEditor::setMarkupVisible( bool visibleFlag )
        {
                mMarkupVisible = visibleFlag;
                update();
        }


        ///
        /// Zoom In "One Notch"
        ///
        void
        LabelEditor::zoomIn()
        {
                // Find closest standard zoom level to our current zoom
                // Start with 2nd largest scale
                int i_min = 1;
                double dist_min = qFabs( zoomLevels[1] - mZoom );

                for ( int i = 2; i < nZoomLevels; i++ )
                {
                        double dist = qFabs( zoomLevels[i] - mZoom );
                        if ( dist < dist_min )
                        {
                                i_min = i;
                                dist_min = dist;
                        }
                }

                // Zoom in one notch
                setZoomReal( zoomLevels[i_min-1], false );
        }


        ///
        /// Zoom Out "One Notch"
        ///
        void
        LabelEditor::zoomOut()
        {
                // Find closest standard zoom level to our current zoom
                // Start with largest scale, end on 2nd smallest
                int i_min = 0;
                double dist_min = qFabs( zoomLevels[0] - mZoom );

                for ( int i = 1; i < (nZoomLevels-1); i++ )
                {
                        double dist = qFabs( zoomLevels[i] - mZoom );
                        if ( dist < dist_min )
                        {
                                i_min = i;
                                dist_min = dist;
                        }
                }

                // Zoom out one notch
                setZoomReal( zoomLevels[i_min+1], false );
        }


        ///
        /// Zoom To 1:1 Scale
        ///
        void
        LabelEditor::zoom1To1()
        {
                setZoomReal( 1.0, false );
        }


        ///
        /// Zoom To Fit
        ///
        void
        LabelEditor::zoomToFit()
        {
                double wPixels = mScrollArea->maximumViewportSize().width();
                double hPixels = mScrollArea->maximumViewportSize().height();

                double x_scale = ( wPixels - ZOOM_TO_FIT_PAD ) / mModel->w().pt();
                double y_scale = ( hPixels - ZOOM_TO_FIT_PAD ) / mModel->h().pt();
                double newZoom = qMin( x_scale, y_scale ) * model::PTS_PER_INCH / physicalDpiX();

                // Limits
                newZoom = qMin( newZoom, zoomLevels[0] );
                newZoom = qMax( newZoom, zoomLevels[nZoomLevels-1] );

                setZoomReal( newZoom, true );
        }


        ///
        /// Is Zoom at Maximum?
        ///
        bool
        LabelEditor::isZoomMax() const
        {
                return mZoom >= zoomLevels[0];
        }


        ///
        /// Is Zoom at Minimum?
        ///
        bool
        LabelEditor::isZoomMin() const
        {
                return mZoom <= zoomLevels[nZoomLevels-1];
        }


        ///
        /// Set Zoom to Value
        ///
        void
        LabelEditor::setZoomReal( double zoom, bool zoomToFitFlag )
        {
                mZoom          = zoom;
                mZoomToFitFlag = zoomToFitFlag;

                /* Actual scale depends on DPI of display (assume DpiX == DpiY). */
                mScale = zoom * physicalDpiX() / model::PTS_PER_INCH;

                setMinimumSize( mScale*mModel->w().pt() + ZOOM_TO_FIT_PAD,
                                mScale*mModel->h().pt() + ZOOM_TO_FIT_PAD );

                /* Adjust origin to center label in widget. */
                mX0 = (width()/mScale - mModel->w()) / 2;
                mY0 = (height()/mScale - mModel->h()) / 2;

                update();

                emit zoomChanged();
        }


        ///
        /// Arrow mode (normal mode)
        ///
        void
        LabelEditor::arrowMode()
        {
                setCursor( Qt::ArrowCursor );

                mState = IdleState;
        }


        ///
        /// Create box mode
        ///
        void
        LabelEditor::createBoxMode()
        {
                setCursor( Cursors::Box() );

                mCreateObjectType = Box;
                mState = CreateIdle;
        }


        ///
        /// Create ellipse mode
        ///
        void
        LabelEditor::createEllipseMode()
        {
                setCursor( Cursors::Ellipse() );

                mCreateObjectType = Ellipse;
                mState = CreateIdle;
        }


        ///
        /// Create image mode
        ///
        void
        LabelEditor::createImageMode()
        {
                setCursor( Cursors::Image() );

                mCreateObjectType = Image;
                mState = CreateIdle;
        }


        ///
        /// Create line mode
        ///
        void
        LabelEditor::createLineMode()
        {
                setCursor( Cursors::Line() );

                mCreateObjectType = Line;
                mState = CreateIdle;
        }


        ///
        /// Create text mode
        ///
        void
        LabelEditor::createTextMode()
        {
                setCursor( Cursors::Text() );

                mCreateObjectType = Text;
                mState = CreateIdle;
        }


        ///
        /// Create barcode mode
        ///
        void
        LabelEditor::createBarcodeMode()
        {
                setCursor( Cursors::Barcode() );

                mCreateObjectType = Barcode;
                mState = CreateIdle;
        }


        ///
        /// Resize Event Handler
        ///
        void
        LabelEditor::resizeEvent( QResizeEvent *event )
        {
                if ( mModel )
                {
                        if ( mZoomToFitFlag )
                        {
                                zoomToFit();
                        }
                        else
                        {
                                /* Re-adjust origin to center label in widget. */
                                mX0 = (width()/mScale - mModel->w()) / 2;
                                mY0 = (height()/mScale - mModel->h()) / 2;

                                update();
                        }
                }
        }


        ///
        /// Mouse Button Press Event Handler
        ///
        void
        LabelEditor::mousePressEvent( QMouseEvent* event )
        {
                if ( mModel )
                {
                        /*
                         * Transform to label coordinates
                         */
                        QTransform transform;

                        transform.scale( mScale, mScale );
                        transform.translate( mX0.pt(), mY0.pt() );

                        QPointF pWorld = transform.inverted().map( event->pos() );
                        model::Distance xWorld = model::Distance::pt( pWorld.x() );
                        model::Distance yWorld = model::Distance::pt( pWorld.y() );


                        if ( event->button() & Qt::LeftButton )
                        {
                                //
                                // LEFT BUTTON
                                //
                                switch (mState)
                                {

                                case IdleState:
                                {
                                        model::ModelObject* object = nullptr;
                                        auto& handle = mModel->handleAt( mScale, xWorld, yWorld );

                                        if ( mModel->isSelectionAtomic() && !handle.isNull() )
                                        {
                                                //
                                                // Start an object resize
                                                //
                                                mResizeObject = handle.owner();
                                                mResizeHandleLocation = handle.location();
                                                mResizeHonorAspect = event->modifiers() & Qt::ControlModifier;
                                                if ( mResizeObject->lockAspectRatio() )
                                                {
                                                        mResizeHonorAspect = !mResizeHonorAspect;
                                                }

                                                mState = ArrowResize;
                                        }
                                        else if ( (object = mModel->objectAt( mScale, xWorld, yWorld )) != nullptr )
                                        {
                                                //
                                                // Start a Move Selection (adjusting selection if necessary)
                                                //
                                                if ( event->modifiers() & Qt::ControlModifier )
                                                {
                                                        if ( object->isSelected() )
                                                        {
                                                                // Un-selecting a selected item
                                                                mModel->unselectObject( object );
                                                        }
                                                        else
                                                        {
                                                                // Add to current selection
                                                                mModel->selectObject( object );
                                                        }
                                                }
                                                else
                                                {
                                                        if ( !object->isSelected() )
                                                        {
                                                                // Replace current selection with this object
                                                                mModel->unselectAll();
                                                                mModel->selectObject( object );
                                                        }
                                                }

                                                mMoveLastX = xWorld;
                                                mMoveLastY = yWorld;

                                                mState = ArrowMove;
                                        }
                                        else
                                        {
                                                //
                                                // Start a Select Region
                                                //
                                                if ( !(event->modifiers() & Qt::ControlModifier) )
                                                {
                                                        mModel->unselectAll();
                                                }

                                                mSelectRegionVisible = true;
                                                mSelectRegion.setX1( xWorld );
                                                mSelectRegion.setY1( yWorld );
                                                mSelectRegion.setX2( xWorld );
                                                mSelectRegion.setY2( yWorld );

                                                mState = ArrowSelectRegion;
                                                update();
                                        }
                                }
                                break;


                                case CreateIdle:
                                {
                                        switch ( mCreateObjectType )
                                        {
                                        case Box:
                                                mCreateObject = new model::ModelBoxObject();
                                                break;
                                        case Ellipse:
                                                mCreateObject = new model::ModelEllipseObject();
                                                break;
                                        case Line:
                                                mCreateObject = new model::ModelLineObject();
                                                break;
                                        case Image:
                                                mCreateObject = new model::ModelImageObject();
                                                break;
                                        case Text:
                                                mCreateObject = new model::ModelTextObject();
                                                break;
                                        case Barcode:
                                                mCreateObject = new model::ModelBarcodeObject();
                                                break;
                                        default:
                                                qDebug() << "LabelEditor::mousePressEvent: Invalid creation type. Should not happen!";
                                                break;
                                        }

                                        mCreateObject->setPosition( xWorld, yWorld );
                                        mCreateObject->setSize( 0, 0 );
                                        mModel->addObject( mCreateObject );

                                        mModel->unselectAll();
                                        mModel->selectObject( mCreateObject );

                                        mCreateX0 = xWorld;
                                        mCreateY0 = yWorld;

                                        mState = CreateDrag;
                                }
                                break;


                                default:
                                {
                                        qDebug() << "LabelEditor::mousePressEvent: Invalid state. Should not happen!";
                                }
                                break;

                                }

                        }
                        else if ( event->button() & Qt::RightButton )
                        {
                                //
                                // RIGHT BUTTON
                                //
                                if ( mState == IdleState )
                                {
                                        emit contextMenuActivate( model::Point( xWorld, yWorld ) );
                                }
                        }
                }
        }


        ///
        /// Mouse Movement Event Handler
        ///
        void
        LabelEditor::mouseMoveEvent( QMouseEvent* event )
        {
                if ( mModel )
                {
                        /*
                         * Transform to label coordinates
                         */
                        QTransform transform;

                        transform.scale( mScale, mScale );
                        transform.translate( mX0.pt(), mY0.pt() );

                        QPointF pWorld = transform.inverted().map( event->pos() );
                        model::Distance xWorld = model::Distance::pt( pWorld.x() );
                        model::Distance yWorld = model::Distance::pt( pWorld.y() );


                        /*
                         * Emit signal regardless of mode
                         */
                        emit pointerMoved( model::Point( xWorld, yWorld ) );


                        /*
                         * Handle event as appropriate for state
                         */
                        switch (mState)
                        {

                        case IdleState:
                                if ( mModel->isSelectionAtomic() &&
                                     !mModel->handleAt( mScale, xWorld, yWorld ).isNull() )
                                {
                                        setCursor( Cursors::Crosshair() );
                                }
                                else if ( mModel->objectAt( mScale, xWorld, yWorld ) )
                                {
                                        setCursor( Cursors::Move() );
                                }
                                else
                                {
                                        setCursor( Qt::ArrowCursor );
                                }
                                break;

                        case ArrowSelectRegion:
                                mSelectRegion.setX2( xWorld );
                                mSelectRegion.setY2( yWorld );
                                update();
                                break;

                        case ArrowMove:
                                mUndoRedoModel->checkpoint( tr("Move") );
                                mModel->moveSelection( (xWorld - mMoveLastX),
                                                       (yWorld - mMoveLastY) );
                                mMoveLastX = xWorld;
                                mMoveLastY = yWorld;
                                break;

                        case ArrowResize:
                                mUndoRedoModel->checkpoint( tr("Resize") );
                                handleResizeMotion( xWorld, yWorld );
                                break;

                        case CreateIdle:
                                break;

                        case CreateDrag:
                                switch (mCreateObjectType)
                                {
                                case Box:
                                case Ellipse:
                                case Image:
                                case Text:
                                case Barcode:
                                        mCreateObject->setPosition( min( xWorld, mCreateX0 ),
                                                                    min( yWorld, mCreateY0 ) );
                                        mCreateObject->setSize( max(xWorld,mCreateX0) - min(xWorld,mCreateX0),
                                                                max(yWorld,mCreateY0) - min(yWorld,mCreateY0) );

                                        break;
                                case Line:
                                        mCreateObject->setSize( xWorld - mCreateX0, yWorld - mCreateY0 );
                                        break;
                                default:
                                        qDebug() << "LabelEditor::mouseMoveEvent: Invalid creation mode. Should not happen!";
                                        break;
                                }
                                break;

                        default:
                                qDebug() << "LabelEditor::mouseMoveEvent: Invalid state. Should not happen!";
                                break;

                        }
                }
        }


        ///
        /// Mouse Button Release Event Handler
        ///
        void
        LabelEditor::mouseReleaseEvent( QMouseEvent* event )
        {
                if ( mModel )
                {
                        /*
                         * Transform to label coordinates
                         */
                        QTransform transform;

                        transform.scale( mScale, mScale );
                        transform.translate( mX0.pt(), mY0.pt() );

                        QPointF pWorld = transform.inverted().map( event->pos() );
                        model::Distance xWorld = model::Distance::pt( pWorld.x() );
                        model::Distance yWorld = model::Distance::pt( pWorld.y() );


                        if ( event->button() & Qt::LeftButton )
                        {
                                //
                                // LEFT BUTTON Release
                                //
                                switch (mState)
                                {

                                case ArrowResize:
                                        mState = IdleState;
                                        break;

                                case ArrowSelectRegion:
                                        mSelectRegionVisible = false;
                                        mSelectRegion.setX2( xWorld );
                                        mSelectRegion.setY2( yWorld );

                                        mModel->selectRegion( mSelectRegion );

                                        mState = IdleState;
                                        update();
                                        break;

                                case CreateDrag:
                                        if ( (fabs(mCreateObject->w()) < 4) && (fabs(mCreateObject->h()) < 4) )
                                        {
                                                switch (mCreateObjectType)
                                                {
                                                case Text:
                                                        mCreateObject->setSize( 72, 36 );
                                                        break;
                                                case Barcode:
                                                        mCreateObject->setSize( 72, 36 );
                                                        break;
                                                case Line:
                                                        mCreateObject->setSize( 72, 0 );
                                                        break;
                                                default:
                                                        mCreateObject->setSize( 72, 72 );
                                                        break;
                                                }
                                        }

                                        setCursor( Qt::ArrowCursor );
                                        mState = IdleState;
                                        break;

                                default:
                                        mState = IdleState;
                                        break;

                                }

                        }
                }
        }


        ///
        /// Leave Event Handler
        ///
        void
        LabelEditor::leaveEvent( QEvent* event )
        {
                if ( mModel )
                {
                        emit pointerExited();
                }
        }


        ///
        /// Handle resize motion
        ///
        void
        LabelEditor::handleResizeMotion( model::Distance xWorld,
                                         model::Distance yWorld )
        {
                QPointF p( xWorld.pt(), yWorld.pt() );

                /*
                 * Change point to object relative coordinates
                 */
                p -= QPointF( mResizeObject->x0().pt(), mResizeObject->y0().pt() );
                p = mResizeObject->matrix().inverted().map( p );

                /*
                 * Initialize origin and 2 corners in object relative coordinates.
                 */
                double x0 = 0.0;
                double y0 = 0.0;

                double x1 = 0.0;
                double y1 = 0.0;

                double x2 = mResizeObject->w().pt();
                double y2 = mResizeObject->h().pt();

                /*
                 * Calculate new size
                 */
                double w = 0.0;
                double h = 0.0;
                switch ( mResizeHandleLocation )
                {
                case model::Handle::NW:
                        w = std::max( x2 - p.x(), 0.0 );
                        h = std::max( y2 - p.y(), 0.0 );
                        break;
                case model::Handle::N:
                        w = x2 - x1;
                        h = std::max( y2 - p.y(), 0.0 );
                        break;
                case model::Handle::NE:
                        w = std::max( p.x() - x1, 0.0 );
                        h = std::max( y2 - p.y(), 0.0 );
                        break;
                case model::Handle::E:
                        w = std::max( p.x() - x1, 0.0 );
                        h = y2 - y1;
                        break;
                case model::Handle::SE:
                        w = std::max( p.x() - x1, 0.0 );
                        h = std::max( p.y() - y1, 0.0 );
                        break;
                case model::Handle::S:
                        w = x2 - x1;
                        h = std::max( p.y() - y1, 0.0 );
                        break;
                case model::Handle::SW:
                        w = std::max( x2 - p.x(), 0.0 );
                        h = std::max( p.y() - y1, 0.0 );
                        break;
                case model::Handle::W:
                        w = std::max( x2 - p.x(), 0.0 );
                        h = y2 - y1;
                        break;
                case model::Handle::P1:
                        x1 = p.x();
                        y1 = p.y();
                        w  = x2 - p.x();
                        h  = y2 - p.y();
                        x0 = x0 + x1;
                        y0 = y0 + y1;
                        break;
                case model::Handle::P2:
                        w  = p.x() - x1;
                        h  = p.y() - y1;
                        x0 = x0 + x1;
                        y0 = y0 + y1;
                        break;
                default:
                        qDebug() << "LabelEditor::handleResizeMotion: Invalid Handle Location. Should not happen!";
                }

                /*
                 * Set size
                 */
                if ( !(mResizeHandleLocation == model::Handle::P1) && !(mResizeHandleLocation == model::Handle::P2) )
                {
                        if ( mResizeHonorAspect )
                        {
                                switch ( mResizeHandleLocation )
                                {
                                case model::Handle::E:
                                case model::Handle::W:
                                        mResizeObject->setWHonorAspect( model::Distance::pt(w) );
                                        break;
                                case model::Handle::N:
                                case model::Handle::S:
                                        mResizeObject->setHHonorAspect( model::Distance::pt(h) );
                                        break;
                                default:
                                        mResizeObject->setSizeHonorAspect( model::Distance::pt(w),
                                                                           model::Distance::pt(h) );
                                        break;
                                }
                        }
                        else
                        {
                                mResizeObject->setSize( model::Distance::pt(w),
                                                        model::Distance::pt(h) );
                        }

                        /*
                         * Adjust origin, if needed.
                         */
                        switch ( mResizeHandleLocation )
                        {
                        case model::Handle::NW:
                                x0 += x2 - mResizeObject->w().pt();
                                y0 += y2 - mResizeObject->h().pt();
                                break;
                        case model::Handle::N:
                        case model::Handle::NE:
                                y0 += y2 - mResizeObject->h().pt();
                                break;
                        case model::Handle::W:
                        case model::Handle::SW:
                                x0 += x2 - mResizeObject->w().pt();
                                break;
                        default:
                                break;
                        }
                }
                else
                {
                        mResizeObject->setSize( model::Distance::pt(w),
                                                model::Distance::pt(h) );
                }

                /*
                 * Put new origin back into world coordinates and set.
                 */
                QPointF p0( x0, y0 );
                p0 = mResizeObject->matrix().map( p0 );
                p0 += QPointF( mResizeObject->x0().pt(), mResizeObject->y0().pt() );
                mResizeObject->setPosition( model::Distance::pt(p0.x()),
                                            model::Distance::pt(p0.y()) );
        }


        ///
        /// Key Press Event Handler
        void
        LabelEditor::keyPressEvent( QKeyEvent* event )
        {
                if ( mState == IdleState )
                {
                        switch (event->key())
                        {

                        case Qt::Key_Left:
                                mUndoRedoModel->checkpoint( tr("Move") );
                                mModel->moveSelection( -mStepSize, model::Distance(0) );
                                break;

                        case Qt::Key_Up:
                                mUndoRedoModel->checkpoint( tr("Move") );
                                mModel->moveSelection( model::Distance(0), -mStepSize );
                                break;

                        case Qt::Key_Right:
                                mUndoRedoModel->checkpoint( tr("Move") );
                                mModel->moveSelection( mStepSize, model::Distance(0) );
                                break;

                        case Qt::Key_Down:
                                mUndoRedoModel->checkpoint( tr("Move") );
                                mModel->moveSelection( model::Distance(0), mStepSize );
                                break;

                        case Qt::Key_Delete:
                                mUndoRedoModel->checkpoint( tr("Delete") );
                                mModel->deleteSelection();
                                setCursor( Qt::ArrowCursor );
                                break;

                        default:
                                QWidget::keyPressEvent( event );
                                break;

                        }
                }
                else
                {
                        QWidget::keyPressEvent( event );
                }
        }


        ///
        /// Paint Event Handler
        ///
        void
        LabelEditor::paintEvent( QPaintEvent* event )
        {
                if ( mModel )
                {
                        QPainter painter( this );

                        painter.setRenderHint( QPainter::Antialiasing, true );
                        painter.setRenderHint( QPainter::TextAntialiasing, true );
                        painter.setRenderHint( QPainter::SmoothPixmapTransform, true );

                        /* Fill background before any transformations */
                        painter.setBrush( QBrush( backgroundColor ) );
                        painter.setPen( Qt::NoPen );
                        painter.drawRect( rect() );

                        /* Transform. */
                        painter.scale( mScale, mScale );
                        painter.translate( mX0.pt(), mY0.pt() );

                        /* Now draw from the bottom layer up. */
                        drawBgLayer( &painter );
                        drawGridLayer( &painter );
                        drawMarkupLayer( &painter );
                        drawObjectsLayer( &painter );
                        drawFgLayer( &painter );
                        drawHighlightLayer( &painter );
                        drawSelectRegionLayer( &painter );
                }
        }


        //
        // Handle drag enter event
        //
        void LabelEditor::dragEnterEvent( QDragEnterEvent *event )
        {
                if ( event->mimeData()->hasUrls()   ||
                     event->mimeData()->hasImage()  ||
                     event->mimeData()->hasText() )
                {
                        event->acceptProposedAction();
                }
                else
                {
                        event->ignore();
                }
        }


        //
        // Handle drag move event
        //
        void LabelEditor::dragMoveEvent( QDragMoveEvent *event )
        {
                if ( event->mimeData()->hasUrls()   ||
                     event->mimeData()->hasImage()  ||
                     event->mimeData()->hasText() )
                {
                        event->acceptProposedAction();
                }
                else
                {
                        event->ignore();
                }
        }


        //
        // Handle drop event
        //
        void LabelEditor::dropEvent( QDropEvent *event )
        {
                /*
                 * Transform to label coordinates
                 */
                QTransform transform;

                transform.scale( mScale, mScale );
                transform.translate( mX0.pt(), mY0.pt() );

                QPointF pWorld = transform.inverted().map( event->position() );
                auto xWorld = model::Distance::pt( pWorld.x() );
                auto yWorld = model::Distance::pt( pWorld.y() );
                auto p = model::Point( xWorld, yWorld );

                if ( event->mimeData()->hasUrls() )
                {
                        mUndoRedoModel->checkpoint( tr("Drop") );
                        mModel->pasteAsUrls( event->mimeData(), p );
                        event->acceptProposedAction();
                }
                else if ( event->mimeData()->hasImage() )
                {
                        mUndoRedoModel->checkpoint( tr("Drop") );
                        mModel->pasteAsImage( event->mimeData(), p );
                        event->acceptProposedAction();
                }
                else if ( event->mimeData()->hasText() )
                {
                        mUndoRedoModel->checkpoint( tr("Drop") );
                        mModel->pasteAsText( event->mimeData(), p );
                        event->acceptProposedAction();
                }
                else
                {
                        event->ignore();
                }
        }


        ///
        /// Draw Background Layer
        ///
        void
        LabelEditor::drawBgLayer( QPainter* painter )
        {
                /*
                 * Draw shadow
                 */
                painter->save();

                painter->setBrush( QBrush( shadowColor ) );
                painter->setPen( Qt::NoPen );

                painter->translate( shadowOffsetPixels/mScale, shadowOffsetPixels/mScale );

                if ( mModel->rotate() )
                {
                        painter->rotate( -90 );
                        painter->translate( -mModel->frame()->w().pt(), 0 );
                }
                painter->drawPath( mModel->frame()->path() );

                painter->restore();


                /*
                 * Draw label
                 */
                painter->save();

                painter->setBrush( QBrush( labelColor ) );
                painter->setPen( Qt::NoPen );

                if ( mModel->rotate() )
                {
                        painter->rotate( -90 );
                        painter->translate( -mModel->frame()->w().pt(), 0 );
                }
                painter->drawPath( mModel->frame()->path() );

                painter->restore();
        }


        ///
        /// Draw Grid Layer
        ///
        void
        LabelEditor::drawGridLayer( QPainter* painter )
        {
                if ( mGridVisible )
                {
                        auto gridSpacing = model::Settings::gridSpacing();
                        auto gridOrigin  = model::Settings::gridOrigin();

                        bool isRectangular = dynamic_cast<const model::FrameRect*>( mModel->frame() );

                        model::Distance w = mModel->frame()->w();
                        model::Distance h = mModel->frame()->h();

                        // Set origin of grid.  For non-rectangular labels (e.g. round, cd, etc.),
                        // ignore the gridOrigin setting and always use the center of the label.
                        auto x0 = gridSpacing;
                        auto y0 = gridSpacing;
                        if ( gridOrigin == model::Settings::ORIGIN_CENTER || !isRectangular )
                        {
                                x0 = fmod( w/2, gridSpacing );
                                y0 = fmod( h/2, gridSpacing );
                        }

                        painter->save();
                        if ( mModel->rotate() )
                        {
                                painter->rotate( -90 );
                                painter->translate( -mModel->frame()->w().pt(), 0 );
                        }

                        painter->setClipPath( mModel->frame()->path() );

                        QPen pen( gridLineColor, gridLineWidthPixels );
                        pen.setCosmetic( true );
                        painter->setPen( pen );

                        for ( model::Distance x = x0; x < w; x += gridSpacing )
                        {
                                painter->drawLine( x.pt(), 0, x.pt(), h.pt() );
                        }

                        for ( model::Distance y = y0; y < h; y += gridSpacing )
                        {
                                painter->drawLine( 0, y.pt(), w.pt(), y.pt() );
                        }

                        painter->restore();
                }
        }


        ///
        /// Draw Markup Layer
        ///
        void
        LabelEditor::drawMarkupLayer( QPainter* painter )
        {
                if ( mMarkupVisible )
                {
                        painter->save();

                        QPen pen( markupLineColor, markupLineWidthPixels );
                        pen.setCosmetic( true );

                        painter->setBrush( Qt::NoBrush );
                        painter->setPen( pen );

                        if ( mModel->rotate() )
                        {
                                painter->rotate( -90 );
                                painter->translate( -mModel->frame()->w().pt(), 0 );
                        }

                        for( auto& markup : mModel->frame()->markups() )
                        {
                                painter->drawPath( markup->path( *mModel->frame() ) );
                        }

                        painter->restore();
                }
        }


        ///
        /// Draw Objects Layer
        ///
        void
        LabelEditor::drawObjectsLayer( QPainter* painter )
        {
                mModel->draw( painter, true, merge::NullRecord(), model::Variables() );
        }


        ///
        /// Draw Foreground Layer
        ///
        void
        LabelEditor::drawFgLayer( QPainter* painter )
        {
                /*
                 * Draw label outline
                 */
                painter->save();

                QPen pen( labelOutlineColor, labelOutlineWidthPixels );
                pen.setCosmetic( true );
                painter->setBrush( QBrush( Qt::NoBrush ) );
                painter->setPen( pen );

                if ( mModel->rotate() )
                {
                        painter->rotate( -90 );
                        painter->translate( -mModel->frame()->w().pt(), 0 );
                }
                painter->drawPath( mModel->frame()->path() );

                painter->restore();
        }


        ///
        /// Draw Highlight Layer
        ///
        void
        LabelEditor::drawHighlightLayer( QPainter* painter )
        {
                painter->save();

                for ( model::ModelObject* object : mModel->objectList() )
                {
                        if ( object->isSelected() )
                        {
                                object->drawSelectionHighlight( painter, mScale );
                        }
                }

                painter->restore();
        }


        ///
        /// Draw Select Region Layer
        ///
        void
        LabelEditor::drawSelectRegionLayer( QPainter* painter )
        {
                if ( mSelectRegionVisible )
                {
                        painter->save();

                        QPen pen( selectRegionOutlineColor, selectRegionOutlineWidthPixels );
                        pen.setCosmetic( true );
                        painter->setBrush( selectRegionFillColor );
                        painter->setPen( pen );

                        painter->drawRect( mSelectRegion.rect() );

                        painter->restore();
                }

        }


        ///
        /// Settings changed handler
        ///
        void LabelEditor::onSettingsChanged()
        {
                model::Units units = model::Settings::units();

                mStepSize = model::Distance( units.resolution(), units );

                update();
        }


        ///
        /// Model size changed handler
        ///
        void LabelEditor::onModelSizeChanged()
        {
                if (mZoomToFitFlag)
                {
                        double wPixels = mScrollArea->maximumViewportSize().width();
                        double hPixels = mScrollArea->maximumViewportSize().height();

                        double x_scale = ( wPixels - ZOOM_TO_FIT_PAD ) / mModel->w().pt();
                        double y_scale = ( hPixels - ZOOM_TO_FIT_PAD ) / mModel->h().pt();
                        double newZoom = qMin( x_scale, y_scale ) * model::PTS_PER_INCH / physicalDpiX();

                        // Limits
                        newZoom = qMin( newZoom, zoomLevels[0] );
                        newZoom = qMax( newZoom, zoomLevels[nZoomLevels-1] );

                        mZoom = newZoom;
                }

                /* Actual scale depends on DPI of display (assume DpiX == DpiY). */
                mScale = mZoom * physicalDpiX() / model::PTS_PER_INCH;

                setMinimumSize( mScale*mModel->w().pt() + ZOOM_TO_FIT_PAD,
                                mScale*mModel->h().pt() + ZOOM_TO_FIT_PAD );

                /* Adjust origin to center label in widget. */
                mX0 = (width()/mScale - mModel->w()) / 2;
                mY0 = (height()/mScale - mModel->h()) / 2;

                update();

                emit zoomChanged();
        }


} // namespace glabels
