File indexing completed on 2025-01-05 03:59:43

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2013-08-04
0007  * Description : image editor canvas management class
0008  *
0009  * SPDX-FileCopyrightText: 2013-2014 by Yiou Wang <geow812 at gmail dot com>
0010  * SPDX-FileCopyrightText: 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
0011  * SPDX-FileCopyrightText: 2004-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "canvas.h"
0018 
0019 // Qt includes
0020 
0021 #include <QFileInfo>
0022 #include <QClipboard>
0023 #include <QToolButton>
0024 #include <QScrollBar>
0025 #include <QMimeData>
0026 #include <QApplication>
0027 
0028 // KDE includes
0029 
0030 #include <klocalizedstring.h>
0031 
0032 // Local includes
0033 
0034 #include "imagehistogram.h"
0035 #include "iccsettingscontainer.h"
0036 #include "icctransform.h"
0037 #include "exposurecontainer.h"
0038 #include "iofilesettings.h"
0039 #include "loadingcacheinterface.h"
0040 #include "imagepreviewitem.h"
0041 #include "previewlayout.h"
0042 #include "imagezoomsettings.h"
0043 #include "clickdragreleaseitem.h"
0044 #include "rubberitem.h"
0045 
0046 namespace Digikam
0047 {
0048 
0049 class Q_DECL_HIDDEN Canvas::Private
0050 {
0051 
0052 public:
0053 
0054     explicit Private()
0055       : canvasItem(nullptr),
0056         rubber(nullptr),
0057         wrapItem(nullptr),
0058         core(nullptr)
0059     {
0060     }
0061 
0062     QString               errorMessage;
0063 
0064     ImagePreviewItem*     canvasItem;
0065 
0066     RubberItem*           rubber;
0067     ClickDragReleaseItem* wrapItem;
0068     EditorCore*           core;
0069 };
0070 
0071 Canvas::Canvas(QWidget* const parent)
0072     : GraphicsDImgView(parent),
0073       d(new Private)
0074 {
0075     d->core       = new EditorCore();
0076     d->canvasItem = new ImagePreviewItem;
0077     setItem(d->canvasItem);
0078 
0079     setFrameStyle(QFrame::NoFrame);
0080     addRubber();
0081     layout()->fitToWindow();
0082     installPanIcon();
0083 
0084     setAcceptDrops(true);
0085     viewport()->setAcceptDrops(true);
0086 
0087     // ------------------------------------------------------------
0088 
0089     connect(d->core, SIGNAL(signalModified()),
0090             this, SLOT(slotModified()));
0091 
0092     connect(d->core, SIGNAL(signalLoadingStarted(QString)),
0093             this, SIGNAL(signalLoadingStarted(QString)));
0094 
0095     connect(d->core, SIGNAL(signalImageLoaded(QString,bool)),
0096             this, SLOT(slotImageLoaded(QString,bool)));
0097 
0098     connect(d->core, SIGNAL(signalImageSaved(QString,bool)),
0099             this, SLOT(slotImageSaved(QString,bool)));
0100 
0101     connect(d->core, SIGNAL(signalLoadingProgress(QString,float)),
0102             this, SIGNAL(signalLoadingProgress(QString,float)));
0103 
0104     connect(d->core, SIGNAL(signalSavingStarted(QString)),
0105             this, SIGNAL(signalSavingStarted(QString)));
0106 
0107     connect(d->core, SIGNAL(signalSavingProgress(QString,float)),
0108             this, SIGNAL(signalSavingProgress(QString,float)));
0109 
0110     connect(this, SIGNAL(signalSelected(bool)),
0111             this, SLOT(slotSelected()));
0112 
0113     connect(d->canvasItem, SIGNAL(showContextMenu(QGraphicsSceneContextMenuEvent*)),
0114             this, SIGNAL(signalRightButtonClicked()));
0115 
0116     connect(layout(), SIGNAL(zoomFactorChanged(double)),
0117             this, SIGNAL(signalZoomChanged(double)));
0118 }
0119 
0120 Canvas::~Canvas()
0121 {
0122     delete d->core;
0123     delete d->canvasItem;
0124     delete d;
0125 }
0126 
0127 void Canvas::resetImage()
0128 {
0129     reset();
0130     d->core->resetImage();
0131 }
0132 
0133 void Canvas::reset()
0134 {
0135     if (d->rubber && d->rubber->isVisible())
0136     {
0137         d->rubber->setVisible(false);
0138 
0139         if (d->core->isValid())
0140         {
0141             Q_EMIT signalSelected(false);
0142         }
0143     }
0144 
0145     addRubber();
0146     d->errorMessage.clear();
0147 }
0148 
0149 void Canvas::load(const QString& filename, IOFileSettings* const IOFileSettings)
0150 {
0151     reset();
0152     Q_EMIT signalPrepareToLoad();
0153     d->core->load(filename, IOFileSettings);
0154 }
0155 
0156 void Canvas::slotImageLoaded(const QString& filePath, bool success)
0157 {
0158     if (d->core->getImg())
0159     {
0160         d->canvasItem->setImage(*d->core->getImg());
0161     }
0162 
0163     // Note: in showFoto, we using a null filename to clear canvas.
0164 
0165     if (!success && !filePath.isEmpty())
0166     {
0167         QFileInfo info(filePath);
0168         d->errorMessage = i18n("Failed to load image\n\"%1\"", info.fileName());
0169     }
0170     else
0171     {
0172         d->errorMessage.clear();
0173     }
0174 
0175     viewport()->update();
0176 
0177     Q_EMIT signalLoadingFinished(filePath, success);
0178 }
0179 
0180 void Canvas::fitToSelect()
0181 {
0182     QRect sel = d->core->getSelectedArea();
0183 
0184     if (!sel.size().isNull())
0185     {
0186         // If selected area, use center of selection
0187         // and recompute zoom factor accordingly.
0188 
0189         double cpx       = sel.x() + sel.width()  / 2.0;
0190         double cpy       = sel.y() + sel.height() / 2.0;
0191         double srcWidth  = sel.width();
0192         double srcHeight = sel.height();
0193         double dstWidth  = contentsRect().width();
0194         double dstHeight = contentsRect().height();
0195         double zoom      = qMin(dstWidth / srcWidth, dstHeight / srcHeight);
0196 
0197         Q_EMIT signalToggleOffFitToWindow();
0198 
0199         layout()->setZoomFactor(zoom);
0200 
0201         centerOn(cpx * zoom, cpy * zoom);
0202         viewport()->update();
0203     }
0204 }
0205 
0206 void Canvas::applyTransform(const IccTransform& t)
0207 {
0208     IccTransform transform(t);
0209 
0210     if (transform.willHaveEffect())
0211     {
0212         d->core->applyTransform(transform);
0213     }
0214     else
0215     {
0216         viewport()->update();
0217     }
0218 }
0219 
0220 void Canvas::preload(const QString& /*filename*/)
0221 {
0222 /*
0223     d->core->preload(filename);
0224 */
0225 }
0226 
0227 void Canvas::slotImageSaved(const QString& filePath, bool success)
0228 {
0229     Q_EMIT signalSavingFinished(filePath, success);
0230 }
0231 
0232 void Canvas::abortSaving()
0233 {
0234     d->core->abortSaving();
0235 }
0236 
0237 void Canvas::setModified()
0238 {
0239     d->core->setModified();
0240 }
0241 
0242 QString Canvas::ensureHasCurrentUuid() const
0243 {
0244     return d->core->ensureHasCurrentUuid();
0245 }
0246 
0247 DImg Canvas::currentImage() const
0248 {
0249     DImg* const image = d->core->getImg();
0250 
0251     if (image)
0252     {
0253         return DImg(*image);
0254     }
0255 
0256     return DImg();
0257 }
0258 
0259 QString Canvas::currentImageFileFormat() const
0260 {
0261     return d->core->getImageFormat();
0262 }
0263 
0264 QString Canvas::currentImageFilePath() const
0265 {
0266     return d->core->getImageFilePath();
0267 }
0268 
0269 int Canvas::imageWidth() const
0270 {
0271     return d->core->origWidth();
0272 }
0273 
0274 int Canvas::imageHeight() const
0275 {
0276     return d->core->origHeight();
0277 }
0278 
0279 bool Canvas::isReadOnly() const
0280 {
0281     return d->core->isReadOnly();
0282 }
0283 
0284 QRect Canvas::getSelectedArea() const
0285 {
0286     return d->core->getSelectedArea();
0287 }
0288 
0289 EditorCore* Canvas::interface() const
0290 {
0291     return d->core;
0292 }
0293 
0294 void Canvas::makeDefaultEditingCanvas()
0295 {
0296     EditorCore::setDefaultInstance(d->core);
0297 }
0298 
0299 bool Canvas::exifRotated() const
0300 {
0301     return d->core->exifRotated();
0302 }
0303 
0304 void Canvas::slotRotate90()
0305 {
0306     d->canvasItem->clearCache();
0307     d->core->rotate90();
0308 }
0309 
0310 void Canvas::slotRotate180()
0311 {
0312     d->canvasItem->clearCache();
0313     d->core->rotate180();
0314 }
0315 
0316 void Canvas::slotRotate270()
0317 {
0318     d->canvasItem->clearCache();
0319     d->core->rotate270();
0320 }
0321 
0322 void Canvas::slotFlipHoriz()
0323 {
0324     d->canvasItem->clearCache();
0325     d->core->flipHoriz();
0326 }
0327 
0328 void Canvas::slotFlipVert()
0329 {
0330     d->canvasItem->clearCache();
0331     d->core->flipVert();
0332 }
0333 
0334 void Canvas::slotCrop()
0335 {
0336     d->canvasItem->clearCache();
0337     QRect sel = d->core->getSelectedArea();
0338 
0339     if (sel.size().isNull())   // No current selection.
0340     {
0341         return;
0342     }
0343 
0344     d->core->crop(sel);
0345 
0346     if (d->rubber && d->rubber->isVisible())
0347     {
0348         d->rubber->setVisible(false);
0349     }
0350 
0351     Q_EMIT signalSelected(false);
0352     addRubber();
0353 }
0354 
0355 void Canvas::setICCSettings(const ICCSettingsContainer& cmSettings)
0356 {
0357     d->canvasItem->clearCache();
0358     d->core->setICCSettings(cmSettings);
0359     viewport()->update();
0360 }
0361 
0362 void Canvas::setSoftProofingEnabled(bool enable)
0363 {
0364     d->canvasItem->clearCache();
0365     d->core->setSoftProofingEnabled(enable);
0366     viewport()->update();
0367 }
0368 
0369 void Canvas::setExposureSettings(ExposureSettingsContainer* const expoSettings)
0370 {
0371     d->canvasItem->clearCache();
0372     d->core->setExposureSettings(expoSettings);
0373     viewport()->update();
0374 }
0375 
0376 void Canvas::setExifOrient(bool exifOrient)
0377 {
0378     d->canvasItem->clearCache();
0379     d->core->setExifOrient(exifOrient);
0380     viewport()->update();
0381 }
0382 
0383 void Canvas::slotRestore()
0384 {
0385     d->core->restore();
0386     viewport()->update();
0387 }
0388 
0389 void Canvas::slotUndo(int steps)
0390 {
0391     Q_EMIT signalUndoSteps(steps);
0392 
0393     d->canvasItem->clearCache();
0394 
0395     while (steps > 0)
0396     {
0397         d->core->undo();
0398         --steps;
0399     }
0400 }
0401 
0402 void Canvas::slotRedo(int steps)
0403 {
0404     Q_EMIT signalRedoSteps(steps);
0405 
0406     d->canvasItem->clearCache();
0407 
0408     while (steps > 0)
0409     {
0410         d->core->redo();
0411         --steps;
0412     }
0413 }
0414 
0415 void Canvas::slotCopy()
0416 {
0417     QRect sel = d->core->getSelectedArea();
0418 
0419     if (sel.size().isNull())   // No current selection.
0420     {
0421         return;
0422     }
0423 
0424     QApplication::setOverrideCursor(Qt::WaitCursor);
0425 
0426     DImg selDImg              = d->core->getImgSelection();
0427     QImage selImg             = selDImg.copyQImage();
0428     QMimeData* const mimeData = new QMimeData();
0429     mimeData->setImageData(selImg);
0430     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
0431 
0432     QApplication::restoreOverrideCursor();
0433 }
0434 
0435 void Canvas::slotSelected()
0436 {
0437     QRect sel = QRect(0, 0, 0, 0);
0438 
0439     if (d->wrapItem)
0440     {
0441         cancelAddItem();
0442         return;
0443     }
0444 
0445     if (d->rubber)
0446     {
0447         sel = calcSelectedArea();
0448     }
0449 
0450     d->core->setSelectedArea(sel);
0451     Q_EMIT signalSelectionChanged(sel);
0452 }
0453 
0454 void Canvas::slotSelectionMoved()
0455 {
0456     QRect sel = QRect(0, 0, 0, 0);
0457 
0458     if (d->rubber)
0459     {
0460         sel = calcSelectedArea();
0461     }
0462 
0463     Q_EMIT signalSelectionSetText(sel);
0464 }
0465 
0466 QRect Canvas::calcSelectedArea() const
0467 {
0468     int    x = 0, y = 0, w = 0, h = 0;
0469     double z = layout()->realZoomFactor();
0470 
0471     if (d->rubber && d->rubber->isVisible())
0472     {
0473         QRect r(d->rubber->boundingRect().toRect());
0474 
0475         if (r.isValid())
0476         {
0477             r.translate((int)d->rubber->x(),
0478                         (int)d->rubber->y());
0479 
0480             x = (int)((double)r.x()      / z);
0481             y = (int)((double)r.y()      / z);
0482             w = (int)((double)r.width()  / z);
0483             h = (int)((double)r.height() / z);
0484 
0485             x = qMin(imageWidth(),  qMax(x, 0));
0486             y = qMin(imageHeight(), qMax(y, 0));
0487             w = qMin(imageWidth(),  qMax(w, 0));
0488             h = qMin(imageHeight(), qMax(h, 0));
0489 
0490             // Avoid empty selection by rubberband - at least mark one pixel
0491             // At high zoom factors, the rubberband may operate at subpixel level!
0492 
0493             if (w == 0)
0494             {
0495                 w = 1;
0496             }
0497 
0498             if (h == 0)
0499             {
0500                 h = 1;
0501             }
0502         }
0503     }
0504 
0505     return QRect(x, y, w, h);
0506 }
0507 
0508 void Canvas::slotModified()
0509 {
0510     d->canvasItem->setImage(currentImage());
0511 
0512     Q_EMIT signalChanged();
0513 }
0514 
0515 void Canvas::slotSelectAll()
0516 {
0517     if (d->rubber)
0518     {
0519         delete d->rubber;
0520     }
0521 
0522     d->rubber = new RubberItem(d->canvasItem);
0523     d->rubber->setCanvas(this);
0524     d->rubber->setRectInSceneCoordinatesAdjusted(d->canvasItem->boundingRect());
0525     viewport()->setMouseTracking(true);
0526     viewport()->update();
0527 
0528     if (d->core->isValid())
0529     {
0530         Q_EMIT signalSelected(true);
0531     }
0532 }
0533 
0534 void Canvas::slotSelectNone()
0535 {
0536     reset();
0537     viewport()->update();
0538 }
0539 
0540 void Canvas::keyPressEvent(QKeyEvent* event)
0541 {
0542     if (!event)
0543     {
0544         return;
0545     }
0546 
0547     int mult = 1;
0548 
0549     if ((event->modifiers() & Qt::ControlModifier))
0550     {
0551         mult = 10;
0552     }
0553 
0554     switch (event->key())
0555     {
0556         case Qt::Key_Right:
0557         {
0558             horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep()*mult);
0559             break;
0560         }
0561 
0562         case Qt::Key_Left:
0563         {
0564             horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep()*mult);
0565             break;
0566         }
0567 
0568         case Qt::Key_Up:
0569         {
0570             verticalScrollBar()->setValue(verticalScrollBar()->value() - verticalScrollBar()->singleStep()*mult);
0571             break;
0572         }
0573 
0574         case Qt::Key_Down:
0575         {
0576             verticalScrollBar()->setValue(verticalScrollBar()->value() + verticalScrollBar()->singleStep()*mult);
0577             break;
0578         }
0579 
0580         default:
0581         {
0582             event->ignore();
0583             break;
0584         }
0585     }
0586 }
0587 
0588 void Canvas::addRubber()
0589 {
0590     if (!d->wrapItem)
0591     {
0592         d->wrapItem = new ClickDragReleaseItem(d->canvasItem);
0593     }
0594 
0595     d->wrapItem->setFocus();
0596     setFocus();
0597 
0598     connect(d->wrapItem, SIGNAL(started(QPointF)),
0599             this, SLOT(slotAddItemStarted(QPointF)));
0600 
0601     connect(d->wrapItem, SIGNAL(moving(QRectF)),
0602             this, SLOT(slotAddItemMoving(QRectF)));
0603 
0604     connect(d->wrapItem, SIGNAL(finished(QRectF)),
0605             this, SLOT(slotAddItemFinished(QRectF)));
0606 
0607     connect(d->wrapItem, SIGNAL(cancelled()),
0608             this, SLOT(cancelAddItem()));
0609 }
0610 
0611 void Canvas::slotAddItemStarted(const QPointF& pos)
0612 {
0613     Q_UNUSED(pos);
0614 }
0615 
0616 void Canvas::slotAddItemMoving(const QRectF& rect)
0617 {
0618     if (d->rubber)
0619     {
0620         delete d->rubber;
0621     }
0622 
0623     d->rubber = new RubberItem(d->canvasItem);
0624     d->rubber->setCanvas(this);
0625     d->rubber->setRectInSceneCoordinatesAdjusted(rect);
0626 }
0627 
0628 void Canvas::slotAddItemFinished(const QRectF& rect)
0629 {
0630     if (d->rubber)
0631     {
0632         d->rubber->setRectInSceneCoordinatesAdjusted(rect);
0633 /*
0634         d->wrapItem->stackBefore(d->canvasItem);
0635 */
0636     }
0637 
0638     cancelAddItem();
0639 }
0640 
0641 void Canvas::cancelAddItem()
0642 {
0643     if (d->wrapItem)
0644     {
0645         this->scene()->removeItem(d->wrapItem);
0646         d->wrapItem->deleteLater();
0647         d->wrapItem = nullptr;
0648     }
0649 
0650     Q_EMIT signalSelected(true);
0651 }
0652 
0653 void Canvas::mousePressEvent(QMouseEvent* event)
0654 {
0655     GraphicsDImgView::mousePressEvent(event);
0656 
0657     if (event->button() == Qt::LeftButton)
0658     {
0659         GraphicsDImgItem* const item = dynamic_cast<GraphicsDImgItem*>(itemAt(event->pos()));
0660 
0661         if (item)
0662         {
0663             QLatin1String className(item->metaObject()->className());
0664 
0665             if (!(className == QLatin1String("Digikam::RubberItem") || className == QLatin1String("Digikam::ClickDragReleaseItem")))
0666             {
0667                 if (d->rubber && d->rubber->isVisible())
0668                 {
0669                     d->rubber->setVisible(false);
0670                 }
0671 
0672                 Q_EMIT signalSelected(false);
0673                 addRubber();
0674             }
0675         }
0676     }
0677 }
0678 
0679 void Canvas::dragEnterEvent(QDragEnterEvent* e)
0680 {
0681     QGraphicsView::dragEnterEvent(e);
0682 
0683     if (e->mimeData()->hasUrls())
0684     {
0685         e->acceptProposedAction();
0686     }
0687 }
0688 
0689 void Canvas::dragMoveEvent(QDragMoveEvent* e)
0690 {
0691     QGraphicsView::dragMoveEvent(e);
0692 
0693     if (e->mimeData()->hasUrls())
0694     {
0695         e->acceptProposedAction();
0696     }
0697 }
0698 
0699 void Canvas::dropEvent(QDropEvent* e)
0700 {
0701     QGraphicsView::dropEvent(e);
0702     Q_EMIT signalAddedDropedItems(e);
0703 }
0704 
0705 } // namespace Digikam
0706 
0707 #include "moc_canvas.cpp"