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"