File indexing completed on 2025-04-27 03:58:24

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-04-30
0007  * Description : Graphics View for DImg preview
0008  *
0009  * SPDX-FileCopyrightText: 2010-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2011-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "graphicsdimgview.h"
0017 
0018 // Qt includes
0019 
0020 #include <QApplication>
0021 #include <QGraphicsScene>
0022 #include <QScrollBar>
0023 #include <QToolButton>
0024 #include <QStyle>
0025 
0026 // Local includes
0027 
0028 #include "digikam_debug.h"
0029 #include "dimgpreviewitem.h"
0030 #include "imagezoomsettings.h"
0031 #include "paniconwidget.h"
0032 #include "previewlayout.h"
0033 #include "dimgchilditem.h"
0034 
0035 namespace Digikam
0036 {
0037 
0038 class Q_DECL_HIDDEN GraphicsDImgView::Private
0039 {
0040 public:
0041 
0042     explicit Private()
0043       : scene           (nullptr),
0044         item            (nullptr),
0045         layout          (nullptr),
0046         cornerButton    (nullptr),
0047         panIconPopup    (nullptr),
0048         movingInProgress(false),
0049         showText        (true)
0050     {
0051     }
0052 
0053     QGraphicsScene*           scene;
0054     GraphicsDImgItem*         item;
0055     SinglePhotoPreviewLayout* layout;
0056 
0057     QToolButton*              cornerButton;
0058     PanIconFrame*             panIconPopup;
0059 
0060     QPoint                    mousePressPos;
0061     QPoint                    panningScrollPos;
0062     bool                      movingInProgress;
0063     bool                      showText;
0064 };
0065 
0066 GraphicsDImgView::GraphicsDImgView(QWidget* const parent)
0067     : QGraphicsView(parent),
0068       d(new Private)
0069 {
0070     d->scene  = new QGraphicsScene(this);
0071     d->scene->setItemIndexMethod(QGraphicsScene::NoIndex);
0072 
0073     setScene(d->scene);
0074     d->layout = new SinglePhotoPreviewLayout(this);
0075     d->layout->setGraphicsView(this);
0076 
0077     setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
0078 
0079     horizontalScrollBar()->setSingleStep(1);
0080     horizontalScrollBar()->setPageStep(1);
0081     verticalScrollBar()->setSingleStep(1);
0082     verticalScrollBar()->setPageStep(1);
0083 
0084     connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
0085             this, SLOT(slotContentsMoved()));
0086 
0087     connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
0088             this, SLOT(slotContentsMoved()));
0089 }
0090 
0091 GraphicsDImgView::~GraphicsDImgView()
0092 {
0093     delete d;
0094 }
0095 
0096 void GraphicsDImgView::setItem(GraphicsDImgItem* const item)
0097 {
0098     d->item = item;
0099     d->item->setDisplayWidget(this);
0100 
0101     d->scene->addItem(d->item);
0102     d->layout->addItem(d->item);
0103 }
0104 
0105 GraphicsDImgItem* GraphicsDImgView::item() const
0106 {
0107     return d->item;
0108 }
0109 
0110 DImgPreviewItem* GraphicsDImgView::previewItem() const
0111 {
0112     return dynamic_cast<DImgPreviewItem*>(item());
0113 }
0114 
0115 SinglePhotoPreviewLayout* GraphicsDImgView::layout() const
0116 {
0117     return d->layout;
0118 }
0119 
0120 void GraphicsDImgView::installPanIcon()
0121 {
0122     d->cornerButton = PanIconWidget::button();
0123     setCornerWidget(d->cornerButton);
0124 
0125     connect(d->cornerButton, SIGNAL(pressed()),
0126             this, SLOT(slotCornerButtonPressed()));
0127 }
0128 
0129 void GraphicsDImgView::drawForeground(QPainter* p, const QRectF& rect)
0130 {
0131     QGraphicsView::drawForeground(p, rect);
0132 
0133     QString text = d->item->userLoadingHint();
0134 
0135     if (text.isNull() || !d->showText)
0136     {
0137         return;
0138     }
0139 
0140     QRect viewportRect        = viewport()->rect();
0141     QRect fontRect            = p->fontMetrics().boundingRect(viewportRect, 0, text);
0142     QPoint drawingPoint(viewportRect.topRight().x() - fontRect.width() - 10,
0143                         viewportRect.topRight().y() + 5);
0144 
0145     QPointF sceneDrawingPoint = mapToScene(drawingPoint);
0146     QRectF sceneDrawingRect(sceneDrawingPoint, fontRect.size());
0147 
0148     if (!rect.intersects(sceneDrawingRect))
0149     {
0150         return;
0151     }
0152 
0153     drawText(p, sceneDrawingRect, text);
0154 }
0155 
0156 void GraphicsDImgView::drawText(QPainter* p, const QRectF& rect, const QString& text)
0157 {
0158     p->save();
0159 
0160     p->setRenderHint(QPainter::Antialiasing, true);
0161     p->setBackgroundMode(Qt::TransparentMode);
0162 
0163     // increase width by 5 and height by 2
0164 
0165     QRectF textRect    = rect.adjusted(0, 0, 5, 2);
0166 
0167     // Draw background
0168 
0169     p->setPen(Qt::black);
0170     QColor semiTransBg = palette().color(QPalette::Window);
0171     semiTransBg.setAlpha(190);
0172     p->setBrush(semiTransBg);
0173 /*
0174     p->translate(0.5, 0.5);
0175 */
0176     p->drawRoundedRect(textRect, 10.0, 10.0);
0177 
0178     // Draw shadow and text
0179 
0180     p->setPen(palette().color(QPalette::Window).darker(115));
0181     p->drawText(textRect.translated(3, 1), text);
0182     p->setPen(palette().color(QPalette::WindowText));
0183     p->drawText(textRect.translated(2, 0), text);
0184 
0185     p->restore();
0186 }
0187 
0188 void GraphicsDImgView::mouseDoubleClickEvent(QMouseEvent* e)
0189 {
0190     QGraphicsView::mouseDoubleClickEvent(e);
0191 
0192     if (!acceptsMouseClick(e))
0193     {
0194         return;
0195     }
0196 
0197     if (e->button() == Qt::LeftButton)
0198     {
0199         Q_EMIT leftButtonDoubleClicked();
0200 
0201         if (!qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick))
0202         {
0203             Q_EMIT activated();
0204         }
0205     }
0206 }
0207 
0208 void GraphicsDImgView::mousePressEvent(QMouseEvent* e)
0209 {
0210     QGraphicsView::mousePressEvent(e);
0211 
0212     d->mousePressPos    = QPoint();
0213     d->movingInProgress = false;
0214 
0215     if (!acceptsMouseClick(e))
0216     {
0217         return;
0218     }
0219 
0220     if (e->button() == Qt::LeftButton)
0221     {
0222         Q_EMIT leftButtonClicked();
0223     }
0224 
0225     if ((e->button() == Qt::LeftButton) || (e->button() == Qt::MiddleButton))
0226     {
0227         d->mousePressPos = e->pos();
0228 
0229         if (!qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || (e->button() == Qt::MiddleButton))
0230         {
0231             startPanning(e->pos());
0232         }
0233 
0234         return;
0235     }
0236 
0237     if (e->button() == Qt::RightButton)
0238     {
0239         Q_EMIT rightButtonClicked();
0240     }
0241 }
0242 
0243 void GraphicsDImgView::mouseMoveEvent(QMouseEvent* e)
0244 {
0245     QGraphicsView::mouseMoveEvent(e);
0246 
0247     if (((e->buttons() & Qt::LeftButton) || (e->buttons() & Qt::MiddleButton)) && !d->mousePressPos.isNull())
0248     {
0249         if (!d->movingInProgress && (e->buttons() & Qt::LeftButton))
0250         {
0251             if ((d->mousePressPos - e->pos()).manhattanLength() > QApplication::startDragDistance())
0252             {
0253                 startPanning(d->mousePressPos);
0254             }
0255         }
0256 
0257         if (d->movingInProgress)
0258         {
0259             continuePanning(e->pos());
0260         }
0261     }
0262 }
0263 
0264 void GraphicsDImgView::mouseReleaseEvent(QMouseEvent* e)
0265 {
0266     QGraphicsView::mouseReleaseEvent(e);
0267 
0268     // Do not call acceptsMouseClick() here, only on press. Seems that release event are accepted per default.
0269 
0270     if (((e->button() == Qt::LeftButton) || (e->button() == Qt::MiddleButton)) && !d->mousePressPos.isNull())
0271     {
0272         if (!d->movingInProgress && (e->button() == Qt::LeftButton))
0273         {
0274             if (qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick))
0275             {
0276                 Q_EMIT activated();
0277             }
0278         }
0279         else
0280         {
0281             finishPanning();
0282         }
0283     }
0284 
0285     d->movingInProgress = false;
0286     d->mousePressPos    = QPoint();
0287 }
0288 
0289 bool GraphicsDImgView::acceptsMouseClick(QMouseEvent* e)
0290 {
0291     // the basic condition is that now item ate the event
0292 
0293     if (e->isAccepted())
0294     {
0295         return false;
0296     }
0297 
0298     return true;
0299 }
0300 
0301 void GraphicsDImgView::resizeEvent(QResizeEvent* e)
0302 {
0303     QGraphicsView::resizeEvent(e);
0304     d->layout->updateZoomAndSize();
0305     Q_EMIT resized();
0306     Q_EMIT viewportRectChanged(mapToScene(viewport()->rect()).boundingRect());
0307 }
0308 
0309 void GraphicsDImgView::scrollContentsBy(int dx, int dy)
0310 {
0311     QGraphicsView::scrollContentsBy(dx, dy);
0312     Q_EMIT viewportRectChanged(mapToScene(viewport()->rect()).boundingRect());
0313 }
0314 
0315 void GraphicsDImgView::startPanning(const QPoint& pos)
0316 {
0317     if (horizontalScrollBar()->maximum() || verticalScrollBar()->maximum())
0318     {
0319         d->movingInProgress = true;
0320         d->mousePressPos    = pos;
0321         d->panningScrollPos = QPoint(horizontalScrollBar()->value(),
0322                                      verticalScrollBar()->value());
0323         viewport()->setCursor(Qt::SizeAllCursor);
0324     }
0325 }
0326 
0327 void GraphicsDImgView::continuePanning(const QPoint& pos)
0328 {
0329     QPoint delta = pos - d->mousePressPos;
0330     horizontalScrollBar()->setValue(d->panningScrollPos.x() + (isRightToLeft() ? delta.x() : -delta.x()));
0331     verticalScrollBar()->setValue(d->panningScrollPos.y() - delta.y());
0332     Q_EMIT contentsMoved(false);
0333     viewport()->update();
0334 }
0335 
0336 void GraphicsDImgView::finishPanning()
0337 {
0338     Q_EMIT contentsMoved(true);
0339     viewport()->unsetCursor();
0340 }
0341 
0342 void GraphicsDImgView::scrollPointOnPoint(const QPointF& scenePos, const QPoint& viewportPos)
0343 {
0344     // This is inspired from QGraphicsView's centerOn()
0345     QPointF viewPoint = transform().map(scenePos);
0346 
0347     if (horizontalScrollBar()->maximum())
0348     {
0349         if (isRightToLeft())
0350         {
0351             qint64 horizontal = 0;
0352             horizontal       += horizontalScrollBar()->minimum();
0353             horizontal       += horizontalScrollBar()->maximum();
0354             horizontal       -= int(viewPoint.x() - viewportPos.x());
0355             horizontalScrollBar()->setValue(horizontal);
0356         }
0357         else
0358         {
0359             horizontalScrollBar()->setValue(int(viewPoint.x() - viewportPos.x()));
0360         }
0361     }
0362 
0363     if (verticalScrollBar()->maximum())
0364     {
0365         verticalScrollBar()->setValue(int(viewPoint.y() - viewportPos.y()));
0366     }
0367 
0368     viewport()->update();
0369 }
0370 
0371 void GraphicsDImgView::wheelEvent(QWheelEvent* e)
0372 {
0373     int p = this->verticalScrollBar()->sliderPosition();
0374 
0375     if (e->modifiers() & Qt::ShiftModifier)
0376     {
0377         e->accept();
0378 
0379         if      (e->angleDelta().y() < 0)
0380         {
0381             Q_EMIT toNextImage();
0382         }
0383         else if (e->angleDelta().y() > 0)
0384         {
0385             Q_EMIT toPreviousImage();
0386         }
0387 
0388         return;
0389     }
0390     else if (e->modifiers() & Qt::ControlModifier)
0391     {
0392         // When zooming with the mouse-wheel, the image center is kept fixed.
0393 
0394         QPoint point = e->position().toPoint();
0395 
0396         if      (e->angleDelta().y() < 0)
0397         {
0398             d->layout->decreaseZoom(point);
0399         }
0400         else if (e->angleDelta().y() > 0)
0401         {
0402             d->layout->increaseZoom(point);
0403         }
0404 
0405         return;
0406     }
0407 
0408     else if (
0409              ((p == this->verticalScrollBar()->maximum()) && (e->angleDelta().y() < 0)) ||
0410              ((p == this->verticalScrollBar()->minimum()) && (e->angleDelta().y() > 0))
0411             )
0412     {
0413         // I had to add this condition for "ImageBrushGuideWidget" that subclasses ImageRegionWidget, used
0414         // in the healingclone tool.
0415         // If I remove that condition, this event handler gets called recursively and the program
0416         // crashes.T I couldn't figure out the reason. [Ahmed Fathy]
0417 
0418         return;
0419     }
0420     else
0421     {
0422         QGraphicsView::wheelEvent(e);
0423     }
0424 }
0425 
0426 void GraphicsDImgView::slotCornerButtonPressed()
0427 {
0428     if (d->panIconPopup)
0429     {
0430         d->panIconPopup->hide();
0431         d->panIconPopup->deleteLater();
0432         d->panIconPopup = nullptr;
0433     }
0434 
0435     d->panIconPopup          = new PanIconFrame(this);
0436     PanIconWidget* const pan = new PanIconWidget(d->panIconPopup);
0437 /*
0438     connect(pan, SIGNAL(signalSelectionTakeFocus()),
0439             this, SIGNAL(signalContentTakeFocus()));
0440 */
0441     connect(pan, SIGNAL(signalSelectionMoved(QRect,bool)),
0442             this, SLOT(slotPanIconSelectionMoved(QRect,bool)));
0443 
0444     connect(pan, SIGNAL(signalHidden()),
0445             this, SLOT(slotPanIconHidden()));
0446 
0447     pan->setImage(180, 120, item()->image());
0448     QRectF sceneRect(mapToScene(viewport()->rect().topLeft()), mapToScene(viewport()->rect().bottomRight()));
0449     pan->setRegionSelection(item()->zoomSettings()->sourceRect(sceneRect).toRect());
0450     pan->setMouseFocus();
0451     d->panIconPopup->setMainWidget(pan);
0452 /*
0453     slotContentTakeFocus();
0454 */
0455     QPoint g = mapToGlobal(viewport()->pos());
0456     g.setX(g.x()+ viewport()->size().width());
0457     g.setY(g.y()+ viewport()->size().height());
0458     d->panIconPopup->popup(QPoint(g.x() - d->panIconPopup->width(),
0459                                   g.y() - d->panIconPopup->height()));
0460 
0461     pan->setCursorToLocalRegionSelectionCenter();
0462 }
0463 
0464 void GraphicsDImgView::slotPanIconHidden()
0465 {
0466     d->cornerButton->blockSignals(true);
0467     d->cornerButton->animateClick();
0468     d->cornerButton->blockSignals(false);
0469 }
0470 
0471 void GraphicsDImgView::slotPanIconSelectionMoved(const QRect& imageRect, bool b)
0472 {
0473     QRectF zoomRect = item()->zoomSettings()->mapImageToZoom(imageRect);
0474     qCDebug(DIGIKAM_WIDGETS_LOG) << imageRect << zoomRect;
0475     centerOn(item()->mapToScene(zoomRect.center()));
0476     viewport()->update();
0477 
0478     if (b)
0479     {
0480         d->panIconPopup->hide();
0481         d->panIconPopup->deleteLater();
0482         d->panIconPopup = nullptr;
0483         slotPanIconHidden();
0484         //slotContentLeaveFocus();
0485     }
0486 }
0487 
0488 void GraphicsDImgView::slotContentsMoved()
0489 {
0490     Q_EMIT contentsMoving(horizontalScrollBar()->value(),
0491                         verticalScrollBar()->value());
0492     viewport()->update();
0493 }
0494 
0495 int GraphicsDImgView::contentsX() const
0496 {
0497     return horizontalScrollBar()->value();
0498 }
0499 
0500 int GraphicsDImgView::contentsY() const
0501 {
0502     return verticalScrollBar()->value();
0503 }
0504 
0505 void GraphicsDImgView::setContentsPos(int x, int y)
0506 {
0507     horizontalScrollBar()->setValue(x);
0508     verticalScrollBar()->setValue(y);
0509 }
0510 
0511 void GraphicsDImgView::setShowText(bool val)
0512 {
0513     d->showText = val;
0514 }
0515 
0516 void GraphicsDImgView::setScaleFitToWindow(bool value)
0517 {
0518     d->layout->setScaleFitToWindow(value);
0519 }
0520 
0521 QRect GraphicsDImgView::visibleArea() const
0522 {
0523     return (mapToScene(viewport()->geometry()).boundingRect().toRect());
0524 }
0525 
0526 void GraphicsDImgView::fitToWindow()
0527 {
0528     d->layout->fitToWindow();
0529     update();
0530 }
0531 
0532 void GraphicsDImgView::toggleFullScreen(bool set)
0533 {
0534     if (set)
0535     {
0536         d->scene->setBackgroundBrush(Qt::black);
0537         setFrameShape(QFrame::NoFrame);
0538     }
0539     else
0540     {
0541         d->scene->setBackgroundBrush(Qt::NoBrush);
0542         setFrameShape(QFrame::StyledPanel);
0543     }
0544 }
0545 
0546 } // namespace Digikam
0547 
0548 #include "moc_graphicsdimgview.cpp"