1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2013-08-04
7  * Description : image editor canvas management class
8  *
9  * Copyright (C) 2013-2014 by Yiou Wang <geow812 at gmail dot com>
10  * Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
11  * Copyright (C) 2004-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "canvas.h"
27 
28 // Qt includes
29 
30 #include <QFileInfo>
31 #include <QClipboard>
32 #include <QToolButton>
33 #include <QScrollBar>
34 #include <QMimeData>
35 #include <QApplication>
36 
37 // KDE includes
38 
39 #include <klocalizedstring.h>
40 
41 // Local includes
42 
43 #include "imagehistogram.h"
44 #include "iccsettingscontainer.h"
45 #include "icctransform.h"
46 #include "exposurecontainer.h"
47 #include "iofilesettings.h"
48 #include "loadingcacheinterface.h"
49 #include "imagepreviewitem.h"
50 #include "previewlayout.h"
51 #include "imagezoomsettings.h"
52 #include "clickdragreleaseitem.h"
53 #include "rubberitem.h"
54 
55 namespace Digikam
56 {
57 
58 class Q_DECL_HIDDEN Canvas::Private
59 {
60 
61 public:
62 
Private()63     explicit Private()
64       : canvasItem(nullptr),
65         rubber(nullptr),
66         wrapItem(nullptr),
67         core(nullptr)
68     {
69     }
70 
71     QString               errorMessage;
72 
73     ImagePreviewItem*     canvasItem;
74 
75     RubberItem*           rubber;
76     ClickDragReleaseItem* wrapItem;
77     EditorCore*           core;
78 };
79 
Canvas(QWidget * const parent)80 Canvas::Canvas(QWidget* const parent)
81     : GraphicsDImgView(parent),
82       d(new Private)
83 {
84     d->core       = new EditorCore();
85     d->canvasItem = new ImagePreviewItem;
86     setItem(d->canvasItem);
87 
88     setFrameStyle(QFrame::NoFrame);
89     addRubber();
90     layout()->fitToWindow();
91     installPanIcon();
92 
93     setAcceptDrops(true);
94     viewport()->setAcceptDrops(true);
95 
96     // ------------------------------------------------------------
97 
98     connect(d->core, SIGNAL(signalModified()),
99             this, SLOT(slotModified()));
100 
101     connect(d->core, SIGNAL(signalLoadingStarted(QString)),
102             this, SIGNAL(signalLoadingStarted(QString)));
103 
104     connect(d->core, SIGNAL(signalImageLoaded(QString,bool)),
105             this, SLOT(slotImageLoaded(QString,bool)));
106 
107     connect(d->core, SIGNAL(signalImageSaved(QString,bool)),
108             this, SLOT(slotImageSaved(QString,bool)));
109 
110     connect(d->core, SIGNAL(signalLoadingProgress(QString,float)),
111             this, SIGNAL(signalLoadingProgress(QString,float)));
112 
113     connect(d->core, SIGNAL(signalSavingStarted(QString)),
114             this, SIGNAL(signalSavingStarted(QString)));
115 
116     connect(d->core, SIGNAL(signalSavingProgress(QString,float)),
117             this, SIGNAL(signalSavingProgress(QString,float)));
118 
119     connect(this, SIGNAL(signalSelected(bool)),
120             this, SLOT(slotSelected()));
121 
122     connect(d->canvasItem, SIGNAL(showContextMenu(QGraphicsSceneContextMenuEvent*)),
123             this, SIGNAL(signalRightButtonClicked()));
124 
125     connect(layout(), SIGNAL(zoomFactorChanged(double)),
126             this, SIGNAL(signalZoomChanged(double)));
127 }
128 
~Canvas()129 Canvas::~Canvas()
130 {
131     delete d->core;
132     delete d->canvasItem;
133     delete d;
134 }
135 
resetImage()136 void Canvas::resetImage()
137 {
138     reset();
139     d->core->resetImage();
140 }
141 
reset()142 void Canvas::reset()
143 {
144     if (d->rubber && d->rubber->isVisible())
145     {
146         d->rubber->setVisible(false);
147 
148         if (d->core->isValid())
149         {
150             emit signalSelected(false);
151         }
152     }
153 
154     addRubber();
155     d->errorMessage.clear();
156 }
157 
load(const QString & filename,IOFileSettings * const IOFileSettings)158 void Canvas::load(const QString& filename, IOFileSettings* const IOFileSettings)
159 {
160     reset();
161     emit signalPrepareToLoad();
162     d->core->load(filename, IOFileSettings);
163 }
164 
slotImageLoaded(const QString & filePath,bool success)165 void Canvas::slotImageLoaded(const QString& filePath, bool success)
166 {
167     if (d->core->getImg())
168     {
169         d->canvasItem->setImage(*d->core->getImg());
170     }
171 
172     // Note: in showFoto, we using a null filename to clear canvas.
173 
174     if (!success && !filePath.isEmpty())
175     {
176         QFileInfo info(filePath);
177         d->errorMessage = i18n("Failed to load image\n\"%1\"", info.fileName());
178     }
179     else
180     {
181         d->errorMessage.clear();
182     }
183 
184     viewport()->update();
185 
186     emit signalLoadingFinished(filePath, success);
187 }
188 
fitToSelect()189 void Canvas::fitToSelect()
190 {
191     QRect sel = d->core->getSelectedArea();
192 
193     if (!sel.size().isNull())
194     {
195         // If selected area, use center of selection
196         // and recompute zoom factor accordingly.
197 
198         double cpx       = sel.x() + sel.width()  / 2.0;
199         double cpy       = sel.y() + sel.height() / 2.0;
200         double srcWidth  = sel.width();
201         double srcHeight = sel.height();
202         double dstWidth  = contentsRect().width();
203         double dstHeight = contentsRect().height();
204         double zoom      = qMin(dstWidth / srcWidth, dstHeight / srcHeight);
205 
206         emit signalToggleOffFitToWindow();
207 
208         layout()->setZoomFactor(zoom);
209 
210         centerOn(cpx * zoom, cpy * zoom);
211         viewport()->update();
212     }
213 }
214 
applyTransform(const IccTransform & t)215 void Canvas::applyTransform(const IccTransform& t)
216 {
217     IccTransform transform(t);
218 
219     if (transform.willHaveEffect())
220     {
221         d->core->applyTransform(transform);
222     }
223     else
224     {
225         viewport()->update();
226     }
227 }
228 
preload(const QString &)229 void Canvas::preload(const QString& /*filename*/)
230 {
231 /*
232     d->core->preload(filename);
233 */
234 }
235 
slotImageSaved(const QString & filePath,bool success)236 void Canvas::slotImageSaved(const QString& filePath, bool success)
237 {
238     emit signalSavingFinished(filePath, success);
239 }
240 
abortSaving()241 void Canvas::abortSaving()
242 {
243     d->core->abortSaving();
244 }
245 
setModified()246 void Canvas::setModified()
247 {
248     d->core->setModified();
249 }
250 
ensureHasCurrentUuid() const251 QString Canvas::ensureHasCurrentUuid() const
252 {
253     return d->core->ensureHasCurrentUuid();
254 }
255 
currentImage() const256 DImg Canvas::currentImage() const
257 {
258     DImg* const image = d->core->getImg();
259 
260     if (image)
261     {
262         return DImg(*image);
263     }
264 
265     return DImg();
266 }
267 
currentImageFileFormat() const268 QString Canvas::currentImageFileFormat() const
269 {
270     return d->core->getImageFormat();
271 }
272 
currentImageFilePath() const273 QString Canvas::currentImageFilePath() const
274 {
275     return d->core->getImageFilePath();
276 }
277 
imageWidth() const278 int Canvas::imageWidth() const
279 {
280     return d->core->origWidth();
281 }
282 
imageHeight() const283 int Canvas::imageHeight() const
284 {
285     return d->core->origHeight();
286 }
287 
isReadOnly() const288 bool Canvas::isReadOnly() const
289 {
290     return d->core->isReadOnly();
291 }
292 
getSelectedArea() const293 QRect Canvas::getSelectedArea() const
294 {
295     return d->core->getSelectedArea();
296 }
297 
interface() const298 EditorCore* Canvas::interface() const
299 {
300     return d->core;
301 }
302 
makeDefaultEditingCanvas()303 void Canvas::makeDefaultEditingCanvas()
304 {
305     EditorCore::setDefaultInstance(d->core);
306 }
307 
exifRotated() const308 bool Canvas::exifRotated() const
309 {
310     return d->core->exifRotated();
311 }
312 
slotRotate90()313 void Canvas::slotRotate90()
314 {
315     d->canvasItem->clearCache();
316     d->core->rotate90();
317 }
318 
slotRotate180()319 void Canvas::slotRotate180()
320 {
321     d->canvasItem->clearCache();
322     d->core->rotate180();
323 }
324 
slotRotate270()325 void Canvas::slotRotate270()
326 {
327     d->canvasItem->clearCache();
328     d->core->rotate270();
329 }
330 
slotFlipHoriz()331 void Canvas::slotFlipHoriz()
332 {
333     d->canvasItem->clearCache();
334     d->core->flipHoriz();
335 }
336 
slotFlipVert()337 void Canvas::slotFlipVert()
338 {
339     d->canvasItem->clearCache();
340     d->core->flipVert();
341 }
342 
slotCrop()343 void Canvas::slotCrop()
344 {
345     d->canvasItem->clearCache();
346     QRect sel = d->core->getSelectedArea();
347 
348     if (sel.size().isNull())   // No current selection.
349     {
350         return;
351     }
352 
353     d->core->crop(sel);
354 
355     if (d->rubber && d->rubber->isVisible())
356     {
357         d->rubber->setVisible(false);
358     }
359 
360     emit signalSelected(false);
361     addRubber();
362 }
363 
setICCSettings(const ICCSettingsContainer & cmSettings)364 void Canvas::setICCSettings(const ICCSettingsContainer& cmSettings)
365 {
366     d->canvasItem->clearCache();
367     d->core->setICCSettings(cmSettings);
368     viewport()->update();
369 }
370 
setSoftProofingEnabled(bool enable)371 void Canvas::setSoftProofingEnabled(bool enable)
372 {
373     d->canvasItem->clearCache();
374     d->core->setSoftProofingEnabled(enable);
375     viewport()->update();
376 }
377 
setExposureSettings(ExposureSettingsContainer * const expoSettings)378 void Canvas::setExposureSettings(ExposureSettingsContainer* const expoSettings)
379 {
380     d->canvasItem->clearCache();
381     d->core->setExposureSettings(expoSettings);
382     viewport()->update();
383 }
384 
setExifOrient(bool exifOrient)385 void Canvas::setExifOrient(bool exifOrient)
386 {
387     d->canvasItem->clearCache();
388     d->core->setExifOrient(exifOrient);
389     viewport()->update();
390 }
391 
slotRestore()392 void Canvas::slotRestore()
393 {
394     d->core->restore();
395     viewport()->update();
396 }
397 
slotUndo(int steps)398 void Canvas::slotUndo(int steps)
399 {
400     emit signalUndoSteps(steps);
401 
402     d->canvasItem->clearCache();
403 
404     while (steps > 0)
405     {
406         d->core->undo();
407         --steps;
408     }
409 }
410 
slotRedo(int steps)411 void Canvas::slotRedo(int steps)
412 {
413     emit signalRedoSteps(steps);
414 
415     d->canvasItem->clearCache();
416 
417     while (steps > 0)
418     {
419         d->core->redo();
420         --steps;
421     }
422 }
423 
slotCopy()424 void Canvas::slotCopy()
425 {
426     QRect sel = d->core->getSelectedArea();
427 
428     if (sel.size().isNull())   // No current selection.
429     {
430         return;
431     }
432 
433     QApplication::setOverrideCursor(Qt::WaitCursor);
434 
435     DImg selDImg              = d->core->getImgSelection();
436     QImage selImg             = selDImg.copyQImage();
437     QMimeData* const mimeData = new QMimeData();
438     mimeData->setImageData(selImg);
439     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
440 
441     QApplication::restoreOverrideCursor();
442 }
443 
slotSelected()444 void Canvas::slotSelected()
445 {
446     QRect sel = QRect(0, 0, 0, 0);
447 
448     if (d->wrapItem)
449     {
450         cancelAddItem();
451         return;
452     }
453 
454     if (d->rubber)
455     {
456         sel = calcSelectedArea();
457     }
458 
459     d->core->setSelectedArea(sel);
460     emit signalSelectionChanged(sel);
461 }
462 
slotSelectionMoved()463 void Canvas::slotSelectionMoved()
464 {
465     QRect sel = QRect(0, 0, 0, 0);
466 
467     if (d->rubber)
468     {
469         sel = calcSelectedArea();
470     }
471 
472     emit signalSelectionSetText(sel);
473 }
474 
calcSelectedArea() const475 QRect Canvas::calcSelectedArea() const
476 {
477     int    x = 0, y = 0, w = 0, h = 0;
478     double z = layout()->realZoomFactor();
479 
480     if (d->rubber && d->rubber->isVisible())
481     {
482         QRect r(d->rubber->boundingRect().toRect());
483 
484         if (r.isValid())
485         {
486             r.translate((int)d->rubber->x(),
487                         (int)d->rubber->y());
488 
489             x = (int)((double)r.x()      / z);
490             y = (int)((double)r.y()      / z);
491             w = (int)((double)r.width()  / z);
492             h = (int)((double)r.height() / z);
493 
494             x = qMin(imageWidth(),  qMax(x, 0));
495             y = qMin(imageHeight(), qMax(y, 0));
496             w = qMin(imageWidth(),  qMax(w, 0));
497             h = qMin(imageHeight(), qMax(h, 0));
498 
499             // Avoid empty selection by rubberband - at least mark one pixel
500             // At high zoom factors, the rubberband may operate at subpixel level!
501 
502             if (w == 0)
503             {
504                 w = 1;
505             }
506 
507             if (h == 0)
508             {
509                 h = 1;
510             }
511         }
512     }
513 
514     return QRect(x, y, w, h);
515 }
516 
slotModified()517 void Canvas::slotModified()
518 {
519     d->canvasItem->setImage(currentImage());
520 
521     emit signalChanged();
522 }
523 
slotSelectAll()524 void Canvas::slotSelectAll()
525 {
526     if (d->rubber)
527     {
528         delete d->rubber;
529     }
530 
531     d->rubber = new RubberItem(d->canvasItem);
532     d->rubber->setCanvas(this);
533     d->rubber->setRectInSceneCoordinatesAdjusted(d->canvasItem->boundingRect());
534     viewport()->setMouseTracking(true);
535     viewport()->update();
536 
537     if (d->core->isValid())
538     {
539         emit signalSelected(true);
540     }
541 }
542 
slotSelectNone()543 void Canvas::slotSelectNone()
544 {
545     reset();
546     viewport()->update();
547 }
548 
keyPressEvent(QKeyEvent * event)549 void Canvas::keyPressEvent(QKeyEvent* event)
550 {
551     if (!event)
552     {
553         return;
554     }
555 
556     int mult = 1;
557 
558     if ((event->modifiers() & Qt::ControlModifier))
559     {
560         mult = 10;
561     }
562 
563     switch (event->key())
564     {
565         case Qt::Key_Right:
566         {
567             horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep()*mult);
568             break;
569         }
570 
571         case Qt::Key_Left:
572         {
573             horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep()*mult);
574             break;
575         }
576 
577         case Qt::Key_Up:
578         {
579             verticalScrollBar()->setValue(verticalScrollBar()->value() - verticalScrollBar()->singleStep()*mult);
580             break;
581         }
582 
583         case Qt::Key_Down:
584         {
585             verticalScrollBar()->setValue(verticalScrollBar()->value() + verticalScrollBar()->singleStep()*mult);
586             break;
587         }
588 
589         default:
590         {
591             event->ignore();
592             break;
593         }
594     }
595 }
596 
addRubber()597 void Canvas::addRubber()
598 {
599     if (!d->wrapItem)
600     {
601         d->wrapItem = new ClickDragReleaseItem(d->canvasItem);
602     }
603 
604     d->wrapItem->setFocus();
605     setFocus();
606 
607     connect(d->wrapItem, SIGNAL(started(QPointF)),
608             this, SLOT(slotAddItemStarted(QPointF)));
609 
610     connect(d->wrapItem, SIGNAL(moving(QRectF)),
611             this, SLOT(slotAddItemMoving(QRectF)));
612 
613     connect(d->wrapItem, SIGNAL(finished(QRectF)),
614             this, SLOT(slotAddItemFinished(QRectF)));
615 
616     connect(d->wrapItem, SIGNAL(cancelled()),
617             this, SLOT(cancelAddItem()));
618 }
619 
slotAddItemStarted(const QPointF & pos)620 void Canvas::slotAddItemStarted(const QPointF& pos)
621 {
622     Q_UNUSED(pos);
623 }
624 
slotAddItemMoving(const QRectF & rect)625 void Canvas::slotAddItemMoving(const QRectF& rect)
626 {
627     if (d->rubber)
628     {
629         delete d->rubber;
630     }
631 
632     d->rubber = new RubberItem(d->canvasItem);
633     d->rubber->setCanvas(this);
634     d->rubber->setRectInSceneCoordinatesAdjusted(rect);
635 }
636 
slotAddItemFinished(const QRectF & rect)637 void Canvas::slotAddItemFinished(const QRectF& rect)
638 {
639     if (d->rubber)
640     {
641         d->rubber->setRectInSceneCoordinatesAdjusted(rect);
642 /*
643         d->wrapItem->stackBefore(d->canvasItem);
644 */
645     }
646 
647     cancelAddItem();
648 }
649 
cancelAddItem()650 void Canvas::cancelAddItem()
651 {
652     if (d->wrapItem)
653     {
654         this->scene()->removeItem(d->wrapItem);
655         d->wrapItem->deleteLater();
656         d->wrapItem = nullptr;
657     }
658 
659     emit signalSelected(true);
660 }
661 
mousePressEvent(QMouseEvent * event)662 void Canvas::mousePressEvent(QMouseEvent* event)
663 {
664     GraphicsDImgView::mousePressEvent(event);
665 
666     if (event->button() == Qt::LeftButton)
667     {
668         GraphicsDImgItem* const item = dynamic_cast<GraphicsDImgItem*>(itemAt(event->pos()));
669 
670         if (item)
671         {
672             QLatin1String className(item->metaObject()->className());
673 
674             if (!(className == QLatin1String("Digikam::RubberItem") || className == QLatin1String("Digikam::ClickDragReleaseItem")))
675             {
676                 if (d->rubber && d->rubber->isVisible())
677                 {
678                     d->rubber->setVisible(false);
679                 }
680 
681                 emit signalSelected(false);
682                 addRubber();
683             }
684         }
685     }
686 }
687 
dragEnterEvent(QDragEnterEvent * e)688 void Canvas::dragEnterEvent(QDragEnterEvent* e)
689 {
690     QGraphicsView::dragEnterEvent(e);
691 
692     if (e->mimeData()->hasUrls())
693     {
694         e->acceptProposedAction();
695     }
696 }
697 
dragMoveEvent(QDragMoveEvent * e)698 void Canvas::dragMoveEvent(QDragMoveEvent* e)
699 {
700     QGraphicsView::dragMoveEvent(e);
701 
702     if (e->mimeData()->hasUrls())
703     {
704         e->acceptProposedAction();
705     }
706 }
707 
dropEvent(QDropEvent * e)708 void Canvas::dropEvent(QDropEvent* e)
709 {
710     QGraphicsView::dropEvent(e);
711     emit signalAddedDropedItems(e);
712 }
713 
714 } // namespace Digikam
715