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