1 /************************************************************************
2 * *
3 * This file is part of libkscan, a KDE scanning library. *
4 * *
5 * Copyright (C) 2013 Jonathan Marten <jjm@keelhaul.me.uk> *
6 * Copyright (C) 1999 Klaas Freitag <freitag@suse.de> *
7 * *
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU Library General Public *
10 * License as published by the Free Software Foundation and appearing *
11 * in the file COPYING included in the packaging of this file; *
12 * either version 2 of the License, or (at your option) any later *
13 * version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; see the file COPYING. If not, write to *
22 * the Free Software Foundation, Inc., 51 Franklin Street, *
23 * Fifth Floor, Boston, MA 02110-1301, USA. *
24 * *
25 ************************************************************************/
26
27 #include "imagecanvas.h"
28
29 #include <QGraphicsScene>
30 #include <QGraphicsPixmapItem>
31 #include <QTransform>
32
33 #include <qimage.h>
34 #include <qpainter.h>
35 #include <qstyle.h>
36 #include <qevent.h>
37 #include <qapplication.h>
38
39 #include <KLocalizedString>
40 #include <QDebug>
41 #include <QMenu>
42
43 #include "imgscaledialog.h"
44
45 // Parameters for display and selection
46 // ------------------------------------
47
48 #undef HOLD_SELECTION // hold selection over new image
49
50 const int MIN_AREA_WIDTH = 2; // minimum size of selection area
51 const int MIN_AREA_HEIGHT = 2;
52 const int DELTA = 3; // tolerance for mouse hits
53 const int TIMER_INTERVAL = 100; // animation timer interval
54
55 const int DASH_DASH = 4; // length of drawn dashes
56 const int DASH_SPACE = 4; // length of drawn spaces
57
58 const int DASH_LENGTH = (DASH_DASH + DASH_SPACE);
59
60 // HighlightItem -- A graphics item to maintain and draw a single
61 // highlight rectangle.
62
63 class HighlightItem : public QGraphicsItem
64 {
65 public:
66 HighlightItem(const QRect &rect,
67 ImageCanvas::HighlightStyle style,
68 const QPen &pen, const QBrush &brush,
69 QGraphicsItem *parent = nullptr);
~HighlightItem()70 virtual ~HighlightItem() {}
71
72 QRectF boundingRect() const override;
73
74 virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
75 QWidget *widget = nullptr) override;
76
77 private:
78 QRectF mRectangle;
79
80 ImageCanvas::HighlightStyle mStyle;
81 QPen mPen;
82 QBrush mBrush;
83 };
84
HighlightItem(const QRect & rect,ImageCanvas::HighlightStyle style,const QPen & pen,const QBrush & brush,QGraphicsItem * parent)85 HighlightItem::HighlightItem(const QRect &rect,
86 ImageCanvas::HighlightStyle style,
87 const QPen &pen, const QBrush &brush,
88 QGraphicsItem *parent)
89 : QGraphicsItem(parent)
90 {
91 mRectangle = rect;
92 mStyle = style;
93 mPen = pen;
94 mBrush = brush;
95
96 mPen.setCosmetic(true);
97 }
98
boundingRect() const99 QRectF HighlightItem::boundingRect() const
100 {
101 return (mRectangle);
102 }
103
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)104 void HighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
105 {
106 painter->setPen(mPen);
107 painter->setBrush(mBrush);
108
109 if (mStyle == ImageCanvas::HighlightBox) {
110 painter->drawRect(mRectangle);
111 } else painter->drawLine(mRectangle.left(), mRectangle.bottom(),
112 mRectangle.right(), mRectangle.bottom());
113 }
114
115 // SelectionItem -- A graphics item to maintain and draw the
116 // selection rectangle.
117 //
118 // There is only one of these items on the canvas. Its bounding
119 // rectangle, stored here in scene coordinates (i.e. image pixels)
120 // is the master reference for the selection rectangle.
121
122 class SelectionItem : public QGraphicsItem
123 {
124 public:
125 explicit SelectionItem(QGraphicsItem *parent = nullptr);
~SelectionItem()126 ~SelectionItem() override {}
127
128 QRectF boundingRect() const override;
129 void setRect(const QRectF &rect);
130
131 void stepDashPattern();
132 void resetDashPattern();
133
134 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
135 QWidget *widget = nullptr) override;
136
137 private:
138 QRectF mRectangle;
139 int mDashOffset;
140 };
141
SelectionItem(QGraphicsItem * parent)142 SelectionItem::SelectionItem(QGraphicsItem *parent)
143 : QGraphicsItem(parent)
144 {
145 //qDebug();
146
147 mDashOffset = 0;
148 }
149
boundingRect() const150 QRectF SelectionItem::boundingRect() const
151 {
152 return (mRectangle);
153 }
154
setRect(const QRectF & rect)155 void SelectionItem::setRect(const QRectF &rect)
156 {
157 prepareGeometryChange();
158 mRectangle = rect;
159 }
160
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)161 void SelectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
162 {
163 painter->setBrush(QBrush());
164
165 QPen pen1(Qt::white); // solid white box behind
166 painter->setPen(pen1);
167 painter->drawRect(mRectangle);
168
169 QPen pen2(Qt::CustomDashLine); // dashed black box on top
170 QVector<qreal> dashes(2);
171 dashes[0] = DASH_DASH;
172 dashes[1] = DASH_SPACE;
173 pen2.setDashPattern(dashes);
174 pen2.setDashOffset(mDashOffset);
175 painter->setPen(pen2);
176 painter->drawRect(mRectangle);
177 }
178
179 // Counting backwards so that the "ants" march clockwise
180 // (at least with the current Qt painting implementation)
stepDashPattern()181 void SelectionItem::stepDashPattern()
182 {
183 --mDashOffset;
184 if (mDashOffset < 0) {
185 mDashOffset = (DASH_LENGTH - 1);
186 }
187 update();
188 }
189
resetDashPattern()190 void SelectionItem::resetDashPattern()
191 {
192 mDashOffset = 0;
193 }
194
195 // ImageCanvas -- Scrolling area containing the pixmap/highlight widget.
196 // Selected areas are drawn over the top of that.
197
ImageCanvas(QWidget * parent,const QImage * start_image)198 ImageCanvas::ImageCanvas(QWidget *parent, const QImage *start_image)
199 : QGraphicsView(parent)
200 {
201 setObjectName("ImageCanvas");
202
203 //qDebug();
204
205 mContextMenu = nullptr;
206 mTimerId = 0;
207
208 mKeepZoom = false;
209 mReadOnly = false;
210 mScaleType = ImageCanvas::ScaleUnspecified;
211 mDefaultScaleType = ImageCanvas::ScaleOriginal;
212 mScaleFactor = 100; // means original size
213 mMaintainAspect = true;
214
215 setAlignment(Qt::AlignLeft | Qt::AlignTop);
216
217 mScene = new QGraphicsScene(this);
218 setScene(mScene);
219
220 mPixmapItem = new QGraphicsPixmapItem;
221 mPixmapItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
222 mScene->addItem(mPixmapItem);
223
224 mSelectionItem = new SelectionItem;
225 mSelectionItem->setVisible(false); // not displayed yet
226 mScene->addItem(mSelectionItem);
227
228 mMoving = ImageCanvas::MoveNone;
229 mCurrentCursor = Qt::ArrowCursor;
230
231 newImage(start_image);
232
233 setCursorShape(Qt::CrossCursor);
234 setMouseTracking(true);
235
236 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
237 show();
238 }
239
~ImageCanvas()240 ImageCanvas::~ImageCanvas()
241 {
242 //qDebug();
243 stopMarqueeTimer();
244 }
245
246 // Setting the image
247 // -----------------
248
newImage(const QImage * new_image,bool hold_zoom)249 void ImageCanvas::newImage(const QImage *new_image, bool hold_zoom)
250 {
251 mImage = new_image; // don't free old image, not ours
252
253 #ifdef HOLD_SELECTION
254 QRect oldSelected = mSelected;
255 //qDebug() << "original selection" << oldSelected << "null=" << oldSelected.isEmpty();
256 //qDebug() << " w=" << mSelected.width() << "h=" << mSelected.height();
257 #endif
258
259 stopMarqueeTimer(); // also clears selection
260
261 if (mImage != nullptr) { // handle the new image
262 //qDebug() << "new image size is" << mImage->size();
263 mPixmapItem->setPixmap(QPixmap::fromImage(*mImage));
264 setSceneRect(mPixmapItem->boundingRect()); // image always defines size
265
266 if (!mKeepZoom && !hold_zoom) {
267 setScaleType(defaultScaleType());
268 }
269
270 #ifdef HOLD_SELECTION
271 if (!oldSelected.isNull()) {
272 mSelected = oldSelected;
273 //qDebug() << "restored selection" << mSelected;
274 startMarqueeTimer();
275 }
276 #endif
277 } else {
278 //qDebug() << "no new image";
279 mPixmapItem->setPixmap(QPixmap());
280 }
281
282 recalculateViewScale();
283 }
284
285 // Context menu
286 // ------------
287 //
288 // We don't actually populate the menu or handle its actions here.
289 // The menu is populated with the parent application's actions via
290 // KookaView::connectViewerAction(), and the application handles the
291 // triggered actions. Those which need some work from us are passed to
292 // slotUserAction() with the appropriate UserAction key.
293
contextMenu()294 QMenu *ImageCanvas::contextMenu()
295 {
296 if (mContextMenu == nullptr) { // menu not created yet
297 mContextMenu = new QMenu(this);
298 //qDebug() << "context menu enabled";
299 }
300
301 return (mContextMenu);
302 }
303
contextMenuEvent(QContextMenuEvent * ev)304 void ImageCanvas::contextMenuEvent(QContextMenuEvent *ev)
305 {
306 if (mContextMenu == nullptr) {
307 return; // menu not enabled
308 }
309 // but allowed if no image loaded
310 mContextMenu->popup(ev->globalPos());
311 ev->accept();
312 }
313
performUserAction(ImageCanvas::UserAction act)314 void ImageCanvas::performUserAction(ImageCanvas::UserAction act)
315 {
316 if (mImage == nullptr) {
317 return; // no action if no image loaded
318 }
319
320 switch (act) {
321 case ImageCanvas::UserActionZoom: {
322 ImgScaleDialog zoomDia(this, mScaleFactor);
323 if (zoomDia.exec()) {
324 int sf = zoomDia.getSelected();
325 setScaleType(ImageCanvas::ScaleZoom);
326 setScaleFactor(sf);
327 }
328 }
329 break;
330
331 case ImageCanvas::UserActionOrigSize:
332 setScaleType(ImageCanvas::ScaleOriginal);
333 break;
334
335 case ImageCanvas::UserActionFitWidth:
336 setScaleType(ImageCanvas::ScaleFitWidth);
337 break;
338
339 case ImageCanvas::UserActionFitHeight:
340 setScaleType(ImageCanvas::ScaleFitHeight);
341 break;
342
343 case ImageCanvas::UserActionClose:
344 emit closingRequested();
345 return;
346 }
347
348 recalculateViewScale();
349 }
350
351 // Selected rectangle
352 // ------------------
353
hasSelectedRect() const354 bool ImageCanvas::hasSelectedRect() const
355 {
356 if (!hasImage()) {
357 return (false);
358 }
359 return (mSelectionItem->isVisible() && mSelectionItem->boundingRect().isValid());
360 }
361
362 // Get the selected area in absolute image pixels.
selectedRect() const363 QRect ImageCanvas::selectedRect() const
364 {
365 if (!hasSelectedRect()) {
366 return (QRect());
367 }
368 return (mSelectionItem->boundingRect().toRect());
369 }
370
371 // Get the selected area as a proportion of the image size.
selectedRectF() const372 QRectF ImageCanvas::selectedRectF() const
373 {
374 if (!hasSelectedRect()) {
375 return (QRectF());
376 }
377 const QRectF r = mSelectionItem->boundingRect();
378
379 QRectF retval;
380 retval.setLeft(r.left() / mImage->width());
381 retval.setRight(r.right() / mImage->width());
382 retval.setTop(r.top() / mImage->height());
383 retval.setBottom(r.bottom() / mImage->height());
384 return (retval);
385 }
386
387 // Set the selected area in absolute image pixels.
setSelectionRect(const QRect & rect)388 void ImageCanvas::setSelectionRect(const QRect &rect)
389 {
390 //qDebug() << "rect=" << rect;
391 if (!hasImage()) {
392 return;
393 }
394
395 if (!rect.isValid()) {
396 stopMarqueeTimer(); // clear the selection
397 } else { // set the selection
398 mSelectionItem->setRect(rect);
399 startMarqueeTimer();
400 }
401 }
402
403 // Set the selected area as a proportion of the image size.
setSelectionRect(const QRectF & rect)404 void ImageCanvas::setSelectionRect(const QRectF &rect)
405 {
406 //qDebug() << "rect=" << rect;
407 if (!hasImage()) {
408 return;
409 }
410
411 if (!rect.isValid()) {
412 stopMarqueeTimer(); // clear the selection
413 } else { // set the selection
414 QRectF setval;
415 setval.setLeft(rect.left()*mImage->width());
416 setval.setRight(rect.right()*mImage->width());
417 setval.setTop(rect.top()*mImage->height());
418 setval.setBottom(rect.bottom()*mImage->height());
419
420 mSelectionItem->setRect(setval);
421 startMarqueeTimer();
422 }
423 }
424
425 // Selected image
426 // --------------
427
selectedImage() const428 QImage ImageCanvas::selectedImage() const
429 {
430 if (!hasImage()) {
431 return (QImage());
432 }
433 // no image available
434 const QRect r = selectedRect();
435 if (!r.isValid()) {
436 return (QImage()); // no selection
437 }
438 return (mImage->copy(r)); // extract and return selection
439 }
440
441 // Animation timer
442 // ---------------
443
timerEvent(QTimerEvent *)444 void ImageCanvas::timerEvent(QTimerEvent *)
445 {
446 if (!hasImage()) {
447 return; // no image acquired
448 }
449 if (!isVisible()) {
450 return; // we're not visible
451 }
452 if (mMoving != ImageCanvas::MoveNone) {
453 return; // mouse operation in progress
454 }
455
456 mSelectionItem->stepDashPattern();
457 }
458
startMarqueeTimer()459 void ImageCanvas::startMarqueeTimer()
460 {
461 if (mTimerId == 0) {
462 mTimerId = startTimer(TIMER_INTERVAL);
463 }
464 mSelectionItem->setVisible(true);
465 }
466
stopMarqueeTimer()467 void ImageCanvas::stopMarqueeTimer()
468 {
469 if (mTimerId != 0) {
470 killTimer(mTimerId);
471 mTimerId = 0;
472 }
473
474 mSelectionItem->setVisible(false); // clear the selection
475 mSelectionItem->resetDashPattern();
476 }
477
478 // Mouse events
479 // ------------
480
mousePressEvent(QMouseEvent * ev)481 void ImageCanvas::mousePressEvent(QMouseEvent *ev)
482 {
483 if (mReadOnly) {
484 return; // only if permitted
485 }
486 if (ev->button() != Qt::LeftButton) {
487 return; // only action this button
488 }
489 if (mMoving != ImageCanvas::MoveNone) {
490 return; // something already in progress
491 }
492 if (!hasImage()) {
493 return; // no image displayed
494 }
495
496 const QList<QGraphicsItem *> its = items(ev->pos());
497 if (its.isEmpty()) {
498 return; // not over any item
499 }
500 if (its.last() != mPixmapItem) {
501 return; // not within the image
502 }
503
504 mMoving = classifyPoint(ev->pos()); // see where hit happened
505 if (mMoving == ImageCanvas::MoveNone) { // starting new area
506 QPoint p = mapToScene(ev->pos()).toPoint(); // position in scene coords
507 mStartPoint = p;
508 mLastPoint = p;
509
510 mSelectionItem->setRect(QRectF(p.x(), p.y(), 0, 0));
511 mSelectionItem->setVisible(true);
512 mMoving = ImageCanvas::MoveNew;
513 }
514 }
515
mouseReleaseEvent(QMouseEvent * ev)516 void ImageCanvas::mouseReleaseEvent(QMouseEvent *ev)
517 {
518 if (mReadOnly) {
519 return; // only if permitted
520 }
521 if (ev->button() != Qt::LeftButton) {
522 return; // only action this button
523 }
524 if (mMoving == ImageCanvas::MoveNone) {
525 return; // nothing in progress
526 }
527 if (!hasImage()) {
528 return; // no image displayed
529 }
530
531 mMoving = ImageCanvas::MoveNone;
532
533 QRect selected = selectedRect();
534 //qDebug() << "selected rect" << selected;
535 if (selected.width() < MIN_AREA_WIDTH || selected.height() < MIN_AREA_HEIGHT) {
536 //qDebug() << "no selection";
537 stopMarqueeTimer(); // also hides selection
538 emit newRect(QRect());
539 emit newRect(QRectF());
540 } else {
541 //qDebug() << "have selection";
542 startMarqueeTimer();
543 emit newRect(selectedRect());
544 emit newRect(selectedRectF());
545 }
546
547 mouseMoveEvent(ev); // update cursor shape
548 }
549
mouseMoveEvent(QMouseEvent * ev)550 void ImageCanvas::mouseMoveEvent(QMouseEvent *ev)
551 {
552 if (mReadOnly) {
553 return; // only if permitted
554 }
555 if (!hasImage()) {
556 return; // no image displayed
557 }
558
559 int x = ev->pos().x(); // mouse position
560 int y = ev->pos().y(); // in view coordinates
561
562 int lx = mImage->width(); // limits for moved rectangle
563 int ly = mImage->height(); // in scene coordinates
564
565 QPoint pixExtent = mapFromScene(lx, ly); // those same limits
566 int ix = pixExtent.x(); // in view coordinates
567 int iy = pixExtent.y();
568
569 // Limit drag rectangle to the scaled pixmap
570 if (x < 0) {
571 x = 0;
572 }
573 if (x >= ix) {
574 x = ix - 1;
575 }
576 if (y < 0) {
577 y = 0;
578 }
579 if (y >= iy) {
580 y = iy - 1;
581 }
582
583 QPoint scenePos = mapToScene(x, y).toPoint(); // that limited position
584 int sx = scenePos.x(); // in scene coordinates
585 int sy = scenePos.y();
586
587 ////qDebug() << x << y << "moving" << mMoving;
588
589 // Set the cursor shape appropriately
590 switch (mMoving != ImageCanvas::MoveNone ? mMoving : classifyPoint(QPoint(x, y))) {
591 case ImageCanvas::MoveNone:
592 setCursorShape(Qt::CrossCursor);
593 break;
594
595 case ImageCanvas::MoveLeft:
596 case ImageCanvas::MoveRight:
597 setCursorShape(Qt::SizeHorCursor);
598 break;
599
600 case ImageCanvas::MoveTop:
601 case ImageCanvas::MoveBottom:
602 setCursorShape(Qt::SizeVerCursor);
603 break;
604
605 case ImageCanvas::MoveTopLeft:
606 case ImageCanvas::MoveBottomRight:
607 setCursorShape(Qt::SizeFDiagCursor);
608 break;
609
610 case ImageCanvas::MoveTopRight:
611 case ImageCanvas::MoveBottomLeft:
612 setCursorShape(Qt::SizeBDiagCursor);
613 break;
614
615 case ImageCanvas::MoveWhole:
616 setCursorShape(Qt::SizeAllCursor);
617 break;
618
619 case ImageCanvas::MoveNew:
620 setCursorShape(Qt::ArrowCursor);
621 break;
622 }
623
624 // Update the selection rectangle
625 if (mMoving != ImageCanvas::MoveNone) {
626 const QRectF originalRect = mSelectionItem->boundingRect();
627 QRectF updatedRect = originalRect;
628 switch (mMoving) {
629 case ImageCanvas::MoveNone:
630 break;
631
632 case ImageCanvas::MoveTopLeft:
633 if (sx < originalRect.right()) {
634 updatedRect.setLeft(sx);
635 }
636 if (sy < originalRect.bottom()) {
637 updatedRect.setTop(sy);
638 }
639 break;
640
641 case ImageCanvas::MoveTop:
642 if (sy < originalRect.bottom()) {
643 updatedRect.setTop(sy);
644 }
645 break;
646
647 case ImageCanvas::MoveTopRight:
648 if (sx > originalRect.left()) {
649 updatedRect.setRight(sx);
650 }
651 if (sy < originalRect.bottom()) {
652 updatedRect.setTop(sy);
653 }
654 break;
655
656 case ImageCanvas::MoveRight:
657 if (sx > originalRect.left()) {
658 updatedRect.setRight(sx);
659 }
660 break;
661
662 case ImageCanvas::MoveBottomRight:
663 if (sx > originalRect.left()) {
664 updatedRect.setRight(sx);
665 }
666 if (sy > originalRect.top()) {
667 updatedRect.setBottom(sy);
668 }
669 break;
670
671 case ImageCanvas::MoveBottom:
672 if (sy > originalRect.top()) {
673 updatedRect.setBottom(sy);
674 }
675 break;
676
677 case ImageCanvas::MoveBottomLeft:
678 if (sx < originalRect.right()) {
679 updatedRect.setLeft(sx);
680 }
681 if (sy > originalRect.top()) {
682 updatedRect.setBottom(sy);
683 }
684 break;
685
686 case ImageCanvas::MoveLeft:
687 if (sx < originalRect.right()) {
688 updatedRect.setLeft(sx);
689 }
690 break;
691
692 case ImageCanvas::MoveNew:
693 if (sx > mStartPoint.x()) { // drag to the right
694 updatedRect.setLeft(mStartPoint.x());
695 updatedRect.setRight(sx);
696 } else { // drag to the left
697 updatedRect.setRight(mStartPoint.x());
698 updatedRect.setLeft(sx);
699 }
700
701 if (sy > mStartPoint.y()) { // drag down
702 updatedRect.setTop(mStartPoint.y());
703 updatedRect.setBottom(sy);
704 } else { // drag up
705 updatedRect.setBottom(mStartPoint.y());
706 updatedRect.setTop(sy);
707 }
708 break;
709
710 case ImageCanvas::MoveWhole:
711 int dx = sx - mLastPoint.x();
712 int dy = sy - mLastPoint.y();
713
714 QRectF r = updatedRect.translated(dx, dy); // prospective new rectangle
715
716 if (r.left() < 0) {
717 dx -= r.left(); // limit to edges
718 }
719 if (r.right() >= lx) {
720 dx -= (r.right() - lx + 1);
721 }
722 if (r.top() < 0) {
723 dy -= r.top();
724 }
725 if (r.bottom() >= ly) {
726 dy -= (r.bottom() - ly + 1);
727 }
728
729 updatedRect.translate(dx, dy);
730 break;
731 }
732
733 if (updatedRect != originalRect) {
734 mSelectionItem->setRect(updatedRect);
735 }
736 }
737
738 mLastPoint = scenePos;
739 }
740
mouseDoubleClickEvent(QMouseEvent * ev)741 void ImageCanvas::mouseDoubleClickEvent(QMouseEvent *ev)
742 {
743 if (!hasImage()) {
744 return; // no image displayed
745 }
746 // convert to image pixels
747 emit doubleClicked(mapToScene(ev->pos()).toPoint());
748 }
749
resizeEvent(QResizeEvent * ev)750 void ImageCanvas::resizeEvent(QResizeEvent *ev)
751 {
752 QGraphicsView::resizeEvent(ev);
753 recalculateViewScale();
754 }
755
756 // Scaling settings
757 // ----------------
758
setScaleFactor(int i)759 void ImageCanvas::setScaleFactor(int i)
760 {
761 //qDebug() << "to" << i;
762 mScaleFactor = i;
763 if (i == 0) {
764 setScaleType(ImageCanvas::ScaleDynamic);
765 }
766
767 recalculateViewScale();
768 }
769
scaleType() const770 ImageCanvas::ScaleType ImageCanvas::scaleType() const
771 {
772 if (mScaleType != ImageCanvas::ScaleUnspecified) {
773 return (mScaleType);
774 } else {
775 return (defaultScaleType());
776 }
777 }
778
setScaleType(ScaleType type)779 void ImageCanvas::setScaleType(ScaleType type)
780 {
781 if (type == mScaleType) {
782 return; // no change
783 }
784
785 //qDebug() << "to" << type;
786 mScaleType = type;
787 emit scalingChanged(scaleTypeString());
788 }
789
scaleTypeString() const790 const QString ImageCanvas::scaleTypeString() const
791 {
792 switch (scaleType()) {
793 case ImageCanvas::ScaleDynamic: return (i18n("Fit Best"));
794 case ImageCanvas::ScaleOriginal: return (i18n("Original size"));
795 case ImageCanvas::ScaleFitWidth: return (i18n("Fit Width"));
796 case ImageCanvas::ScaleFitHeight: return (i18n("Fit Height"));
797 case ImageCanvas::ScaleZoom: return (i18n("Zoom %1%", mScaleFactor));
798 default: return (i18n("Unknown"));
799 }
800 }
801
802 // Calculate an appropriate scale (i.e. view transform) for displaying the image
803 // -----------------------------------------------------------------------------
804
recalculateViewScale()805 void ImageCanvas::recalculateViewScale()
806 {
807 if (!hasImage()) {
808 return;
809 }
810
811 const int iw = mImage->width(); // original image size
812 const int ih = mImage->height();
813 const int aw = width() - 2 * frameWidth(); // available display size
814 const int ah = height() - 2 * frameWidth();
815 // width/height of scroll bar
816 const int sbSize = QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
817
818 double xscale, yscale; // calculated scale factors
819
820 switch (scaleType()) {
821 case ImageCanvas::ScaleDynamic:
822 xscale = double(aw) / double(iw);
823 yscale = double(ah) / double(ih);
824 mScaleFactor = 0;
825
826 if (mMaintainAspect) {
827 xscale = yscale < xscale ? yscale : xscale;
828 yscale = xscale;
829 }
830 break;
831
832 default:
833 //qDebug() << "Unknown scale type" << scaleType();
834 // fall through
835 case ImageCanvas::ScaleOriginal:
836 yscale = xscale = 1.0;
837 mScaleFactor = 100;
838 break;
839
840 case ImageCanvas::ScaleFitWidth:
841 xscale = yscale = double(aw) / double(iw);
842 if ((yscale * ih) >= ah) { // will there be a scroll bar?
843 // account for vertical scroll bar
844 xscale = yscale = double(aw - sbSize) / double(iw);
845 //qDebug() << "FIT WIDTH scrollbar to subtract:" << sbSize;
846 }
847 mScaleFactor = static_cast<int>(100 * xscale);
848 break;
849
850 case ImageCanvas::ScaleFitHeight:
851 yscale = xscale = double(ah) / double(ih);
852 if ((xscale * iw) >= aw) { // will there be a scroll bar?
853 // account for horizontal scroll bar
854 yscale = xscale = double(ah - sbSize) / double(ih);
855 //qDebug() << "FIT HEIGHT scrollbar to subtract:" << sbSize;
856 }
857 mScaleFactor = static_cast<int>(100 * yscale);
858 break;
859
860 case ImageCanvas::ScaleZoom:
861 xscale = yscale = double(mScaleFactor) / 100.0;
862 mScaleFactor = static_cast<int>(100 * xscale);
863 break;
864 }
865
866 QTransform trans;
867 trans.scale(xscale, yscale);
868 //qDebug() << "setting transform to" << trans;
869 setTransform(trans);
870 }
871
872 // Miscellaneous settings and information
873 // --------------------------------------
874
setCursorShape(Qt::CursorShape cs)875 void ImageCanvas::setCursorShape(Qt::CursorShape cs)
876 {
877 if (mCurrentCursor != cs) { // optimise no-change
878 setCursor(cs);
879 mCurrentCursor = cs;
880 }
881 }
882
setReadOnly(bool ro)883 void ImageCanvas::setReadOnly(bool ro)
884 {
885 mReadOnly = ro;
886 if (mReadOnly) {
887 stopMarqueeTimer(); // clear selection
888 setCursorShape(Qt::CrossCursor); // ensure cursor is reset
889 }
890 emit imageReadOnly(ro);
891 }
892
imageInfoString(int w,int h,int d)893 const QString ImageCanvas::imageInfoString(int w, int h, int d) // static
894 {
895 return (i18n("%1x%2 pix, %3 bpp", w, h, d));
896 }
897
imageInfoString(const QImage * img)898 const QString ImageCanvas::imageInfoString(const QImage *img) // static
899 {
900 if (img == nullptr) {
901 return ("-");
902 } else {
903 return (imageInfoString(img->width(), img->height(), img->depth()));
904 }
905 }
906
imageInfoString() const907 const QString ImageCanvas::imageInfoString() const // member
908 {
909 return (imageInfoString(mImage));
910 }
911
hasImage() const912 bool ImageCanvas::hasImage() const
913 {
914 return (mImage != nullptr && !mImage->isNull());
915 }
916
917 // Highlight areas
918 // ---------------
919
addHighlight(const QRect & rect,bool ensureVis)920 int ImageCanvas::addHighlight(const QRect &rect, bool ensureVis)
921 {
922 HighlightItem *item = new HighlightItem(rect, mHighlightStyle,
923 mHighlightPen, mHighlightBrush);
924
925 int idx = mHighlights.indexOf(nullptr); // any empty slots?
926 if (idx != -1) {
927 mHighlights[idx] = item; // yes, reuse that
928 } else { // no, append new item
929 idx = mHighlights.size();
930 mHighlights.append(item);
931 }
932
933 mScene->addItem(item);
934 if (ensureVis) {
935 scrollTo(rect);
936 }
937 return (idx);
938 }
939
removeAllHighlights()940 void ImageCanvas::removeAllHighlights()
941 {
942 for (int idx = 0; idx < mHighlights.size(); ++idx) {
943 removeHighlight(idx);
944 }
945 }
946
removeHighlight(int idx)947 void ImageCanvas::removeHighlight(int idx)
948 {
949 if (idx < 0 || idx > mHighlights.size()) {
950 return;
951 }
952
953 QGraphicsItem *item = mHighlights[idx];
954 if (item == nullptr) {
955 return;
956 }
957
958 mScene->removeItem(item);
959 delete item;
960 mHighlights[idx] = nullptr;
961 }
962
scrollTo(const QRect & rect)963 void ImageCanvas::scrollTo(const QRect &rect)
964 {
965 if (rect.isValid()) {
966 ensureVisible(rect);
967 }
968 }
969
setHighlightStyle(ImageCanvas::HighlightStyle style,const QPen & pen,const QBrush & brush)970 void ImageCanvas::setHighlightStyle(ImageCanvas::HighlightStyle style,
971 const QPen &pen, const QBrush &brush)
972 {
973 mHighlightStyle = style;
974 mHighlightPen = pen;
975 mHighlightBrush = brush;
976 }
977
978 // Mouse position on the selection rectangle
979 // -----------------------------------------
980
981 // This works in view coordinates, in order that the DELTA tolerance
982 // is consistent regardless of the view scale.
983
classifyPoint(const QPoint & p) const984 ImageCanvas::MoveState ImageCanvas::classifyPoint(const QPoint &p) const
985 {
986 if (!mSelectionItem->isVisible()) {
987 return (ImageCanvas::MoveNone);
988 }
989
990 const QRect r = mapFromScene(mSelectionItem->boundingRect()).boundingRect().normalized();
991 ////qDebug() << p << "for rect" << r;
992 if (r.isEmpty()) {
993 return (ImageCanvas::MoveNone);
994 }
995
996 const bool onLeft = (abs(p.x() - r.left()) < DELTA);
997 const bool onRight = (abs(p.x() - r.right()) < DELTA);
998
999 const bool onTop = (abs(p.y() - r.top()) < DELTA);
1000 const bool onBottom = (abs(p.y() - r.bottom()) < DELTA);
1001
1002 const bool withinRect = r.contains(p);
1003 const bool overRect = r.adjusted(-DELTA, -DELTA, DELTA, DELTA).contains(p);
1004
1005 if (onLeft && onTop) {
1006 return (ImageCanvas::MoveTopLeft);
1007 }
1008 if (onLeft && onBottom) {
1009 return (ImageCanvas::MoveBottomLeft);
1010 }
1011
1012 if (onRight && onTop) {
1013 return (ImageCanvas::MoveTopRight);
1014 }
1015 if (onRight && onBottom) {
1016 return (ImageCanvas::MoveBottomRight);
1017 }
1018
1019 if (onLeft && overRect) {
1020 return (ImageCanvas::MoveLeft);
1021 }
1022 if (onRight && overRect) {
1023 return (ImageCanvas::MoveRight);
1024 }
1025 if (onTop && overRect) {
1026 return (ImageCanvas::MoveTop);
1027 }
1028 if (onBottom && overRect) {
1029 return (ImageCanvas::MoveBottom);
1030 }
1031
1032 if (withinRect) {
1033 return (ImageCanvas::MoveWhole);
1034 }
1035
1036 return (ImageCanvas::MoveNone);
1037 }
1038