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        : 2004-08-22
0007  * Description : a generic widget to display a panel to choose
0008  *               a rectangular image area.
0009  *
0010  * SPDX-FileCopyrightText: 1997      by Tim D. Gilman <tdgilman at best dot org>
0011  * SPDX-FileCopyrightText: 1998-2001 by Mirko Boehm <mirko at kde dot org>
0012  * SPDX-FileCopyrightText: 2007      by John Layt <john at layt dot net>
0013  * SPDX-FileCopyrightText: 2004-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0014  *
0015  * SPDX-License-Identifier: GPL-2.0-or-later
0016  *
0017  * ============================================================ */
0018 
0019 #include "paniconwidget.h"
0020 
0021 // C++ includes
0022 
0023 #include <cmath>
0024 
0025 // Qt includes
0026 
0027 #include <QPainter>
0028 #include <QPen>
0029 #include <QTimer>
0030 #include <QToolButton>
0031 #include <QIcon>
0032 #include <QApplication>
0033 #include <QEventLoop>
0034 #include <QKeyEvent>
0035 #include <QScreen>
0036 #include <QWindow>
0037 
0038 // KDE includes
0039 
0040 #include <klocalizedstring.h>
0041 
0042 namespace Digikam
0043 {
0044 
0045 class Q_DECL_HIDDEN PanIconFrame::Private
0046 {
0047 public:
0048 
0049     explicit Private(PanIconFrame* const qq);
0050     ~Private();
0051 
0052 public:
0053 
0054     PanIconFrame*        q;
0055 
0056     /**
0057      * The result. It is returned from exec() when the popup window closes.
0058      */
0059     int                  result;
0060 
0061     /**
0062      * The only subwidget that uses the whole dialog window.
0063      */
0064     QWidget*             main;
0065 
0066     class OutsideClickCatcher;
0067     OutsideClickCatcher* outsideClickCatcher;
0068 };
0069 
0070 // -------------------------------------------------------------------
0071 
0072 class Q_DECL_HIDDEN PanIconFrame::Private::OutsideClickCatcher : public QObject
0073 {
0074     Q_OBJECT
0075 
0076 public:
0077 
0078     explicit OutsideClickCatcher(QObject* const parent = nullptr)
0079         : QObject(parent),
0080           m_popup(nullptr)
0081     {
0082     }
0083 
0084     ~OutsideClickCatcher() override
0085     {
0086     }
0087 
0088     void setPopupFrame(PanIconFrame* const popup)
0089     {
0090         m_popup = popup;
0091         popup->installEventFilter(this);
0092     }
0093 
0094     bool eventFilter(QObject* object, QEvent* event) override
0095     {
0096         Q_UNUSED(object);
0097 
0098         // To catch outside clicks, it is sufficient to check for
0099         // hide events on Qt::Popup type widgets
0100 
0101         if (event->type() == QEvent::Hide && m_popup)
0102         {
0103             // do not set d->result here, because the popup
0104             // hides itself after leaving the event loop.
0105 
0106             Q_EMIT m_popup->leaveModality();
0107         }
0108 
0109         return false;
0110     }
0111 
0112 public:
0113 
0114     PanIconFrame* m_popup;
0115 };
0116 
0117 // -------------------------------------------------------------------
0118 
0119 PanIconFrame::Private::Private(PanIconFrame* const qq)
0120     : q                  (qq),
0121       result             (0), // rejected
0122       main               (nullptr),
0123       outsideClickCatcher(new OutsideClickCatcher)
0124 {
0125     outsideClickCatcher->setPopupFrame(q);
0126 }
0127 
0128 PanIconFrame::Private::~Private()
0129 {
0130     delete outsideClickCatcher;
0131 }
0132 
0133 // -------------------------------------------------------------------
0134 
0135 PanIconFrame::PanIconFrame(QWidget* const parent)
0136     : QFrame(parent, Qt::Popup),
0137       d     (new Private(this))
0138 {
0139     setFrameStyle(QFrame::Box | QFrame::Raised);
0140     setMidLineWidth(2);
0141 }
0142 
0143 PanIconFrame::~PanIconFrame()
0144 {
0145     delete d;
0146 }
0147 
0148 void PanIconFrame::keyPressEvent(QKeyEvent* e)
0149 {
0150     if (e->key() == Qt::Key_Escape)
0151     {
0152         d->result = 0; // rejected
0153         Q_EMIT leaveModality();
0154     }
0155 }
0156 
0157 void PanIconFrame::close(int r)
0158 {
0159     d->result = r;
0160 
0161     Q_EMIT leaveModality();
0162 }
0163 
0164 void PanIconFrame::setMainWidget(QWidget* const main)
0165 {
0166     d->main = main;
0167 
0168     if (d->main)
0169     {
0170         resize(d->main->width() + 2 * frameWidth(), d->main->height() + 2 * frameWidth());
0171     }
0172 }
0173 
0174 void PanIconFrame::resizeEvent(QResizeEvent* e)
0175 {
0176     Q_UNUSED(e);
0177 
0178     if (d->main)
0179     {
0180         d->main->setGeometry(frameWidth(), frameWidth(),
0181                              width() - 2 * frameWidth(), height() - 2 * frameWidth());
0182     }
0183 }
0184 
0185 void PanIconFrame::popup(const QPoint& pos)
0186 {
0187     // Make sure the whole popup is visible.
0188 
0189     QScreen* screen = qApp->primaryScreen();
0190 
0191     if (QWidget* const widget = nativeParentWidget())
0192     {
0193         if (QWindow* const window = widget->windowHandle())
0194         {
0195             screen = window->screen();
0196         }
0197     }
0198 
0199     QRect desktopGeometry = screen->geometry();
0200 
0201     int x = pos.x();
0202     int y = pos.y();
0203     int w = width();
0204     int h = height();
0205 
0206     if ((x + w) > (desktopGeometry.x() + desktopGeometry.width()))
0207     {
0208         x = desktopGeometry.width() - w;
0209     }
0210 
0211     if ((y + h) > (desktopGeometry.y() + desktopGeometry.height()))
0212     {
0213         y = desktopGeometry.height() - h;
0214     }
0215 
0216     if (x < desktopGeometry.x())
0217     {
0218         x = 0;
0219     }
0220 
0221     if (y < desktopGeometry.y())
0222     {
0223         y = 0;
0224     }
0225 
0226     // Pop the thingy up.
0227 
0228     move(x, y);
0229     show();
0230     d->main->setFocus();
0231 }
0232 
0233 int PanIconFrame::exec(const QPoint& pos)
0234 {
0235     popup(pos);
0236     repaint();
0237     d->result = 0; // rejected
0238     QEventLoop eventLoop;
0239 
0240     connect(this, SIGNAL(leaveModality()),
0241             &eventLoop, SLOT(quit()));
0242 
0243     eventLoop.exec();
0244     hide();
0245 
0246     return d->result;
0247 }
0248 
0249 int PanIconFrame::exec(int x, int y)
0250 {
0251     return exec(QPoint(x, y));
0252 }
0253 
0254 // -------------------------------------------------------------------
0255 
0256 class Q_DECL_HIDDEN PanIconWidget::Private
0257 {
0258 
0259 public:
0260 
0261     explicit Private()
0262       : moveSelection   (false),
0263         flicker         (false),
0264         width           (0),
0265         height          (0),
0266         zoomedOrgWidth  (0),
0267         zoomedOrgHeight (0),
0268         orgWidth        (0),
0269         orgHeight       (0),
0270         xpos            (0),
0271         ypos            (0),
0272         zoomFactor      (1.0),
0273         timer           (nullptr)
0274     {
0275     }
0276 
0277     bool    moveSelection;
0278     bool    flicker;
0279 
0280     int     width;
0281     int     height;
0282     int     zoomedOrgWidth;
0283     int     zoomedOrgHeight;
0284     int     orgWidth;
0285     int     orgHeight;
0286     int     xpos;
0287     int     ypos;
0288 
0289     double  zoomFactor;
0290 
0291     QRect   regionSelection;         ///< Original size image selection.
0292     QTimer* timer;
0293 
0294     QRect   rect;
0295     QRect   localRegionSelection;    ///< Thumbnail size selection.
0296 
0297     QPixmap pixmap;
0298 };
0299 
0300 PanIconWidget::PanIconWidget(QWidget* const parent)
0301     : QWidget(parent),
0302       d      (new Private)
0303 {
0304     d->timer = new QTimer(this);
0305     d->timer->setInterval(800);
0306 
0307     setMouseTracking(true);
0308     setAttribute(Qt::WA_DeleteOnClose);
0309 
0310     connect(d->timer, SIGNAL(timeout()),
0311             this, SLOT(slotFlickerTimer()));
0312 }
0313 
0314 PanIconWidget::~PanIconWidget()
0315 {
0316     delete d;
0317 }
0318 
0319 QToolButton* PanIconWidget::button()
0320 {
0321     QToolButton* const btn = new QToolButton;
0322     btn->setToolButtonStyle(Qt::ToolButtonIconOnly);
0323     btn->setIcon(QIcon::fromTheme(QLatin1String("transform-move")));
0324     btn->hide();
0325     btn->setToolTip(i18n("Pan the image to a region"));
0326 
0327     return btn;
0328 }
0329 
0330 void PanIconWidget::setImage(int previewWidth, int previewHeight, const QImage& image)
0331 {
0332     QSize sz(image.width(), image.height());
0333     sz.scale(previewWidth, previewHeight, Qt::KeepAspectRatio);
0334     QImage scaledImg = image.scaled(sz.width(), sz.height(),
0335                                     Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0336 
0337     setImage(scaledImg, image.size());
0338 }
0339 
0340 void PanIconWidget::setImage(const QImage& scaledPreviewImage, const QSize& fullImageSize)
0341 {
0342     d->width           = scaledPreviewImage.width();
0343     d->height          = scaledPreviewImage.height();
0344     d->orgWidth        = fullImageSize.width();
0345     d->orgHeight       = fullImageSize.height();
0346     d->zoomedOrgWidth  = fullImageSize.width();
0347     d->zoomedOrgHeight = fullImageSize.height();
0348     d->pixmap          = QPixmap(d->width, d->height);
0349     d->pixmap.fill(palette().color(QPalette::Window));
0350     QPainter p(&d->pixmap);
0351     p.drawImage(0, 0, scaledPreviewImage);
0352 
0353     setFixedSize(d->width, d->height);
0354 
0355     d->rect = QRect(width()/2-d->width/2, height()/2-d->height/2, d->width, d->height);
0356     update();
0357 }
0358 
0359 void PanIconWidget::setImage(int previewWidth, int previewHeight, const DImg& image)
0360 {
0361     DImg img = DImg(image).smoothScale(previewWidth, previewHeight, Qt::KeepAspectRatio);
0362     setImage(img.copyQImage(), image.size());
0363 }
0364 
0365 void PanIconWidget::slotZoomFactorChanged(double factor)
0366 {
0367     if (d->zoomFactor == factor)
0368     {
0369         return;
0370     }
0371 
0372     d->zoomFactor      = factor;
0373     d->zoomedOrgWidth  = (int)(d->orgWidth  * factor);
0374     d->zoomedOrgHeight = (int)(d->orgHeight * factor);
0375     update();
0376 }
0377 
0378 void PanIconWidget::setRegionSelection(const QRect& regionSelection)
0379 {
0380     d->regionSelection = regionSelection;
0381     d->localRegionSelection.setX(d->rect.x() + (int)((float)d->regionSelection.x() *
0382                                  ((float)d->width / (float)d->zoomedOrgWidth)));
0383 
0384     d->localRegionSelection.setY(d->rect.y() + (int)((float)d->regionSelection.y() *
0385                                  ((float)d->height / (float)d->zoomedOrgHeight)));
0386 
0387     d->localRegionSelection.setWidth((int)((float)d->regionSelection.width() *
0388                                            ((float)d->width / (float)d->zoomedOrgWidth)));
0389 
0390     d->localRegionSelection.setHeight((int)((float)d->regionSelection.height() *
0391                                       ((float)d->height / (float)d->zoomedOrgHeight)));
0392 
0393     update();
0394 }
0395 
0396 QRect PanIconWidget::getRegionSelection() const
0397 {
0398     return (d->regionSelection);
0399 }
0400 
0401 void PanIconWidget::setCursorToLocalRegionSelectionCenter()
0402 {
0403     QCursor::setPos(mapToGlobal(d->localRegionSelection.center()));
0404 }
0405 
0406 void PanIconWidget::setCenterSelection()
0407 {
0408     setRegionSelection(QRect((int)(((float)d->zoomedOrgWidth / 2.0) - ((float)d->regionSelection.width()  / 2.0)),
0409                             (int)(((float)d->zoomedOrgHeight / 2.0) - ((float)d->regionSelection.height() / 2.0)),
0410                             d->regionSelection.width(),
0411                             d->regionSelection.height()));
0412 }
0413 
0414 void PanIconWidget::regionSelectionMoved(bool targetDone)
0415 {
0416     if (targetDone)
0417     {
0418         update();
0419     }
0420 
0421     int x = (int)lround(((float)d->localRegionSelection.x() - (float)d->rect.x() ) *
0422                         ((float)d->zoomedOrgWidth / (float)d->width));
0423 
0424     int y = (int)lround(((float)d->localRegionSelection.y() - (float)d->rect.y() ) *
0425                         ((float)d->zoomedOrgHeight / (float)d->height));
0426 
0427     int w = (int)lround((float)d->localRegionSelection.width() *
0428                         ((float)d->zoomedOrgWidth / (float)d->width));
0429 
0430     int h = (int)lround((float)d->localRegionSelection.height() *
0431                         ((float)d->zoomedOrgHeight / (float)d->height));
0432 
0433     d->regionSelection.setX(x);
0434     d->regionSelection.setY(y);
0435     d->regionSelection.setWidth(w);
0436     d->regionSelection.setHeight(h);
0437 
0438     Q_EMIT signalSelectionMoved(d->regionSelection, targetDone);
0439 }
0440 
0441 void PanIconWidget::paintEvent(QPaintEvent*)
0442 {
0443     QPainter p(this);
0444 
0445     p.drawPixmap(d->rect.x(), d->rect.y(), d->pixmap);
0446 
0447     // Drawing selection border
0448 
0449     if (d->flicker)
0450     {
0451         p.setPen(QPen(Qt::white, 1, Qt::SolidLine));
0452     }
0453     else
0454     {
0455         p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
0456     }
0457 
0458     QRect r(d->localRegionSelection);
0459 
0460     // Clamp to widget size. Selection area must always be visible
0461 
0462     if (r.left() < 0)
0463     {
0464         r.setLeft(0);
0465     }
0466 
0467     if (r.top() < 0)
0468     {
0469         r.setTop(0);
0470     }
0471 
0472     if (r.right() > width()-2)
0473     {
0474         r.setRight(width()-2);
0475     }
0476 
0477     if (r.bottom() > height()-2)
0478     {
0479         r.setBottom(height()-2);
0480     }
0481 
0482     p.drawRect(r.x(), r.y(), r.width(), r.height());
0483 
0484     if (d->flicker)
0485     {
0486         p.setPen(QPen(Qt::red, 1, Qt::DotLine));
0487     }
0488     else
0489     {
0490         p.setPen(QPen(Qt::white, 1, Qt::DotLine));
0491     }
0492 
0493     p.drawRect(r.x(), r.y(), r.width(), r.height());
0494 }
0495 
0496 void PanIconWidget::setMouseFocus()
0497 {
0498     raise();
0499     d->xpos          = d->localRegionSelection.center().x();
0500     d->ypos          = d->localRegionSelection.center().y();
0501     d->moveSelection = true;
0502     setCursor(Qt::SizeAllCursor);
0503     Q_EMIT signalSelectionTakeFocus();
0504 }
0505 
0506 void PanIconWidget::showEvent(QShowEvent* e)
0507 {
0508     QWidget::showEvent(e);
0509 
0510     d->timer->start();
0511 }
0512 
0513 void PanIconWidget::hideEvent(QHideEvent* e)
0514 {
0515     QWidget::hideEvent(e);
0516 
0517     d->timer->stop();
0518 
0519     if (d->moveSelection)
0520     {
0521         d->moveSelection = false;
0522         setCursor(Qt::ArrowCursor);
0523         Q_EMIT signalHidden();
0524     }
0525 }
0526 
0527 void PanIconWidget::mousePressEvent(QMouseEvent* e)
0528 {
0529     if (
0530         ((e->button() == Qt::LeftButton) || (e->button() == Qt::MiddleButton)) &&
0531 
0532 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0533 
0534          d->localRegionSelection.contains(e->position().toPoint().x(), e->position().toPoint().y())
0535 
0536 #else
0537 
0538          d->localRegionSelection.contains(e->x(), e->y())
0539 
0540 #endif
0541 
0542        )
0543     {
0544 
0545 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0546 
0547         d->xpos          = e->position().toPoint().x();
0548         d->ypos          = e->position().toPoint().y();
0549 
0550 #else
0551 
0552         d->xpos          = e->x();
0553         d->ypos          = e->y();
0554 
0555 #endif
0556 
0557         d->moveSelection = true;
0558         setCursor(Qt::SizeAllCursor);
0559         Q_EMIT signalSelectionTakeFocus();
0560     }
0561 }
0562 
0563 void PanIconWidget::mouseMoveEvent(QMouseEvent* e)
0564 {
0565     if (d->moveSelection &&
0566         ((e->buttons() == Qt::LeftButton) || (e->buttons() == Qt::MiddleButton)))
0567     {
0568 
0569 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0570 
0571         int newxpos = e->position().toPoint().x();
0572         int newypos = e->position().toPoint().y();
0573 
0574 #else
0575 
0576         int newxpos = e->x();
0577         int newypos = e->y();
0578 
0579 #endif
0580 
0581         d->localRegionSelection.translate(newxpos - d->xpos, newypos - d->ypos);
0582 
0583         d->xpos = newxpos;
0584         d->ypos = newypos;
0585 
0586         // Perform normalization of selection area.
0587 
0588         if (d->localRegionSelection.left() < d->rect.left())
0589         {
0590             d->localRegionSelection.moveLeft(d->rect.left());
0591         }
0592 
0593         if (d->localRegionSelection.top() < d->rect.top())
0594         {
0595             d->localRegionSelection.moveTop(d->rect.top());
0596         }
0597 
0598         if (d->localRegionSelection.right() > d->rect.right())
0599         {
0600             d->localRegionSelection.moveRight(d->rect.right());
0601         }
0602 
0603         if (d->localRegionSelection.bottom() > d->rect.bottom())
0604         {
0605             d->localRegionSelection.moveBottom(d->rect.bottom());
0606         }
0607 
0608         update();
0609         regionSelectionMoved(false);
0610         return;
0611     }
0612     else
0613     {
0614 
0615 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0616 
0617         if (d->localRegionSelection.contains(e->position().toPoint().x(), e->position().toPoint().y()))
0618 
0619 #else
0620 
0621         if (d->localRegionSelection.contains(e->x(), e->y()))
0622 
0623 #endif
0624 
0625         {
0626             setCursor(Qt::PointingHandCursor);
0627         }
0628         else
0629         {
0630             setCursor(Qt::ArrowCursor);
0631         }
0632     }
0633 }
0634 
0635 void PanIconWidget::mouseReleaseEvent(QMouseEvent*)
0636 {
0637     if (d->moveSelection)
0638     {
0639         d->moveSelection = false;
0640         setCursor(Qt::ArrowCursor);
0641         regionSelectionMoved(true);
0642     }
0643 }
0644 
0645 void PanIconWidget::slotFlickerTimer()
0646 {
0647     d->flicker = !d->flicker;
0648     update();
0649 }
0650 
0651 } // namespace Digikam
0652 
0653 #include "paniconwidget.moc"
0654 
0655 #include "moc_paniconwidget.cpp"