File indexing completed on 2024-04-14 14:36:02
0001 /************************************************************************ 0002 * * 0003 * This file is part of libkscan, a KDE scanning library. * 0004 * * 0005 * Copyright (C) 2013 Jonathan Marten <jjm@keelhaul.me.uk> * 0006 * Copyright (C) 1999 Klaas Freitag <freitag@suse.de> * 0007 * * 0008 * This library is free software; you can redistribute it and/or * 0009 * modify it under the terms of the GNU Library General Public * 0010 * License as published by the Free Software Foundation and appearing * 0011 * in the file COPYING included in the packaging of this file; * 0012 * either version 2 of the License, or (at your option) any later * 0013 * version. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program; see the file COPYING. If not, write to * 0022 * the Free Software Foundation, Inc., 51 Franklin Street, * 0023 * Fifth Floor, Boston, MA 02110-1301, USA. * 0024 * * 0025 ************************************************************************/ 0026 0027 #include "imagecanvas.h" 0028 0029 #include <QGraphicsScene> 0030 #include <QGraphicsPixmapItem> 0031 0032 #include <qtransform.h> 0033 #include <qimage.h> 0034 #include <qpainter.h> 0035 #include <qstyle.h> 0036 #include <qevent.h> 0037 #include <qapplication.h> 0038 #include <qmenu.h> 0039 0040 #include <klocalizedstring.h> 0041 0042 #include "imgscaledialog.h" 0043 #include "libkookascan_logging.h" 0044 0045 0046 // Parameters for display and selection 0047 // ------------------------------------ 0048 0049 #undef HOLD_SELECTION // hold selection over new image 0050 0051 const int MIN_AREA_WIDTH = 2; // minimum size of selection area 0052 const int MIN_AREA_HEIGHT = 2; 0053 const int DELTA = 3; // tolerance for mouse hits 0054 const int TIMER_INTERVAL = 100; // animation timer interval 0055 0056 const int DASH_DASH = 4; // length of drawn dashes 0057 const int DASH_SPACE = 4; // length of drawn spaces 0058 0059 const int DASH_LENGTH = (DASH_DASH + DASH_SPACE); 0060 0061 // HighlightItem -- A graphics item to maintain and draw a single 0062 // highlight rectangle. 0063 0064 class HighlightItem : public QGraphicsItem 0065 { 0066 public: 0067 HighlightItem(const QRect &rect, 0068 ImageCanvas::HighlightStyle style, 0069 const QPen &pen, const QBrush &brush, 0070 QGraphicsItem *parent = nullptr); 0071 virtual ~HighlightItem() {} 0072 0073 QRectF boundingRect() const override; 0074 0075 virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 0076 QWidget *widget = nullptr) override; 0077 0078 private: 0079 QRectF mRectangle; 0080 0081 ImageCanvas::HighlightStyle mStyle; 0082 QPen mPen; 0083 QBrush mBrush; 0084 }; 0085 0086 HighlightItem::HighlightItem(const QRect &rect, 0087 ImageCanvas::HighlightStyle style, 0088 const QPen &pen, const QBrush &brush, 0089 QGraphicsItem *parent) 0090 : QGraphicsItem(parent) 0091 { 0092 mRectangle = rect; 0093 mStyle = style; 0094 mPen = pen; 0095 mBrush = brush; 0096 0097 mPen.setCosmetic(true); 0098 } 0099 0100 QRectF HighlightItem::boundingRect() const 0101 { 0102 return (mRectangle); 0103 } 0104 0105 void HighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0106 { 0107 painter->setPen(mPen); 0108 painter->setBrush(mBrush); 0109 0110 if (mStyle == ImageCanvas::HighlightBox) { 0111 painter->drawRect(mRectangle); 0112 } else painter->drawLine(mRectangle.left(), mRectangle.bottom(), 0113 mRectangle.right(), mRectangle.bottom()); 0114 } 0115 0116 // SelectionItem -- A graphics item to maintain and draw the 0117 // selection rectangle. 0118 // 0119 // There is only one of these items on the canvas. Its bounding 0120 // rectangle, stored here in scene coordinates (i.e. image pixels) 0121 // is the master reference for the selection rectangle. 0122 0123 class SelectionItem : public QGraphicsItem 0124 { 0125 public: 0126 explicit SelectionItem(QGraphicsItem *parent = nullptr); 0127 ~SelectionItem() override {} 0128 0129 QRectF boundingRect() const override; 0130 void setRect(const QRectF &rect); 0131 0132 void stepDashPattern(); 0133 void resetDashPattern(); 0134 0135 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 0136 QWidget *widget = nullptr) override; 0137 0138 private: 0139 QRectF mRectangle; 0140 int mDashOffset; 0141 }; 0142 0143 SelectionItem::SelectionItem(QGraphicsItem *parent) 0144 : QGraphicsItem(parent) 0145 { 0146 mDashOffset = 0; 0147 } 0148 0149 QRectF SelectionItem::boundingRect() const 0150 { 0151 return (mRectangle); 0152 } 0153 0154 void SelectionItem::setRect(const QRectF &rect) 0155 { 0156 prepareGeometryChange(); 0157 mRectangle = rect; 0158 } 0159 0160 void SelectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0161 { 0162 painter->setBrush(QBrush()); 0163 0164 QPen pen1(Qt::white); // solid white box behind 0165 painter->setPen(pen1); 0166 painter->drawRect(mRectangle); 0167 0168 QPen pen2(Qt::CustomDashLine); // dashed black box on top 0169 QVector<qreal> dashes(2); 0170 dashes[0] = DASH_DASH; 0171 dashes[1] = DASH_SPACE; 0172 pen2.setDashPattern(dashes); 0173 pen2.setDashOffset(mDashOffset); 0174 painter->setPen(pen2); 0175 painter->drawRect(mRectangle); 0176 } 0177 0178 // Counting backwards so that the "ants" march clockwise 0179 // (at least with the current Qt painting implementation) 0180 void SelectionItem::stepDashPattern() 0181 { 0182 --mDashOffset; 0183 if (mDashOffset < 0) { 0184 mDashOffset = (DASH_LENGTH - 1); 0185 } 0186 update(); 0187 } 0188 0189 void SelectionItem::resetDashPattern() 0190 { 0191 mDashOffset = 0; 0192 } 0193 0194 // ImageCanvas -- Scrolling area containing the pixmap/highlight widget. 0195 // Selected areas are drawn over the top of that. 0196 0197 ImageCanvas::ImageCanvas(QWidget *parent) 0198 : QGraphicsView(parent) 0199 { 0200 setObjectName("ImageCanvas"); 0201 0202 mContextMenu = nullptr; 0203 mTimerId = 0; 0204 0205 mKeepZoom = false; 0206 mReadOnly = false; 0207 mScaleType = ImageCanvas::ScaleUnspecified; 0208 mDefaultScaleType = ImageCanvas::ScaleOriginal; 0209 mScaleFactor = 100; // means original size 0210 mMaintainAspect = true; 0211 0212 setAlignment(Qt::AlignLeft|Qt::AlignTop); 0213 0214 mScene = new QGraphicsScene(this); 0215 setScene(mScene); 0216 0217 mPixmapItem = new QGraphicsPixmapItem; 0218 mPixmapItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); 0219 mScene->addItem(mPixmapItem); 0220 0221 mSelectionItem = new SelectionItem; 0222 mSelectionItem->setVisible(false); // no selection yet 0223 mScene->addItem(mSelectionItem); 0224 0225 mMoving = ImageCanvas::MoveNone; 0226 mCurrentCursor = Qt::ArrowCursor; 0227 0228 newImage(ScanImage::Ptr()); // no displayed image 0229 0230 setCursorShape(Qt::CrossCursor); 0231 setMouseTracking(true); 0232 0233 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0234 show(); 0235 } 0236 0237 0238 ImageCanvas::~ImageCanvas() 0239 { 0240 stopMarqueeTimer(); 0241 } 0242 0243 0244 // Setting the image 0245 // ----------------- 0246 0247 void ImageCanvas::newImage(ScanImage::Ptr newImage, bool holdZoom) 0248 { 0249 mImage = newImage; // take reference to shared pointer 0250 0251 #ifdef HOLD_SELECTION 0252 QRect oldSelected = mSelected; 0253 qCDebug(LIBKOOKASCAN_LOG) << "original selection" << oldSelected << "null" << oldSelected.isEmpty(); 0254 qCDebug(LIBKOOKASCAN_LOG) << "w" << mSelected.width() << "h" << mSelected.height(); 0255 #endif 0256 0257 stopMarqueeTimer(); // also clears selection 0258 0259 if (!mImage.isNull()) // handle the new image 0260 { 0261 //qCDebug(LIBKOOKASCAN_LOG) << "new image size is" << mImage->size(); 0262 mPixmapItem->setPixmap(QPixmap::fromImage(*mImage.data())); 0263 setSceneRect(mPixmapItem->boundingRect()); // image always defines size 0264 0265 if (!mKeepZoom && !holdZoom) setScaleType(defaultScaleType()); 0266 0267 #ifdef HOLD_SELECTION 0268 if (!oldSelected.isNull()) { 0269 mSelected = oldSelected; 0270 qCDebug(LIBKOOKASCAN_LOG) << "restored selection" << mSelected; 0271 startMarqueeTimer(); 0272 } 0273 #endif 0274 } 0275 else mPixmapItem->setPixmap(QPixmap()); // no new image to show 0276 0277 recalculateViewScale(); 0278 } 0279 0280 0281 // Context menu 0282 // ------------ 0283 // 0284 // We don't actually populate the menu or handle its actions here. 0285 // The menu is populated with the parent application's actions via 0286 // KookaView::connectViewerAction(), and the application handles the 0287 // triggered actions. Those which need some work from us are passed to 0288 // slotUserAction() with the appropriate UserAction key. 0289 0290 QMenu *ImageCanvas::contextMenu() 0291 { 0292 if (mContextMenu == nullptr) { // menu not created yet 0293 mContextMenu = new QMenu(this); 0294 } 0295 0296 return (mContextMenu); 0297 } 0298 0299 void ImageCanvas::contextMenuEvent(QContextMenuEvent *ev) 0300 { 0301 if (mContextMenu == nullptr) { 0302 return; // menu not enabled 0303 } 0304 // but allowed if no image loaded 0305 mContextMenu->popup(ev->globalPos()); 0306 ev->accept(); 0307 } 0308 0309 void ImageCanvas::performUserAction(ImageCanvas::UserAction act) 0310 { 0311 if (mImage.isNull()) return; // no action if no image loaded 0312 0313 switch (act) { 0314 case ImageCanvas::UserActionZoom: { 0315 ImgScaleDialog zoomDia(this, mScaleFactor); 0316 if (zoomDia.exec()) { 0317 int sf = zoomDia.getSelected(); 0318 setScaleType(ImageCanvas::ScaleZoom); 0319 setScaleFactor(sf); 0320 } 0321 } 0322 break; 0323 0324 case ImageCanvas::UserActionOrigSize: 0325 setScaleType(ImageCanvas::ScaleOriginal); 0326 break; 0327 0328 case ImageCanvas::UserActionFitWidth: 0329 setScaleType(ImageCanvas::ScaleFitWidth); 0330 break; 0331 0332 case ImageCanvas::UserActionFitHeight: 0333 setScaleType(ImageCanvas::ScaleFitHeight); 0334 break; 0335 0336 case ImageCanvas::UserActionClose: 0337 emit closingRequested(); 0338 return; 0339 } 0340 0341 recalculateViewScale(); 0342 } 0343 0344 // Selected rectangle 0345 // ------------------ 0346 0347 bool ImageCanvas::hasSelectedRect() const 0348 { 0349 if (!hasImage()) { 0350 return (false); 0351 } 0352 return (mSelectionItem->isVisible() && mSelectionItem->boundingRect().isValid()); 0353 } 0354 0355 // Get the selected area in absolute image pixels. 0356 QRect ImageCanvas::selectedRect() const 0357 { 0358 if (!hasSelectedRect()) { 0359 return (QRect()); 0360 } 0361 return (mSelectionItem->boundingRect().toRect()); 0362 } 0363 0364 // Get the selected area as a proportion of the image size. 0365 QRectF ImageCanvas::selectedRectF() const 0366 { 0367 if (!hasSelectedRect()) { 0368 return (QRectF()); 0369 } 0370 const QRectF r = mSelectionItem->boundingRect(); 0371 0372 QRectF retval; 0373 retval.setLeft(r.left() / mImage->width()); 0374 retval.setRight(r.right() / mImage->width()); 0375 retval.setTop(r.top() / mImage->height()); 0376 retval.setBottom(r.bottom() / mImage->height()); 0377 return (retval); 0378 } 0379 0380 // Set the selected area in absolute image pixels. 0381 void ImageCanvas::setSelectionRect(const QRect &rect) 0382 { 0383 //qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect; 0384 if (!hasImage()) { 0385 return; 0386 } 0387 0388 if (!rect.isValid()) { 0389 stopMarqueeTimer(); // clear the selection 0390 } else { // set the selection 0391 mSelectionItem->setRect(rect); 0392 startMarqueeTimer(); 0393 } 0394 } 0395 0396 // Set the selected area as a proportion of the image size. 0397 void ImageCanvas::setSelectionRect(const QRectF &rect) 0398 { 0399 //qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect; 0400 if (!hasImage()) { 0401 return; 0402 } 0403 0404 if (!rect.isValid()) { 0405 stopMarqueeTimer(); // clear the selection 0406 } else { // set the selection 0407 QRectF setval; 0408 setval.setLeft(rect.left()*mImage->width()); 0409 setval.setRight(rect.right()*mImage->width()); 0410 setval.setTop(rect.top()*mImage->height()); 0411 setval.setBottom(rect.bottom()*mImage->height()); 0412 0413 mSelectionItem->setRect(setval); 0414 startMarqueeTimer(); 0415 } 0416 } 0417 0418 0419 // Selected image 0420 // -------------- 0421 0422 ScanImage::Ptr ImageCanvas::selectedImage() const 0423 { 0424 if (!hasImage()) return (ScanImage::Ptr()); // no image available 0425 const QRect r = selectedRect(); 0426 if (!r.isValid()) return (ScanImage::Ptr()); // no selection 0427 0428 ScanImage *img = new ScanImage(mImage->copy(r)); // extract selection as new image 0429 return (ScanImage::Ptr(img)); // return shared pointer to it 0430 } 0431 0432 0433 // Animation timer 0434 // --------------- 0435 0436 void ImageCanvas::timerEvent(QTimerEvent *) 0437 { 0438 if (!hasImage()) { 0439 return; // no image acquired 0440 } 0441 if (!isVisible()) { 0442 return; // we're not visible 0443 } 0444 if (mMoving != ImageCanvas::MoveNone) { 0445 return; // mouse operation in progress 0446 } 0447 0448 mSelectionItem->stepDashPattern(); 0449 } 0450 0451 void ImageCanvas::startMarqueeTimer() 0452 { 0453 if (mTimerId == 0) { 0454 mTimerId = startTimer(TIMER_INTERVAL); 0455 } 0456 mSelectionItem->setVisible(true); 0457 } 0458 0459 void ImageCanvas::stopMarqueeTimer() 0460 { 0461 if (mTimerId != 0) { 0462 killTimer(mTimerId); 0463 mTimerId = 0; 0464 } 0465 0466 mSelectionItem->setVisible(false); // clear the selection 0467 mSelectionItem->resetDashPattern(); 0468 } 0469 0470 // Mouse events 0471 // ------------ 0472 0473 void ImageCanvas::mousePressEvent(QMouseEvent *ev) 0474 { 0475 if (mReadOnly) { 0476 return; // only if permitted 0477 } 0478 if (ev->button() != Qt::LeftButton) { 0479 return; // only action this button 0480 } 0481 if (mMoving != ImageCanvas::MoveNone) { 0482 return; // something already in progress 0483 } 0484 if (!hasImage()) { 0485 return; // no image displayed 0486 } 0487 0488 const QList<QGraphicsItem *> its = items(ev->pos()); 0489 if (its.isEmpty()) { 0490 return; // not over any item 0491 } 0492 if (its.last() != mPixmapItem) { 0493 return; // not within the image 0494 } 0495 0496 mMoving = classifyPoint(ev->pos()); // see where hit happened 0497 if (mMoving == ImageCanvas::MoveNone) { // starting new area 0498 QPoint p = mapToScene(ev->pos()).toPoint(); // position in scene coords 0499 mStartPoint = p; 0500 mLastPoint = p; 0501 0502 mSelectionItem->setRect(QRectF(p.x(), p.y(), 0, 0)); 0503 mSelectionItem->setVisible(true); 0504 mMoving = ImageCanvas::MoveNew; 0505 } 0506 } 0507 0508 void ImageCanvas::mouseReleaseEvent(QMouseEvent *ev) 0509 { 0510 if (mReadOnly) { 0511 return; // only if permitted 0512 } 0513 if (ev->button() != Qt::LeftButton) { 0514 return; // only action this button 0515 } 0516 if (mMoving == ImageCanvas::MoveNone) { 0517 return; // nothing in progress 0518 } 0519 if (!hasImage()) { 0520 return; // no image displayed 0521 } 0522 0523 mMoving = ImageCanvas::MoveNone; 0524 0525 QRect selected = selectedRect(); 0526 //qCDebug(LIBKOOKASCAN_LOG) << "selected rect" << selected; 0527 if (selected.width() < MIN_AREA_WIDTH || selected.height() < MIN_AREA_HEIGHT) { 0528 //qCDebug(LIBKOOKASCAN_LOG) << "no selection"; 0529 stopMarqueeTimer(); // also hides selection 0530 emit newRect(QRect()); 0531 emit newRect(QRectF()); 0532 } else { 0533 //qCDebug(LIBKOOKASCAN_LOG) << "have selection"; 0534 startMarqueeTimer(); 0535 emit newRect(selectedRect()); 0536 emit newRect(selectedRectF()); 0537 } 0538 0539 mouseMoveEvent(ev); // update cursor shape 0540 } 0541 0542 void ImageCanvas::mouseMoveEvent(QMouseEvent *ev) 0543 { 0544 if (mReadOnly) { 0545 return; // only if permitted 0546 } 0547 if (!hasImage()) { 0548 return; // no image displayed 0549 } 0550 0551 int x = ev->pos().x(); // mouse position 0552 int y = ev->pos().y(); // in view coordinates 0553 0554 int lx = mImage->width(); // limits for moved rectangle 0555 int ly = mImage->height(); // in scene coordinates 0556 0557 QPoint pixExtent = mapFromScene(lx, ly); // those same limits 0558 int ix = pixExtent.x(); // in view coordinates 0559 int iy = pixExtent.y(); 0560 0561 // Limit drag rectangle to the scaled pixmap 0562 if (x < 0) { 0563 x = 0; 0564 } 0565 if (x >= ix) { 0566 x = ix - 1; 0567 } 0568 if (y < 0) { 0569 y = 0; 0570 } 0571 if (y >= iy) { 0572 y = iy - 1; 0573 } 0574 0575 QPoint scenePos = mapToScene(x, y).toPoint(); // that limited position 0576 int sx = scenePos.x(); // in scene coordinates 0577 int sy = scenePos.y(); 0578 0579 //qCDebug(LIBKOOKASCAN_LOG) << x << y << "moving" << mMoving; 0580 0581 // Set the cursor shape appropriately 0582 switch (mMoving != ImageCanvas::MoveNone ? mMoving : classifyPoint(QPoint(x, y))) { 0583 case ImageCanvas::MoveNone: 0584 setCursorShape(Qt::CrossCursor); 0585 break; 0586 0587 case ImageCanvas::MoveLeft: 0588 case ImageCanvas::MoveRight: 0589 setCursorShape(Qt::SizeHorCursor); 0590 break; 0591 0592 case ImageCanvas::MoveTop: 0593 case ImageCanvas::MoveBottom: 0594 setCursorShape(Qt::SizeVerCursor); 0595 break; 0596 0597 case ImageCanvas::MoveTopLeft: 0598 case ImageCanvas::MoveBottomRight: 0599 setCursorShape(Qt::SizeFDiagCursor); 0600 break; 0601 0602 case ImageCanvas::MoveTopRight: 0603 case ImageCanvas::MoveBottomLeft: 0604 setCursorShape(Qt::SizeBDiagCursor); 0605 break; 0606 0607 case ImageCanvas::MoveWhole: 0608 setCursorShape(Qt::SizeAllCursor); 0609 break; 0610 0611 case ImageCanvas::MoveNew: 0612 setCursorShape(Qt::ArrowCursor); 0613 break; 0614 } 0615 0616 // Update the selection rectangle 0617 if (mMoving != ImageCanvas::MoveNone) { 0618 const QRectF originalRect = mSelectionItem->boundingRect(); 0619 QRectF updatedRect = originalRect; 0620 switch (mMoving) { 0621 case ImageCanvas::MoveNone: 0622 break; 0623 0624 case ImageCanvas::MoveTopLeft: 0625 if (sx < originalRect.right()) { 0626 updatedRect.setLeft(sx); 0627 } 0628 if (sy < originalRect.bottom()) { 0629 updatedRect.setTop(sy); 0630 } 0631 break; 0632 0633 case ImageCanvas::MoveTop: 0634 if (sy < originalRect.bottom()) { 0635 updatedRect.setTop(sy); 0636 } 0637 break; 0638 0639 case ImageCanvas::MoveTopRight: 0640 if (sx > originalRect.left()) { 0641 updatedRect.setRight(sx); 0642 } 0643 if (sy < originalRect.bottom()) { 0644 updatedRect.setTop(sy); 0645 } 0646 break; 0647 0648 case ImageCanvas::MoveRight: 0649 if (sx > originalRect.left()) { 0650 updatedRect.setRight(sx); 0651 } 0652 break; 0653 0654 case ImageCanvas::MoveBottomRight: 0655 if (sx > originalRect.left()) { 0656 updatedRect.setRight(sx); 0657 } 0658 if (sy > originalRect.top()) { 0659 updatedRect.setBottom(sy); 0660 } 0661 break; 0662 0663 case ImageCanvas::MoveBottom: 0664 if (sy > originalRect.top()) { 0665 updatedRect.setBottom(sy); 0666 } 0667 break; 0668 0669 case ImageCanvas::MoveBottomLeft: 0670 if (sx < originalRect.right()) { 0671 updatedRect.setLeft(sx); 0672 } 0673 if (sy > originalRect.top()) { 0674 updatedRect.setBottom(sy); 0675 } 0676 break; 0677 0678 case ImageCanvas::MoveLeft: 0679 if (sx < originalRect.right()) { 0680 updatedRect.setLeft(sx); 0681 } 0682 break; 0683 0684 case ImageCanvas::MoveNew: 0685 if (sx > mStartPoint.x()) { // drag to the right 0686 updatedRect.setLeft(mStartPoint.x()); 0687 updatedRect.setRight(sx); 0688 } else { // drag to the left 0689 updatedRect.setRight(mStartPoint.x()); 0690 updatedRect.setLeft(sx); 0691 } 0692 0693 if (sy > mStartPoint.y()) { // drag down 0694 updatedRect.setTop(mStartPoint.y()); 0695 updatedRect.setBottom(sy); 0696 } else { // drag up 0697 updatedRect.setBottom(mStartPoint.y()); 0698 updatedRect.setTop(sy); 0699 } 0700 break; 0701 0702 case ImageCanvas::MoveWhole: 0703 int dx = sx - mLastPoint.x(); 0704 int dy = sy - mLastPoint.y(); 0705 0706 QRectF r = updatedRect.translated(dx, dy); // prospective new rectangle 0707 0708 if (r.left() < 0) { 0709 dx -= r.left(); // limit to edges 0710 } 0711 if (r.right() >= lx) { 0712 dx -= (r.right() - lx + 1); 0713 } 0714 if (r.top() < 0) { 0715 dy -= r.top(); 0716 } 0717 if (r.bottom() >= ly) { 0718 dy -= (r.bottom() - ly + 1); 0719 } 0720 0721 updatedRect.translate(dx, dy); 0722 break; 0723 } 0724 0725 if (updatedRect != originalRect) { 0726 mSelectionItem->setRect(updatedRect); 0727 } 0728 } 0729 0730 mLastPoint = scenePos; 0731 } 0732 0733 void ImageCanvas::mouseDoubleClickEvent(QMouseEvent *ev) 0734 { 0735 if (!hasImage()) { 0736 return; // no image displayed 0737 } 0738 // convert to image pixels 0739 emit doubleClicked(mapToScene(ev->pos()).toPoint()); 0740 } 0741 0742 void ImageCanvas::resizeEvent(QResizeEvent *ev) 0743 { 0744 QGraphicsView::resizeEvent(ev); 0745 recalculateViewScale(); 0746 } 0747 0748 // Scaling settings 0749 // ---------------- 0750 0751 void ImageCanvas::setScaleFactor(int i) 0752 { 0753 //qCDebug(LIBKOOKASCAN_LOG) << "to" << i; 0754 mScaleFactor = i; 0755 if (i == 0) { 0756 setScaleType(ImageCanvas::ScaleDynamic); 0757 } 0758 0759 recalculateViewScale(); 0760 } 0761 0762 ImageCanvas::ScaleType ImageCanvas::scaleType() const 0763 { 0764 if (mScaleType != ImageCanvas::ScaleUnspecified) { 0765 return (mScaleType); 0766 } else { 0767 return (defaultScaleType()); 0768 } 0769 } 0770 0771 void ImageCanvas::setScaleType(ScaleType type) 0772 { 0773 if (type == mScaleType) { 0774 return; // no change 0775 } 0776 0777 //qCDebug(LIBKOOKASCAN_LOG) << "to" << type; 0778 mScaleType = type; 0779 emit scalingChanged(scaleTypeString()); 0780 } 0781 0782 const QString ImageCanvas::scaleTypeString() const 0783 { 0784 switch (scaleType()) { 0785 case ImageCanvas::ScaleDynamic: return (i18n("Fit Best")); 0786 case ImageCanvas::ScaleOriginal: return (i18n("Original size")); 0787 case ImageCanvas::ScaleFitWidth: return (i18n("Fit Width")); 0788 case ImageCanvas::ScaleFitHeight: return (i18n("Fit Height")); 0789 case ImageCanvas::ScaleZoom: return (i18n("Zoom %1%", mScaleFactor)); 0790 default: return (i18n("Unknown")); 0791 } 0792 } 0793 0794 // Calculate an appropriate scale (i.e. view transform) for displaying the image 0795 // ----------------------------------------------------------------------------- 0796 0797 void ImageCanvas::recalculateViewScale() 0798 { 0799 if (!hasImage()) { 0800 return; 0801 } 0802 0803 const int iw = mImage->width(); // original image size 0804 const int ih = mImage->height(); 0805 const int aw = width() - 2 * frameWidth(); // available display size 0806 const int ah = height() - 2 * frameWidth(); 0807 // width/height of scroll bar 0808 const int sbSize = QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent); 0809 0810 double xscale, yscale; // calculated scale factors 0811 0812 switch (scaleType()) { 0813 case ImageCanvas::ScaleDynamic: 0814 xscale = double(aw) / double(iw); 0815 yscale = double(ah) / double(ih); 0816 mScaleFactor = 0; 0817 0818 if (mMaintainAspect) { 0819 xscale = yscale < xscale ? yscale : xscale; 0820 yscale = xscale; 0821 } 0822 break; 0823 0824 default: 0825 qCDebug(LIBKOOKASCAN_LOG) << "Unknown scale type" << scaleType(); 0826 // fall through 0827 case ImageCanvas::ScaleOriginal: 0828 yscale = xscale = 1.0; 0829 mScaleFactor = 100; 0830 break; 0831 0832 case ImageCanvas::ScaleFitWidth: 0833 xscale = yscale = double(aw) / double(iw); 0834 if ((yscale * ih) >= ah) { // will there be a scroll bar? 0835 // account for vertical scroll bar 0836 xscale = yscale = double(aw - sbSize) / double(iw); 0837 //qCDebug(LIBKOOKASCAN_LOG) << "FIT WIDTH scrollbar to subtract:" << sbSize; 0838 } 0839 mScaleFactor = static_cast<int>(100 * xscale); 0840 break; 0841 0842 case ImageCanvas::ScaleFitHeight: 0843 yscale = xscale = double(ah) / double(ih); 0844 if ((xscale * iw) >= aw) { // will there be a scroll bar? 0845 // account for horizontal scroll bar 0846 yscale = xscale = double(ah - sbSize) / double(ih); 0847 //qCDebug(LIBKOOKASCAN_LOG) << "FIT HEIGHT scrollbar to subtract:" << sbSize; 0848 } 0849 mScaleFactor = static_cast<int>(100 * yscale); 0850 break; 0851 0852 case ImageCanvas::ScaleZoom: 0853 xscale = yscale = double(mScaleFactor) / 100.0; 0854 mScaleFactor = static_cast<int>(100 * xscale); 0855 break; 0856 } 0857 0858 QTransform trans; 0859 trans.scale(xscale, yscale); 0860 //qCDebug(LIBKOOKASCAN_LOG) << "setting transform to" << trans; 0861 setTransform(trans); 0862 } 0863 0864 // Miscellaneous settings and information 0865 // -------------------------------------- 0866 0867 void ImageCanvas::setCursorShape(Qt::CursorShape cs) 0868 { 0869 if (mCurrentCursor != cs) { // optimise no-change 0870 setCursor(cs); 0871 mCurrentCursor = cs; 0872 } 0873 } 0874 0875 void ImageCanvas::setReadOnly(bool ro) 0876 { 0877 mReadOnly = ro; 0878 if (mReadOnly) { 0879 stopMarqueeTimer(); // clear selection 0880 setCursorShape(Qt::CrossCursor); // ensure cursor is reset 0881 } 0882 emit imageReadOnly(ro); 0883 } 0884 0885 0886 /* static */ const QString ImageCanvas::imageInfoString(int w, int h, int d) 0887 { 0888 return (i18n("%1x%2 pix, %3 bpp", w, h, d)); 0889 } 0890 0891 0892 /* static */ const QString ImageCanvas::imageInfoString(const QImage *img) 0893 { 0894 if (img==nullptr) return ("-"); 0895 else return (imageInfoString(img->width(), img->height(), img->depth())); 0896 } 0897 0898 0899 const QString ImageCanvas::imageInfoString() const 0900 { 0901 return (imageInfoString(mImage.data())); 0902 } 0903 0904 0905 bool ImageCanvas::hasImage() const 0906 { 0907 return (!mImage.isNull() && !mImage->isNull()); 0908 } 0909 0910 0911 // Highlight areas 0912 // --------------- 0913 0914 int ImageCanvas::addHighlight(const QRect &rect, bool ensureVis) 0915 { 0916 HighlightItem *item = new HighlightItem(rect, mHighlightStyle, 0917 mHighlightPen, mHighlightBrush); 0918 0919 int idx = mHighlights.indexOf(nullptr); // any empty slots? 0920 if (idx != -1) { 0921 mHighlights[idx] = item; // yes, reuse that 0922 } else { // no, append new item 0923 idx = mHighlights.size(); 0924 mHighlights.append(item); 0925 } 0926 0927 mScene->addItem(item); 0928 if (ensureVis) { 0929 scrollTo(rect); 0930 } 0931 return (idx); 0932 } 0933 0934 void ImageCanvas::removeAllHighlights() 0935 { 0936 for (int idx = 0; idx < mHighlights.size(); ++idx) { 0937 removeHighlight(idx); 0938 } 0939 } 0940 0941 void ImageCanvas::removeHighlight(int idx) 0942 { 0943 if (idx < 0 || idx > mHighlights.size()) { 0944 return; 0945 } 0946 0947 QGraphicsItem *item = mHighlights[idx]; 0948 if (item == nullptr) { 0949 return; 0950 } 0951 0952 mScene->removeItem(item); 0953 delete item; 0954 mHighlights[idx] = nullptr; 0955 } 0956 0957 void ImageCanvas::scrollTo(const QRect &rect) 0958 { 0959 if (rect.isValid()) { 0960 ensureVisible(rect); 0961 } 0962 } 0963 0964 void ImageCanvas::setHighlightStyle(ImageCanvas::HighlightStyle style, 0965 const QPen &pen, const QBrush &brush) 0966 { 0967 mHighlightStyle = style; 0968 mHighlightPen = pen; 0969 mHighlightBrush = brush; 0970 } 0971 0972 // Mouse position on the selection rectangle 0973 // ----------------------------------------- 0974 0975 // This works in view coordinates, in order that the DELTA tolerance 0976 // is consistent regardless of the view scale. 0977 0978 ImageCanvas::MoveState ImageCanvas::classifyPoint(const QPoint &p) const 0979 { 0980 if (!mSelectionItem->isVisible()) { 0981 return (ImageCanvas::MoveNone); 0982 } 0983 0984 const QRect r = mapFromScene(mSelectionItem->boundingRect()).boundingRect().normalized(); 0985 //qCDebug(LIBKOOKASCAN_LOG) << p << "for rect" << r; 0986 if (r.isEmpty()) { 0987 return (ImageCanvas::MoveNone); 0988 } 0989 0990 const bool onLeft = (abs(p.x() - r.left()) < DELTA); 0991 const bool onRight = (abs(p.x() - r.right()) < DELTA); 0992 0993 const bool onTop = (abs(p.y() - r.top()) < DELTA); 0994 const bool onBottom = (abs(p.y() - r.bottom()) < DELTA); 0995 0996 const bool withinRect = r.contains(p); 0997 const bool overRect = r.adjusted(-DELTA, -DELTA, DELTA, DELTA).contains(p); 0998 0999 if (onLeft && onTop) { 1000 return (ImageCanvas::MoveTopLeft); 1001 } 1002 if (onLeft && onBottom) { 1003 return (ImageCanvas::MoveBottomLeft); 1004 } 1005 1006 if (onRight && onTop) { 1007 return (ImageCanvas::MoveTopRight); 1008 } 1009 if (onRight && onBottom) { 1010 return (ImageCanvas::MoveBottomRight); 1011 } 1012 1013 if (onLeft && overRect) { 1014 return (ImageCanvas::MoveLeft); 1015 } 1016 if (onRight && overRect) { 1017 return (ImageCanvas::MoveRight); 1018 } 1019 if (onTop && overRect) { 1020 return (ImageCanvas::MoveTop); 1021 } 1022 if (onBottom && overRect) { 1023 return (ImageCanvas::MoveBottom); 1024 } 1025 1026 if (withinRect) { 1027 return (ImageCanvas::MoveWhole); 1028 } 1029 1030 return (ImageCanvas::MoveNone); 1031 }