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