/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2010-04-30
 * Description : Graphics View for DImg preview
 *
 * SPDX-FileCopyrightText: 2010-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
 * SPDX-FileCopyrightText: 2011-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * ============================================================ */

#include "graphicsdimgview.h"

// Qt includes

#include <QApplication>
#include <QGraphicsScene>
#include <QScrollBar>
#include <QToolButton>
#include <QStyle>

// Local includes

#include "digikam_debug.h"
#include "dimgpreviewitem.h"
#include "imagezoomsettings.h"
#include "paniconwidget.h"
#include "previewlayout.h"
#include "dimgchilditem.h"

namespace Digikam
{

class Q_DECL_HIDDEN GraphicsDImgView::Private
{
public:

    Private() = default;

    QGraphicsScene*           scene             = nullptr;
    GraphicsDImgItem*         item              = nullptr;
    SinglePhotoPreviewLayout* layout            = nullptr;

    QToolButton*              cornerButton      = nullptr;
    PanIconFrame*             panIconPopup      = nullptr;

    QPoint                    mousePressPos;
    QPoint                    panningScrollPos;
    bool                      movingInProgress  = false;
    bool                      showText          = true;
};

GraphicsDImgView::GraphicsDImgView(QWidget* const parent)
    : QGraphicsView(parent),
      d            (new Private)
{
    d->scene  = new QGraphicsScene(this);
    d->scene->setItemIndexMethod(QGraphicsScene::NoIndex);

    setScene(d->scene);
    d->layout = new SinglePhotoPreviewLayout(this);
    d->layout->setGraphicsView(this);

    setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);

    horizontalScrollBar()->setSingleStep(1);
    horizontalScrollBar()->setPageStep(1);
    verticalScrollBar()->setSingleStep(1);
    verticalScrollBar()->setPageStep(1);

    connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
            this, SLOT(slotContentsMoved()));

    connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
            this, SLOT(slotContentsMoved()));
}

GraphicsDImgView::~GraphicsDImgView()
{
    delete d;
}

void GraphicsDImgView::setItem(GraphicsDImgItem* const item)
{
    d->item = item;
    d->item->setDisplayWidget(this);

    d->scene->addItem(d->item);
    d->layout->addItem(d->item);
}

GraphicsDImgItem* GraphicsDImgView::item() const
{
    return d->item;
}

DImgPreviewItem* GraphicsDImgView::previewItem() const
{
    return dynamic_cast<DImgPreviewItem*>(item());
}

SinglePhotoPreviewLayout* GraphicsDImgView::layout() const
{
    return d->layout;
}

void GraphicsDImgView::installPanIcon()
{
    d->cornerButton = PanIconWidget::button();
    setCornerWidget(d->cornerButton);

    connect(d->cornerButton, SIGNAL(pressed()),
            this, SLOT(slotCornerButtonPressed()));
}

void GraphicsDImgView::drawForeground(QPainter* p, const QRectF& rect)
{
    QGraphicsView::drawForeground(p, rect);

    QString text = d->item->userLoadingHint();

    if (text.isNull() || !d->showText)
    {
        return;
    }

    QRect viewportRect        = viewport()->rect();
    QRect fontRect            = p->fontMetrics().boundingRect(viewportRect, 0, text);
    QPoint drawingPoint(viewportRect.topRight().x() - fontRect.width() - 10,
                        viewportRect.topRight().y() + 5);

    QPointF sceneDrawingPoint = mapToScene(drawingPoint);
    QRectF sceneDrawingRect(sceneDrawingPoint, fontRect.size());

    if (!rect.intersects(sceneDrawingRect))
    {
        return;
    }

    drawText(p, sceneDrawingRect, text);
}

void GraphicsDImgView::drawText(QPainter* p, const QRectF& rect, const QString& text)
{
    p->save();

    p->setRenderHint(QPainter::Antialiasing, true);
    p->setBackgroundMode(Qt::TransparentMode);

    // increase width by 5 and height by 2

    QRectF textRect    = rect.adjusted(0, 0, 5, 2);

    // Draw background

    p->setPen(Qt::black);
    QColor semiTransBg = palette().color(QPalette::Window);
    semiTransBg.setAlpha(190);
    p->setBrush(semiTransBg);
/*
    p->translate(0.5, 0.5);
*/
    p->drawRoundedRect(textRect, 10.0, 10.0);

    // Draw shadow and text

    p->setPen(palette().color(QPalette::Window).darker(115));
    p->drawText(textRect.translated(3, 1), text);
    p->setPen(palette().color(QPalette::WindowText));
    p->drawText(textRect.translated(2, 0), text);

    p->restore();
}

void GraphicsDImgView::mouseDoubleClickEvent(QMouseEvent* e)
{
    QGraphicsView::mouseDoubleClickEvent(e);

    if (!acceptsMouseClick(e))
    {
        return;
    }

    if (e->button() == Qt::LeftButton)
    {
        Q_EMIT leftButtonDoubleClicked();

        if (!qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick))
        {
            Q_EMIT activated();
        }
    }
}

void GraphicsDImgView::mousePressEvent(QMouseEvent* e)
{
    QGraphicsView::mousePressEvent(e);

    d->mousePressPos    = QPoint();
    d->movingInProgress = false;

    if (!acceptsMouseClick(e))
    {
        return;
    }

    if (e->button() == Qt::LeftButton)
    {
        Q_EMIT leftButtonClicked();
    }

    if (
        (e->button() == Qt::LeftButton)   ||
        (e->button() == Qt::MiddleButton)
       )
    {
        d->mousePressPos = e->pos();

        if (
            !qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) ||
            (e->button() == Qt::MiddleButton)
           )
        {
            startPanning(e->pos());
        }

        return;
    }

    if (e->button() == Qt::RightButton)
    {
        Q_EMIT rightButtonClicked();
    }
}

void GraphicsDImgView::mouseMoveEvent(QMouseEvent* e)
{
    QGraphicsView::mouseMoveEvent(e);

    if (
        ((e->buttons() & Qt::LeftButton) || (e->buttons() & Qt::MiddleButton)) &&
        !d->mousePressPos.isNull()
       )
    {
        if (!d->movingInProgress && (e->buttons() & Qt::LeftButton))
        {
            if ((d->mousePressPos - e->pos()).manhattanLength() > QApplication::startDragDistance())
            {
                startPanning(d->mousePressPos);
            }
        }

        if (d->movingInProgress)
        {
            continuePanning(e->pos());
        }
    }
}

void GraphicsDImgView::mouseReleaseEvent(QMouseEvent* e)
{
    QGraphicsView::mouseReleaseEvent(e);

    // Do not call acceptsMouseClick() here, only on press. Seems that release event are accepted per default.

    if (
        ((e->button() == Qt::LeftButton) || (e->button() == Qt::MiddleButton)) &&
        !d->mousePressPos.isNull()
       )
    {
        if (!d->movingInProgress && (e->button() == Qt::LeftButton))
        {
            if (qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick))
            {
                Q_EMIT activated();
            }
        }
        else
        {
            finishPanning();
        }
    }

    d->movingInProgress = false;
    d->mousePressPos    = QPoint();
}

bool GraphicsDImgView::acceptsMouseClick(QMouseEvent* e)
{
    // the basic condition is that now item ate the event

    if (e->isAccepted())
    {
        return false;
    }

    return true;
}

void GraphicsDImgView::resizeEvent(QResizeEvent* e)
{
    QGraphicsView::resizeEvent(e);
    d->layout->updateZoomAndSize();

    Q_EMIT resized();
    Q_EMIT viewportRectChanged(mapToScene(viewport()->rect()).boundingRect());
}

void GraphicsDImgView::scrollContentsBy(int dx, int dy)
{
    QGraphicsView::scrollContentsBy(dx, dy);

    Q_EMIT viewportRectChanged(mapToScene(viewport()->rect()).boundingRect());
}

void GraphicsDImgView::startPanning(const QPoint& pos)
{
    if (horizontalScrollBar()->maximum() || verticalScrollBar()->maximum())
    {
        d->movingInProgress = true;
        d->mousePressPos    = pos;
        d->panningScrollPos = QPoint(horizontalScrollBar()->value(),
                                     verticalScrollBar()->value());
        viewport()->setCursor(Qt::SizeAllCursor);
    }
}

void GraphicsDImgView::continuePanning(const QPoint& pos)
{
    QPoint delta = pos - d->mousePressPos;
    horizontalScrollBar()->setValue(d->panningScrollPos.x() + (isRightToLeft() ? delta.x() : -delta.x()));
    verticalScrollBar()->setValue(d->panningScrollPos.y() - delta.y());

    Q_EMIT contentsMoved(false);

    viewport()->update();
}

void GraphicsDImgView::finishPanning()
{
    Q_EMIT contentsMoved(true);

    viewport()->unsetCursor();
}

void GraphicsDImgView::scrollPointOnPoint(const QPointF& scenePos, const QPoint& viewportPos)
{
    // This is inspired from QGraphicsView's centerOn()

    QPointF viewPoint = transform().map(scenePos);

    if (horizontalScrollBar()->maximum())
    {
        if (isRightToLeft())
        {
            qint64 horizontal = 0;
            horizontal       += horizontalScrollBar()->minimum();
            horizontal       += horizontalScrollBar()->maximum();
            horizontal       -= int(viewPoint.x() - viewportPos.x());
            horizontalScrollBar()->setValue(horizontal);
        }
        else
        {
            horizontalScrollBar()->setValue(int(viewPoint.x() - viewportPos.x()));
        }
    }

    if (verticalScrollBar()->maximum())
    {
        verticalScrollBar()->setValue(int(viewPoint.y() - viewportPos.y()));
    }

    viewport()->update();
}

void GraphicsDImgView::wheelEvent(QWheelEvent* e)
{
    int p = this->verticalScrollBar()->sliderPosition();

    if      (e->modifiers() & Qt::ShiftModifier)
    {
        e->accept();

        if      (e->angleDelta().y() < 0)
        {
            Q_EMIT toNextImage();
        }
        else if (e->angleDelta().y() > 0)
        {
            Q_EMIT toPreviousImage();
        }

        return;
    }
    else if (e->modifiers() & Qt::ControlModifier)
    {
        // When zooming with the mouse-wheel, the image center is kept fixed.

        QPoint point = e->position().toPoint();

        if      (e->angleDelta().y() < 0)
        {
            d->layout->decreaseZoom(point);
        }
        else if (e->angleDelta().y() > 0)
        {
            d->layout->increaseZoom(point);
        }

        return;
    }

    else if (
             ((p == this->verticalScrollBar()->maximum()) && (e->angleDelta().y() < 0)) ||
             ((p == this->verticalScrollBar()->minimum()) && (e->angleDelta().y() > 0))
            )
    {
        // I had to add this condition for "ImageBrushGuideWidget" that subclasses ImageRegionWidget, used
        // in the healingclone tool.
        // If I remove that condition, this event handler gets called recursively and the program
        // crashes.T I couldn't figure out the reason. [Ahmed Fathy]

        return;
    }
    else
    {
        QGraphicsView::wheelEvent(e);
    }
}

void GraphicsDImgView::slotCornerButtonPressed()
{
    if (d->panIconPopup)
    {
        d->panIconPopup->hide();
        d->panIconPopup->deleteLater();
        d->panIconPopup = nullptr;
    }

    d->panIconPopup          = new PanIconFrame(this);
    PanIconWidget* const pan = new PanIconWidget(d->panIconPopup);
/*
    connect(pan, SIGNAL(signalSelectionTakeFocus()),
            this, SIGNAL(signalContentTakeFocus()));
*/
    connect(pan, SIGNAL(signalSelectionMoved(QRect,bool)),
            this, SLOT(slotPanIconSelectionMoved(QRect,bool)));

    connect(pan, SIGNAL(signalHidden()),
            this, SLOT(slotPanIconHidden()));

    pan->setImage(180, 120, item()->image());
    QRectF sceneRect(mapToScene(viewport()->rect().topLeft()), mapToScene(viewport()->rect().bottomRight()));
    pan->setRegionSelection(item()->zoomSettings()->sourceRect(sceneRect).toRect());
    pan->setMouseFocus();
    d->panIconPopup->setMainWidget(pan);
/*
    slotContentTakeFocus();
*/
    QPoint g = mapToGlobal(viewport()->pos());
    g.setX(g.x()+ viewport()->size().width());
    g.setY(g.y()+ viewport()->size().height());
    d->panIconPopup->popup(QPoint(g.x() - d->panIconPopup->width(),
                                  g.y() - d->panIconPopup->height()));

    pan->setCursorToLocalRegionSelectionCenter();
}

void GraphicsDImgView::slotPanIconHidden()
{
    d->cornerButton->blockSignals(true);
    d->cornerButton->animateClick();
    d->cornerButton->blockSignals(false);
}

void GraphicsDImgView::slotPanIconSelectionMoved(const QRect& imageRect, bool b)
{
    QRectF zoomRect = item()->zoomSettings()->mapImageToZoom(imageRect);
    qCDebug(DIGIKAM_WIDGETS_LOG) << imageRect << zoomRect;
    centerOn(item()->mapToScene(zoomRect.center()));
    viewport()->update();

    if (b)
    {
        d->panIconPopup->hide();
        d->panIconPopup->deleteLater();
        d->panIconPopup = nullptr;
        slotPanIconHidden();
        //slotContentLeaveFocus();
    }
}

void GraphicsDImgView::slotContentsMoved()
{
    Q_EMIT contentsMoving(horizontalScrollBar()->value(),
                          verticalScrollBar()->value());

    viewport()->update();
}

int GraphicsDImgView::contentsX() const
{
    return horizontalScrollBar()->value();
}

int GraphicsDImgView::contentsY() const
{
    return verticalScrollBar()->value();
}

void GraphicsDImgView::setContentsPos(int x, int y)
{
    horizontalScrollBar()->setValue(x);
    verticalScrollBar()->setValue(y);
}

void GraphicsDImgView::setShowText(bool val)
{
    d->showText = val;
}

void GraphicsDImgView::setScaleFitToWindow(bool value)
{
    d->layout->setScaleFitToWindow(value);
}

QRect GraphicsDImgView::visibleArea() const
{
    return (mapToScene(viewport()->geometry()).boundingRect().toRect());
}

void GraphicsDImgView::fitToWindow()
{
    d->layout->fitToWindow();
    update();
}

void GraphicsDImgView::toggleFullScreen(bool set)
{
    if (set)
    {
        d->scene->setBackgroundBrush(Qt::black);
        setFrameShape(QFrame::NoFrame);
    }
    else
    {
        d->scene->setBackgroundBrush(Qt::NoBrush);
        setFrameShape(QFrame::StyledPanel);
    }
}

} // namespace Digikam

#include "moc_graphicsdimgview.cpp"
