/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2004-12-09
 * Description : image selection widget used by ratio crop tool.
 *
 * SPDX-FileCopyrightText: 2007      by Jaromir Malenko <malenko at email.cz>
 * SPDX-FileCopyrightText: 2008      by Roberto Castagnola <roberto dot castagnola at gmail dot com>
 * SPDX-FileCopyrightText: 2004-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * ============================================================ */

#define OPACITY  0.7
#define RCOL     0xAA
#define GCOL     0xAA
#define BCOL     0xAA

#define MINRANGE 0

// Golden number (1+sqrt(5))/2
#define PHI      1.61803398874989479F
// 1/PHI
#define INVPHI   0.61803398874989479F
// DIN A sqrt(2)
#define DINA     1.41421356237309504F

#include "ratiocropwidget.h"

// C++ includes

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>

// Qt includes

#include <QRegion>
#include <QPainter>
#include <QBrush>
#include <QPixmap>
#include <QImage>
#include <QPen>
#include <QPoint>
#include <QTimer>
#include <QSizePolicy>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QPaintEvent>

// Local includes

#include "digikam_debug.h"
#include "dimg.h"

namespace DigikamEditorRatioCropToolPlugin
{

class Q_DECL_HIDDEN RatioCropWidget::Private
{
public:

    enum ResizingMode
    {
        ResizingNone = 0,
        ResizingTopLeft,
        ResizingTopRight,
        ResizingBottomLeft,
        ResizingBottomRight
    };

public:

    Private() = default;

    // Golden guide types.

    bool        drawGoldenSection       = false;
    bool        drawGoldenSpiralSection = false;
    bool        drawGoldenSpiral        = false;
    bool        drawGoldenTriangle      = false;

    // Golden guide translations.

    bool        flipHorGoldenGuide      = false;
    bool        flipVerGoldenGuide      = false;

    bool        moving                  = false;
    bool        autoOrientation         = false;
    bool        preciseCrop             = false;

    bool        isDrawingSelection      = false;

    int         guideLinesType          = 0;
    int         guideSize               = 1;

    int         currentAspectRatioType  = 0;
    int         currentResizing         = ResizingNone;
    int         currentOrientation      = 0;

    float       currentWidthRatioValue  = 0.0;
    float       currentHeightRatioValue = 0.0;

    QPoint      lastPos;

    QRect       rect;
    QRect       image;                  // Real image dimension.
    QRect       regionSelection;        // Real size image selection.
    QRect       localRegionSelection;   // Local size selection.

    // Draggable local region selection corners.

    QRect       localTopLeftCorner;
    QRect       localBottomLeftCorner;
    QRect       localTopRightCorner;
    QRect       localBottomRightCorner;

    QPixmap*    pixmap                  = nullptr;
    QPixmap     grayOverLay;
    QPixmap     previewPixmap;

    QColor      guideColor;
    QColor      bgColor;

    DImg        preview;

    ImageIface* iface                   = nullptr;
};

RatioCropWidget::RatioCropWidget(int w, int h, QWidget* const parent)
    : QWidget(parent),
      d      (new Private)
{
    d->isDrawingSelection = true;
    d->bgColor            = palette().color(QPalette::Window);
    setup(w, h);
}

RatioCropWidget::RatioCropWidget(int w, int h, bool initDrawing, QWidget* const parent)
    : QWidget(parent),
      d      (new Private)
{
    d->isDrawingSelection = initDrawing;
    setup(w, h);
}

RatioCropWidget::~RatioCropWidget()
{
    delete d->iface;
    delete d->pixmap;
    delete d;
}

void RatioCropWidget::setup(int w, int h,
                                 int widthRatioValue, int heightRatioValue,
                                 int aspectRatio, int orient,
                                 int guideLinesType)
{
    setMinimumSize(w, h);
    setMouseTracking(true);
    setAttribute(Qt::WA_DeleteOnClose);

    d->currentAspectRatioType  = aspectRatio;
    d->currentWidthRatioValue  = widthRatioValue;
    d->currentHeightRatioValue = heightRatioValue;
    d->currentOrientation      = orient;
    d->guideLinesType          = guideLinesType;
    d->autoOrientation         = false;
    d->preciseCrop             = false;
    d->moving                  = true;
    reverseRatioValues();

    d->iface   = new ImageIface(QSize(w, h));
    d->preview = d->iface->preview();
    d->preview.setIccProfile(d->iface->original()->getIccProfile());
    d->preview.convertToEightBit();

    d->pixmap  = new QPixmap(w, h);
    d->image   = QRect(0, 0, d->iface->originalSize().width(), d->iface->originalSize().height());
    d->rect    = QRect((w-d->preview.width()) /2,
                       (h-d->preview.height())/2,
                       d->preview.width(), d->preview.height());

    updatePixmap();
    setGoldenGuideTypes(true, false, false, false, false, false);
}

ImageIface* RatioCropWidget::imageIface() const
{
    return d->iface;
}

void RatioCropWidget::resizeEvent(QResizeEvent* e)
{
    delete d->pixmap;

    int w      = e->size().width();
    int h      = e->size().height();
    d->preview = d->iface->setPreviewSize(QSize(w, h));
    d->preview.setIccProfile(d->iface->original()->getIccProfile());
    d->preview.convertToEightBit();

    d->pixmap  = new QPixmap(w, h);
    d->rect    = QRect((w-d->preview.width()) /2,
                       (h-d->preview.height())/2,
                       d->preview.width(), d->preview.height());

    // Drawing the gray overlay

    {
        DImg image = d->preview.copy();
        uchar* ptr = image.bits();
        uchar  r, g, b;

        int xlow  = d->rect.left();
        int xhigh = d->rect.right();
        int ylow  = d->rect.top();
        int yhigh = d->rect.bottom();

        for (int y = ylow ; y <= yhigh ; ++y)
        {
            for (int x = xlow ; x <= xhigh ; ++x)
            {
                b = ptr[0];
                g = ptr[1];
                r = ptr[2];

                r += (uchar)((RCOL - r) * OPACITY);
                g += (uchar)((GCOL - g) * OPACITY);
                b += (uchar)((BCOL - b) * OPACITY);

                ptr[0] = b;
                ptr[1] = g;
                ptr[2] = r;

                ptr+=4;
            }
        }

        d->grayOverLay   = image.convertToPixmap();
        d->previewPixmap = d->iface->convertToPixmap(d->preview);
    }

    updatePixmap();
}

int RatioCropWidget::getOriginalImageWidth() const
{
    return d->image.width();
}

int RatioCropWidget::getOriginalImageHeight() const
{
    return d->image.height();
}

QRect RatioCropWidget::getRegionSelection() const
{
    return d->regionSelection;
}

int RatioCropWidget::getMinWidthRange() const
{
    return MINRANGE;
}

int RatioCropWidget::getMinHeightRange() const
{
    return MINRANGE;
}

int RatioCropWidget::getMaxWidthRange() const
{
    int maxW = d->image.width() - d->regionSelection.left();

    if (d->currentAspectRatioType != RATIONONE)
    {
        // Compute max width taking aspect ratio into account

        int t = d->currentWidthRatioValue > d->currentHeightRatioValue ? 1 : 0;
        int h = d->image.height() - d->regionSelection.top();
        int w = (int)rint((h + t) * d->currentWidthRatioValue /
                          d->currentHeightRatioValue) - t;

        if (w < maxW)
        {
            maxW = w;
        }
    }

    // Return max width adjusted if a precise crop is wanted

    return computePreciseSize(maxW, (int)d->currentWidthRatioValue);
}

int RatioCropWidget::getMaxHeightRange() const
{
    int maxH = d->image.height() - d->regionSelection.top();

    if (d->currentAspectRatioType != RATIONONE)
    {
        // Compute max height taking aspect ratio into account

        int t = d->currentHeightRatioValue > d->currentWidthRatioValue ? 1 : 0;
        int w = d->image.width() - d->regionSelection.left();
        int h = (int)rint((w + t) * d->currentHeightRatioValue /
                          d->currentWidthRatioValue) - t;

        if (h < maxH)
        {
            maxH = h;
        }
    }

    // Return max height adjusted if a precise crop is wanted

    return computePreciseSize(maxH, (int)d->currentHeightRatioValue);
}

int RatioCropWidget::getWidthStep() const
{
    if (d->preciseCrop && preciseCropAvailable())
    {
        return (int)d->currentWidthRatioValue;
    }
    else
    {
        return 1;
    }
}

int RatioCropWidget::getHeightStep() const
{
    if (d->preciseCrop && preciseCropAvailable())
    {
        return (int)d->currentHeightRatioValue;
    }
    else
    {
        return 1;
    }
}

/**
 * Draw a new centered selection with half width (if orientation = Landscape)
 * or with half height (if orientation = Portrait)
 */
void RatioCropWidget::resetSelection()
{
    d->regionSelection.setWidth(d->image.width()/2);
    d->regionSelection.setHeight(d->image.height()/2);
    applyAspectRatio(d->currentOrientation == Portrait, false);

    setCenterSelection(CenterImage);
}

void RatioCropWidget::setCenterSelection(int centerType)
{
    // Adjust selection size if bigger than real image

    if (d->regionSelection.height() > d->image.height())
    {
        d->regionSelection.setHeight(d->image.height());
        applyAspectRatio(true, false);
    }

    if (d->regionSelection.width() > d->image.width())
    {
        d->regionSelection.setWidth(d->image.width());
        applyAspectRatio(false, false);
    }

    // Set center point for selection

    QPoint center = d->image.center();

    switch (centerType)
    {
        case CenterWidth:
        {
            center.setY(d->regionSelection.center().y());
            break;
        }

        case CenterHeight:
        {
            center.setX(d->regionSelection.center().x());
            break;
        }
    }

    d->regionSelection.moveCenter(center);

    // Repaint

    updatePixmap();
    update();
    regionSelectionChanged();
}

/**
 * Draw a new centered selection with max size
 */
void RatioCropWidget::maxAspectSelection()
{
    d->regionSelection.setWidth(d->image.width());
    d->regionSelection.setHeight(d->image.height());

    if (d->currentAspectRatioType != RATIONONE)
    {
        applyAspectRatio(d->currentOrientation == Portrait, false);
    }

    setCenterSelection(CenterImage);
}

void RatioCropWidget::setGoldenGuideTypes(bool drawGoldenSection,  bool drawGoldenSpiralSection,
                                          bool drawGoldenSpiral,   bool drawGoldenTriangle,
                                          bool flipHorGoldenGuide, bool flipVerGoldenGuide)
{
    d->drawGoldenSection       = drawGoldenSection;
    d->drawGoldenSpiralSection = drawGoldenSpiralSection;
    d->drawGoldenSpiral        = drawGoldenSpiral;
    d->drawGoldenTriangle      = drawGoldenTriangle;
    d->flipHorGoldenGuide      = flipHorGoldenGuide;
    d->flipVerGoldenGuide      = flipVerGoldenGuide;
}

void RatioCropWidget::setBackgroundColor(const QColor& bg)
{
    d->bgColor = bg;
    updatePixmap();
    update();
}

void RatioCropWidget::slotGuideLines(int guideLinesType)
{
    d->guideLinesType = guideLinesType;
    updatePixmap();
    update();
}

void RatioCropWidget::slotChangeGuideColor(const QColor& color)
{
    d->guideColor = color;
    updatePixmap();
    update();
}

void RatioCropWidget::slotChangeGuideSize(int size)
{
    d->guideSize = size;
    updatePixmap();
    update();
}

void RatioCropWidget::setSelectionOrientation(int orient)
{
    d->currentOrientation = orient;
    reverseRatioValues();
    applyAspectRatio(true);

    Q_EMIT signalSelectionOrientationChanged(d->currentOrientation);
}

void RatioCropWidget::setSelectionAspectRatioType(int aspectRatioType)
{
    d->currentAspectRatioType = aspectRatioType;

    // Set ratio values

    switch (aspectRatioType)
    {
        case RATIO01X01:
        {
            d->currentHeightRatioValue = 1.0;
            d->currentWidthRatioValue  = 1.0;
            break;
        }

        case RATIO02x01:
        {
            d->currentHeightRatioValue = 2.0;
            d->currentWidthRatioValue  = 1.0;
            break;
        }

        case RATIO02x03:
        {
            d->currentHeightRatioValue = 2.0;
            d->currentWidthRatioValue  = 3.0;
            break;
        }

        case RATIO03X01:
        {
            d->currentHeightRatioValue = 3.0;
            d->currentWidthRatioValue  = 1.0;
            break;
        }

        case RATIO03X04:
        {
            d->currentHeightRatioValue = 3.0;
            d->currentWidthRatioValue  = 4.0;
            break;
        }

        case RATIO04X01:
        {
            d->currentHeightRatioValue = 4.0;
            d->currentWidthRatioValue  = 1.0;
            break;
        }

        case RATIO04X05:
        {
            d->currentHeightRatioValue = 4.0;
            d->currentWidthRatioValue  = 5.0;
            break;
        }

        case RATIO05x07:
        {
            d->currentHeightRatioValue = 5.0;
            d->currentWidthRatioValue  = 7.0;
            break;
        }

        case RATIO07x10:
        {
            d->currentHeightRatioValue = 7.0;
            d->currentWidthRatioValue  = 10.0;
            break;
        }

        case RATIO08x05:
        {
            d->currentHeightRatioValue = 8.0;
            d->currentWidthRatioValue  = 5.0;
            break;
        }

        case RATIO16x09:
        {
            d->currentHeightRatioValue = 16.0;
            d->currentWidthRatioValue  = 9.0;
            break;
        }

        case RATIODINA0:
        {
            d->currentHeightRatioValue = 1.0;
            d->currentWidthRatioValue  = DINA;
            break;
        }

        case RATIOGOLDEN:
        {
            d->currentHeightRatioValue = 1.0;
            d->currentWidthRatioValue  = PHI;
            break;
        }

        case RATIOCURRENT:
        {
            d->currentHeightRatioValue = d->image.height();
            d->currentWidthRatioValue  = d->image.width();
            break;
        }
    }

    reverseRatioValues();
    applyAspectRatio(false);
}

void RatioCropWidget::setSelectionAspectRatioValue(int widthRatioValue, int heightRatioValue)
{
    int gdc = widthRatioValue;

    // Compute greatest common divisor using Euclidean algorithm

    for (int tmp, mod = heightRatioValue; mod != 0; mod = tmp % mod)
    {
        tmp = gdc;
        gdc = mod;
    }

    d->currentWidthRatioValue  = widthRatioValue  / gdc;
    d->currentHeightRatioValue = heightRatioValue / gdc;
    d->currentAspectRatioType  = RATIOCUSTOM;

    // Fix orientation

    if (d->autoOrientation)
    {
        if      ((heightRatioValue > widthRatioValue) &&
                 (d->currentOrientation == Landscape))
        {
            d->currentOrientation = Portrait;

            Q_EMIT signalSelectionOrientationChanged(d->currentOrientation);
        }
        else if ((widthRatioValue > heightRatioValue) &&
                 (d->currentOrientation == Portrait))
        {
            d->currentOrientation = Landscape;

            Q_EMIT signalSelectionOrientationChanged(d->currentOrientation);
        }
    }
    else
    {
        reverseRatioValues();
    }

    applyAspectRatio(false);
}

void RatioCropWidget::reverseRatioValues()
{
    // Reverse ratio values if needed

    if (
        ((d->currentWidthRatioValue > d->currentHeightRatioValue)  &&
         (d->currentOrientation == Portrait))                      ||
         ((d->currentHeightRatioValue > d->currentWidthRatioValue) &&
          (d->currentOrientation == Landscape))
       )
    {
        float tmp                  = d->currentWidthRatioValue;
        d->currentWidthRatioValue  = d->currentHeightRatioValue;
        d->currentHeightRatioValue = tmp;
    }
}

bool RatioCropWidget::preciseCropAvailable() const
{
    // Define when precise crop feature can be used
    // No needed when aspect ratio is 1:1

    switch (d->currentAspectRatioType)
    {
        case RATIONONE:
        case RATIO01X01:
        case RATIODINA0:
        case RATIOGOLDEN:
        {
            return false;
        }

        case RATIOCUSTOM:
        {
            return (d->currentWidthRatioValue != d->currentHeightRatioValue);
        }

        default:
        {
            return true;
        }
    }
}

void RatioCropWidget::setPreciseCrop(bool precise)
{
    d->preciseCrop = precise;
    applyAspectRatio(false, true);
    regionSelectionChanged();
}

void RatioCropWidget::setAutoOrientation(bool orientation)
{
    d->autoOrientation = orientation;
}

void RatioCropWidget::setSelectionX(int x)
{
    d->regionSelection.moveLeft(x);
    regionSelectionMoved();
}

void RatioCropWidget::setSelectionY(int y)
{
    d->regionSelection.moveTop(y);
    regionSelectionMoved();
}

void RatioCropWidget::setSelectionWidth(int w)
{
    d->regionSelection.setWidth(w);
    applyAspectRatio(false, true);

    regionSelectionChanged();
}

void RatioCropWidget::setSelectionHeight(int h)
{
    d->regionSelection.setHeight(h);
    applyAspectRatio(true, true);

    regionSelectionChanged();
}

QPoint RatioCropWidget::convertPoint(const QPoint& pm, bool localToReal) const
{
    return convertPoint(pm.x(), pm.y(), localToReal);
}

QPoint RatioCropWidget::convertPoint(int x, int y, bool localToReal) const
{
    int pmX, pmY;

    if (localToReal)
    {
        pmX = (int)((x - d->rect.left()) * (float)d->image.width() /
                    (float)d->preview.width());

        pmY = (int)((y - d->rect.top()) * (float)d->image.height() /
                    (float)d->preview.height());
    }
    else
    {
        pmX = (int)(d->rect.left() + (x * (float)d->preview.width() /
                                      (float)d->image.width()));

        pmY = (int)(d->rect.top() + (y * (float)d->preview.height() /
                                     (float)d->image.height()));
    }

    return QPoint(pmX, pmY);
}

int RatioCropWidget::computePreciseSize(int size, int step) const
{
    // Adjust size if precise crop is wanted

    if (d->preciseCrop && preciseCropAvailable())
    {
        size = int(size / step) * step;
    }

    return size;
}

void RatioCropWidget::applyAspectRatio(bool useHeight, bool repaintWidget)
{
    // Save selection area for re-adjustment after changing width and height.

    QRect oldRegionSelection = d->regionSelection;

    if (!useHeight)  // Width changed.
    {
        int w = computePreciseSize(d->regionSelection.width(),
                                   (int)d->currentWidthRatioValue);

        d->regionSelection.setWidth(w);

        switch (d->currentAspectRatioType)
        {
            case RATIONONE:
            {
                break;
            }

            default:
            {
                d->regionSelection.setHeight((int)
                                             rint(w * d->currentHeightRatioValue /
                                                  d->currentWidthRatioValue));
                break;
            }
        }
    }
    else      // Height changed.
    {
        int h = computePreciseSize(d->regionSelection.height(),
                                   (int)d->currentHeightRatioValue);

        d->regionSelection.setHeight(h);

        switch (d->currentAspectRatioType)
        {
            case RATIONONE:
            {
                break;
            }

            default:
            {
                d->regionSelection.setWidth((int)
                                            rint(h * d->currentWidthRatioValue /
                                                 d->currentHeightRatioValue));
                break;
            }
        }
    }

    // If we change selection size by a corner, re-adjust the opposite corner position.

    switch (d->currentResizing)
    {
        case Private::ResizingTopLeft:
        {
            d->regionSelection.moveBottomRight(oldRegionSelection.bottomRight());
            break;
        }

        case Private::ResizingTopRight:
        {
            d->regionSelection.moveBottomLeft(oldRegionSelection.bottomLeft());
            break;
        }

        case Private::ResizingBottomLeft:
        {
            d->regionSelection.moveTopRight(oldRegionSelection.topRight());
            break;
        }

        case Private::ResizingBottomRight:
        {
            d->regionSelection.moveTopLeft(oldRegionSelection.topLeft());
            break;
        }
    }

    if (repaintWidget)
    {
        updatePixmap();
        update();
    }
}

void RatioCropWidget::normalizeRegion()
{
    // Perform normalization of selection area.

    if (d->regionSelection.left() < d->image.left())
    {
        d->regionSelection.moveLeft(d->image.left());
    }

    if (d->regionSelection.top() < d->image.top())
    {
        d->regionSelection.moveTop(d->image.top());
    }

    if (d->regionSelection.right() > d->image.right())
    {
        d->regionSelection.moveRight(d->image.right());
    }

    if (d->regionSelection.bottom() > d->image.bottom())
    {
        d->regionSelection.moveBottom(d->image.bottom());
    }
}

void RatioCropWidget::regionSelectionMoved()
{
    normalizeRegion();

    updatePixmap();
    update();

    Q_EMIT signalSelectionMoved(d->regionSelection);
}

void RatioCropWidget::regionSelectionChanged()
{
    // Compute the intersection of selection region and image region

    QRect cut = d->regionSelection & d->image;

    // Adjust selection size if it was cropped

    if (d->regionSelection.width() > cut.width())
    {
        d->regionSelection = cut;
        applyAspectRatio(false);
    }

    if (d->regionSelection.height() > cut.height())
    {
        d->regionSelection = cut;
        applyAspectRatio(true);
    }

    Q_EMIT signalSelectionChanged(d->regionSelection);
}

void RatioCropWidget::drawRulesOfThirds(QPainter& p, const int& xThird, const int& yThird)
{

    p.drawLine(d->localRegionSelection.left() + xThird,   d->localRegionSelection.top(),
               d->localRegionSelection.left() + xThird,   d->localRegionSelection.bottom());
    p.drawLine(d->localRegionSelection.left() + 2*xThird, d->localRegionSelection.top(),
               d->localRegionSelection.left() + 2*xThird, d->localRegionSelection.bottom());

    p.drawLine(d->localRegionSelection.left(),  d->localRegionSelection.top() + yThird,
               d->localRegionSelection.right(), d->localRegionSelection.top() + yThird);
    p.drawLine(d->localRegionSelection.left(),  d->localRegionSelection.top() + 2*yThird,
               d->localRegionSelection.right(), d->localRegionSelection.top() + 2*yThird);

}

void RatioCropWidget::drawRulesOfCenter(QPainter& p, const int& xHalf, const int& yHalf)
{
    p.drawLine(d->localRegionSelection.left() + xHalf, d->localRegionSelection.top(),
               d->localRegionSelection.left() + xHalf, d->localRegionSelection.bottom());

    p.drawLine(d->localRegionSelection.left(),  d->localRegionSelection.top() + yHalf,
               d->localRegionSelection.right(), d->localRegionSelection.top() + yHalf);
}

void RatioCropWidget::drawDiagonalMethod(QPainter& p, const int& w, const int& h)
{
    p.setRenderHint(QPainter::Antialiasing);

    if (w > h)
    {
        p.drawLine(0, 0, h, h);
        p.drawLine(0, h, h, 0);
        p.drawLine(w-h, 0, w, h);
        p.drawLine(w-h, h, w, 0);
    }
    else
    {
        p.drawLine(0, 0, w, w);
        p.drawLine(0, w, w, 0);
        p.drawLine(0, h-w, w, h);
        p.drawLine(0, h, w, h-w);
    }
}

void RatioCropWidget::drawHarmoniousTriangles(QPainter& p, const int& dst)
{
    p.setRenderHint(QPainter::Antialiasing);

    p.drawLine(-d->localRegionSelection.width()/2, -d->localRegionSelection.height()/2,
                d->localRegionSelection.width()/2,  d->localRegionSelection.height()/2);

    p.drawLine(-d->localRegionSelection.width()/2 + dst, -d->localRegionSelection.height()/2,
               -d->localRegionSelection.width()/2,        d->localRegionSelection.height()/2);

    p.drawLine(d->localRegionSelection.width()/2,       -d->localRegionSelection.height()/2,
               d->localRegionSelection.width()/2 - dst,  d->localRegionSelection.height()/2);
}

void RatioCropWidget::drawGoldenMean(QPainter& p, const QRect& R1,
                                          const QRect& R2, const QRect& R3, const QRect& R4,
                                          const QRect& R5, const QRect& R6, const QRect& R7)
{
    p.setRenderHint(QPainter::Antialiasing);

    // Drawing Golden sections.

    if (d->drawGoldenSection)
    {
        // horizontal lines:

        p.drawLine(R1.left(), R2.top(),
                   R2.right(), R2.top());

        p.drawLine(R1.left(), R1.top() + R2.height(),
                   R2.right(), R1.top() + R2.height());

        // vertical lines:

        p.drawLine(R1.right(), R1.top(),
                   R1.right(), R1.bottom());

        p.drawLine(R1.left()+R2.width(), R1.top(),
                   R1.left()+R2.width(), R1.bottom());
    }

    // Drawing Golden triangle guides.

    if (d->drawGoldenTriangle)
    {
        p.drawLine(R1.left(),  R1.bottom(),
                   R2.right(), R1.top());

        p.drawLine(R1.left(), R1.top(),
                   R2.right() - R1.width(), R1.bottom());

        p.drawLine(R1.left() + R1.width(), R1.top(),
                   R2.right(), R1.bottom());
    }

    // Drawing Golden spiral sections.

    if (d->drawGoldenSpiralSection)
    {
        p.drawLine(R1.topRight(),   R1.bottomRight());
        p.drawLine(R2.topLeft(),    R2.topRight());
        p.drawLine(R3.topLeft(),    R3.bottomLeft());
        p.drawLine(R4.bottomLeft(), R4.bottomRight());
        p.drawLine(R5.topRight(),   R5.bottomRight());
        p.drawLine(R6.topLeft(),    R6.topRight());
        p.drawLine(R7.topLeft(),    R7.bottomLeft());
    }

    // Drawing Golden Spiral.

    if (d->drawGoldenSpiral)
    {
        p.drawArc(R1.left(),
                  R1.top() - R1.height(),
                  2*R1.width(), 2*R1.height(),
                  180*16, 90*16);

        p.drawArc(R2.right()  - 2*R2.width(),
                  R1.bottom() - 2*R2.height(),
                  2*R2.width(), 2*R2.height(),
                  270*16, 90*16);

        p.drawArc(R2.right() - 2*R3.width(),
                  R3.top(),
                  2*R3.width(), 2*R3.height(),
                  0, 90*16);

        p.drawArc(R4.left(),
                  R4.top(),
                  2*R4.width(), 2*R4.height(),
                  90*16, 90*16);

        p.drawArc(R5.left(),
                  R5.top() - R5.height(),
                  2*R5.width(), 2*R5.height(),
                  180*16, 90*16);

        p.drawArc(R6.left() - R6.width(),
                  R6.top()  - R6.height(),
                  2*R6.width(), 2*R6.height(),
                  270*16, 90*16);

        p.drawArc(R7.left() - R7.width(),
                  R7.top(),
                  2*R7.width(), 2*R7.height(),
                  0, 90*16);
    }
}

void RatioCropWidget::updatePixmap()
{
    // Updated local selection region.

    d->localRegionSelection.setTopLeft(convertPoint(d->regionSelection.topLeft(), false));
    d->localRegionSelection.setBottomRight(convertPoint(d->regionSelection.bottomRight(), false));

    // Updated dragging corners region.

    d->localTopLeftCorner.setRect(d->localRegionSelection.left(),          d->localRegionSelection.top(),        8, 8);
    d->localBottomLeftCorner.setRect(d->localRegionSelection.left(),       d->localRegionSelection.bottom() - 7, 8, 8);
    d->localTopRightCorner.setRect(d->localRegionSelection.right() - 7,    d->localRegionSelection.top(),        8, 8);
    d->localBottomRightCorner.setRect(d->localRegionSelection.right() - 7, d->localRegionSelection.bottom() - 7, 8, 8);

    // Drawing background.

    d->pixmap->fill(d->bgColor);

    if (d->preview.isNull())
    {
        return;
    }

    int sx = d->localRegionSelection.left() - d->rect.left();
    int sy = d->localRegionSelection.top()  - d->rect.top();
    int dw = d->localRegionSelection.width();
    int dh = d->localRegionSelection.height();

    QPainter p(d->pixmap);
    p.drawPixmap(d->rect.x(), d->rect.y(), d->grayOverLay);

    // Stop here if no selection to draw

    if (d->regionSelection.isEmpty() || !d->isDrawingSelection)
    {
        return;
    }

    // Now draw the image.

    p.drawPixmap(d->localRegionSelection.left(), d->localRegionSelection.top(), d->previewPixmap,
                 sx, sy, dw, dh);

    // Drawing selection borders.

    p.setPen(QPen(QColor(250, 250, 255), 1, Qt::SolidLine));
    p.drawRect(d->localRegionSelection);

    // Drawing selection corners.

    p.drawRect(d->localTopLeftCorner);
    p.drawRect(d->localBottomLeftCorner);
    p.drawRect(d->localTopRightCorner);
    p.drawRect(d->localBottomRightCorner);

    // Drawing guide lines.

    // Constraint drawing only on local selection region.
    // This is needed because arcs and incurved lines can draw
    // outside a little of local selection region.

    p.setClipping(true);
    p.setClipRect(d->localRegionSelection);

    switch (d->guideLinesType)
    {
        case RulesOfThirds:
        {
            int xThird = d->localRegionSelection.width()  / 3;
            int yThird = d->localRegionSelection.height() / 3;

            p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
            drawRulesOfThirds(p, xThird, yThird);

            p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
            drawRulesOfThirds(p, xThird, yThird);
            break;
        }

        case DiagonalMethod:
        {
            // Move coordinates to top, left

            p.translate(d->localRegionSelection.topLeft().x(), d->localRegionSelection.topLeft().y());

            int w = d->localRegionSelection.width();
            int h = d->localRegionSelection.height();

            p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
            drawDiagonalMethod(p, w, h);

            p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
            drawDiagonalMethod(p, w, h);
            break;
        }

        case HarmoniousTriangles:
        {
            // Move coordinates to local center selection.

            p.translate(d->localRegionSelection.center().x(), d->localRegionSelection.center().y());

            // Flip horizontal.

            if (d->flipHorGoldenGuide)
            {
                p.scale(-1, 1);
            }

            // Flip vertical.

            if (d->flipVerGoldenGuide)
            {
                p.scale(1, -1);
            }

            float w = (float)d->localRegionSelection.width();
            float h = (float)d->localRegionSelection.height();
            int dst = (int)((h*cos(atan(w/h)) / (cos(atan(h/w)))));

            p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
            drawHarmoniousTriangles(p, dst);

            p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
            drawHarmoniousTriangles(p, dst);
            break;
        }

        case GoldenMean:
        {
            // Move coordinates to local center selection.

            p.translate(d->localRegionSelection.center().x(), d->localRegionSelection.center().y());

            // Flip horizontal.

            if (d->flipHorGoldenGuide)
            {
                p.scale(-1, 1);
            }

            // Flip vertical.

            if (d->flipVerGoldenGuide)
            {
                p.scale(1, -1);
            }

            int w   = d->localRegionSelection.width();
            int h   = d->localRegionSelection.height();

            // lengths for the golden mean and half the sizes of the region:

            int w_g = (int)(w*INVPHI);
            int h_g = (int)(h*INVPHI);
            int w_2 = w/2;
            int h_2 = h/2;

            QRect R1(-w_2, -h_2, w_g, h);

            // w - 2*w_2 corrects for one-pixel difference
            // so that R2.right() is really at the right end of the region

            QRect R2(w_g-w_2, h_2-h_g, w-w_g+1-(w - 2*w_2), h_g);

            QRect R3((int)(w_2 - R2.width()*INVPHI), -h_2,
                     (int)(R2.width()*INVPHI), h - R2.height());
            QRect R4(R2.x(), R1.y(), R3.x() - R2.x(),
                     (int)(R3.height()*INVPHI));
            QRect R5(R4.x(), R4.bottom(), (int)(R4.width()*INVPHI),
                     R3.height() - R4.height());
            QRect R6(R5.x() + R5.width(), R5.bottom() - (int)(R5.height()*INVPHI),
                     R3.x() - R5.right(), (int)(R5.height()*INVPHI));
            QRect R7(R6.right() - (int)(R6.width()*INVPHI), R4.bottom(),
                     (int)(R6.width()*INVPHI), R5.height() - R6.height());

            p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
            drawGoldenMean(p, R1, R2, R3, R4, R5, R6, R7);

            p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
            drawGoldenMean(p, R1, R2, R3, R4, R5, R6, R7);

            break;
        }

        case CenterLines:
        {
            int xHalf = d->localRegionSelection.width()  / 2;
            int yHalf = d->localRegionSelection.height() / 2;

            p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
            drawRulesOfCenter(p, xHalf, yHalf);

            p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
            drawRulesOfCenter(p, xHalf, yHalf);
            break;
        }
    }

    p.setClipping(false);

    p.end();
}

void RatioCropWidget::paintEvent(QPaintEvent*)
{
    QPainter p(this);
    p.drawPixmap(0, 0, *d->pixmap);
    p.end();
}

QPoint RatioCropWidget::opposite() const
{
    QPoint opp;

    switch (d->currentResizing)
    {
        case Private::ResizingTopRight:
        {
            opp = d->regionSelection.bottomLeft();
            break;
        }

        case Private::ResizingBottomLeft:
        {
            opp = d->regionSelection.topRight();
            break;
        }

        case Private::ResizingBottomRight:
        {
            opp = d->regionSelection.topLeft();
            break;
        }

        case Private::ResizingTopLeft:
        default:
        {
            opp = d->regionSelection.bottomRight();
            break;
        }
    }

    return opp;
}

float RatioCropWidget::distance(const QPoint& a, const QPoint& b) const
{
    return sqrt(pow(a.x() - b.x(), 2) + pow(a.y() - b.y(), 2));
}

void RatioCropWidget::setCursorResizing()
{
    switch (d->currentResizing)
    {
        case Private::ResizingTopLeft:
        {
            setCursor(Qt::SizeFDiagCursor);
            break;
        }

        case Private::ResizingTopRight:
        {
            setCursor(Qt::SizeBDiagCursor);
            break;
        }

        case Private::ResizingBottomLeft:
        {
            setCursor(Qt::SizeBDiagCursor);
            break;
        }

        case Private::ResizingBottomRight:
        {
            setCursor(Qt::SizeFDiagCursor);
            break;
        }
    }
}

void RatioCropWidget::placeSelection(const QPoint& pm, bool symmetric, const QPoint& center)
{
    // Set orientation

    if (d->autoOrientation)
    {
        QPoint rel = pm - opposite();

        if (abs(rel.x()) > abs(rel.y()))
        {
            if (d->currentOrientation == Portrait)
            {
                d->currentOrientation = Landscape;
                reverseRatioValues();

                Q_EMIT signalSelectionOrientationChanged(d->currentOrientation);
            }
        }
        else
        {
            if (d->currentOrientation == Landscape)
            {
                d->currentOrientation = Portrait;
                reverseRatioValues();

                Q_EMIT signalSelectionOrientationChanged(d->currentOrientation);
            }
        }
    }

    // Place the corner at the mouse
    // If a symmetric selection is wanted, place opposite corner to
    // the center, double selection size and move it to old center after
    // computing aspect ratio.

    switch (d->currentResizing)
    {
        case Private::ResizingTopLeft:
        {
            // Place corners to the proper position

            d->regionSelection.setTopLeft(pm);

            if (symmetric)
            {
                d->regionSelection.setBottomRight(center);
            }

            break;
        }

        case Private::ResizingTopRight:
        {
            d->regionSelection.setTopRight(pm);

            if (symmetric)
            {
                d->regionSelection.setBottomLeft(center);
            }

            break;
        }

        case Private::ResizingBottomLeft:
        {
            d->regionSelection.setBottomLeft(pm);

            if (symmetric)
            {
                d->regionSelection.setTopRight(center);
            }

            break;
        }

        case Private::ResizingBottomRight:
        {
            d->regionSelection.setBottomRight(pm);

            if (symmetric)
            {
                d->regionSelection.setTopLeft(center);
            }

            break;
        }
    }

    if (symmetric)
    {
        d->regionSelection.setSize(d->regionSelection.size()*2);
    }

    applyAspectRatio(d->currentOrientation == Portrait, false);

    if (symmetric)
    {
        d->regionSelection.moveCenter(center);
    }

    // Repaint

    updatePixmap();
    update();
}

void RatioCropWidget::mousePressEvent(QMouseEvent* e)
{
    if (e->button() == Qt::LeftButton)
    {

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))

        QPoint pm        = QPoint(e->position().toPoint().x(), e->position().toPoint().y());

#else

        QPoint pm        = QPoint(e->x(), e->y());

#endif

        QPoint pmVirtual = convertPoint(pm);
        d->moving        = false;

        if ((e->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier)
        {
            bool symmetric  = (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier;
            QPoint center   = d->regionSelection.center();

            // Find the closest corner

            const QPoint points[] =
                              {
                                d->regionSelection.topLeft(),
                                d->regionSelection.topRight(),
                                d->regionSelection.bottomLeft(),
                                d->regionSelection.bottomRight()
                              };

            const int resizings[] =
                              {
                                Private::ResizingTopLeft,
                                Private::ResizingTopRight,
                                Private::ResizingBottomLeft,
                                Private::ResizingBottomRight
                              };

            float dist      = -1.0f;
            float dist2     =  0.0f;

            for (int i = 0 ; i < 4 ; ++i)
            {
                QPoint point = points[i];
                dist2        = distance(pmVirtual, point);

                if ((dist2 < dist) || (d->currentResizing == Private::ResizingNone))
                {
                    dist               = dist2;
                    d->currentResizing = resizings[i];
                }
            }

            setCursorResizing();

            placeSelection(pmVirtual, symmetric, center);
        }
        else
        {
            if      (d->localTopLeftCorner.contains(pm))
            {
                d->currentResizing = Private::ResizingTopLeft;
            }
            else if (d->localTopRightCorner.contains(pm))
            {
                d->currentResizing = Private::ResizingTopRight;
            }
            else if (d->localBottomLeftCorner.contains(pm))
            {
                d->currentResizing = Private::ResizingBottomLeft;
            }
            else if (d->localBottomRightCorner.contains(pm))
            {
                d->currentResizing = Private::ResizingBottomRight;
            }
            else
            {
                d->lastPos = pmVirtual;
                setCursor(Qt::SizeAllCursor);

                if (d->regionSelection.contains(pmVirtual))
                {
                    d->moving = true;
                }
                else
                {
                    d->regionSelection.moveCenter(pmVirtual);
                    normalizeRegion();
                    updatePixmap();
                    update();
                }
            }
        }
    }
}

void RatioCropWidget::mouseReleaseEvent(QMouseEvent*)
{
    if      (d->currentResizing != Private::ResizingNone)
    {
        setCursor(Qt::ArrowCursor);
        regionSelectionChanged();
        d->currentResizing = Private::ResizingNone;
    }
    else if (d->regionSelection.contains(d->lastPos))
    {
        setCursor(Qt::SizeAllCursor);
        regionSelectionMoved();
    }
    else
    {
        setCursor(Qt::ArrowCursor);
        regionSelectionMoved();
    }
}

void RatioCropWidget::mouseMoveEvent(QMouseEvent* e)
{
    if (e->buttons() & Qt::LeftButton)
    {
        if (d->moving)
        {
            setCursor(Qt::SizeAllCursor);

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))

            QPoint newPos = convertPoint(e->position().toPoint().x(), e->position().toPoint().y());

#else

            QPoint newPos = convertPoint(e->x(), e->y());

#endif

            d->regionSelection.translate(newPos.x() - d->lastPos.x(),
                                         newPos.y() - d->lastPos.y());

            d->lastPos = newPos;

            normalizeRegion();

            updatePixmap();
            update();
        }
        else
        {

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))

            QPoint pmVirtual = convertPoint(e->position().toPoint().x(), e->position().toPoint().y());

#else

            QPoint pmVirtual = convertPoint(e->x(), e->y());

#endif

            if (d->currentResizing == Private::ResizingNone)
            {
                d->regionSelection.setTopLeft(pmVirtual);
                d->regionSelection.setBottomRight(pmVirtual);
                d->currentResizing = Private::ResizingTopLeft; // set to anything
            }

            QPoint center  = d->regionSelection.center();
            bool symmetric = (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier;

            // Change resizing mode

            QPoint opp = symmetric ? center : opposite();
            QPoint dir = pmVirtual - opp;

            if      ((dir.x() > 0) && (dir.y() > 0) && (d->currentResizing != Private::ResizingBottomRight))
            {
                d->currentResizing = Private::ResizingBottomRight;
                d->regionSelection.setTopLeft(opp);
                setCursor(Qt::SizeFDiagCursor);
            }
            else if ((dir.x() > 0) && (dir.y() < 0) && (d->currentResizing != Private::ResizingTopRight))
            {
                d->currentResizing = Private::ResizingTopRight;
                d->regionSelection.setBottomLeft(opp);
                setCursor(Qt::SizeBDiagCursor);
            }
            else if ((dir.x() < 0) && (dir.y() > 0) && (d->currentResizing != Private::ResizingBottomLeft))
            {
                d->currentResizing = Private::ResizingBottomLeft;
                d->regionSelection.setTopRight(opp);
                setCursor(Qt::SizeBDiagCursor);
            }
            else if ((dir.x() < 0) && (dir.y() < 0) && (d->currentResizing != Private::ResizingTopLeft))
            {
                d->currentResizing = Private::ResizingTopLeft;
                d->regionSelection.setBottomRight(opp);
                setCursor(Qt::SizeFDiagCursor);
            }
            else
            {
                if      ((dir.x() == 0) && (dir.y() == 0))
                {
                    setCursor(Qt::SizeAllCursor);
                }
                else if (dir.x() == 0)
                {
                    setCursor(Qt::SizeHorCursor);
                }
                else if (dir.y() == 0)
                {
                    setCursor(Qt::SizeVerCursor);
                }
            }

            placeSelection(pmVirtual, symmetric, center);
        }
    }
    else
    {

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))

        if      (d->localTopLeftCorner.contains(e->position().toPoint().x(), e->position().toPoint().y()) ||
                 d->localBottomRightCorner.contains(e->position().toPoint().x(), e->position().toPoint().y()))
        {
            setCursor(Qt::SizeFDiagCursor);
        }
        else if (d->localTopRightCorner.contains(e->position().toPoint().x(), e->position().toPoint().y()) ||
                 d->localBottomLeftCorner.contains(e->position().toPoint().x(), e->position().toPoint().y()))
        {
            setCursor(Qt::SizeBDiagCursor);
        }
        else if (d->localRegionSelection.contains(e->position().toPoint().x(), e->position().toPoint().y()))
        {
            setCursor(Qt::SizeAllCursor);
        }

#else

        if      (d->localTopLeftCorner.contains(e->x(), e->y()) ||
                 d->localBottomRightCorner.contains(e->x(), e->y()))
        {
            setCursor(Qt::SizeFDiagCursor);
        }
        else if (d->localTopRightCorner.contains(e->x(), e->y()) ||
                 d->localBottomLeftCorner.contains(e->x(), e->y()))
        {
            setCursor(Qt::SizeBDiagCursor);
        }
        else if (d->localRegionSelection.contains(e->x(), e->y()))
        {
            setCursor(Qt::SizeAllCursor);
        }

#endif

        else
        {
            setCursor(Qt::ArrowCursor);
        }
    }
}

void RatioCropWidget::setIsDrawingSelection(bool draw)
{
    d->isDrawingSelection = draw;
}

} // namespace DigikamEditorRatioCropToolPlugin

#include "moc_ratiocropwidget.cpp"
