File indexing completed on 2024-04-14 04:16:46

0001 /*
0002    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
0003    Copyright (c) 2011      Martin Koller <kollix@aon.at>
0004    All rights reserved.
0005 
0006    Redistribution and use in source and binary forms, with or without
0007    modification, are permitted provided that the following conditions
0008    are met:
0009 
0010    1. Redistributions of source code must retain the above copyright
0011       notice, this list of conditions and the following disclaimer.
0012    2. Redistributions in binary form must reproduce the above copyright
0013       notice, this list of conditions and the following disclaimer in the
0014       documentation and/or other materials provided with the distribution.
0015 
0016    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0017    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0018    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0019    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0020    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0021    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0022    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0023    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0024    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0025    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0026 */
0027 
0028 #define DEBUG_KP_VIEW_SCROLLABLE_CONTAINER 0
0029 
0030 #include "kpViewScrollableContainer.h"
0031 
0032 #include <QCursor>
0033 #include <QKeyEvent>
0034 #include <QPainter>
0035 #include <QPixmap>
0036 #include <QTimer>
0037 #include <QScrollBar>
0038 
0039 #include "kpLogCategories.h"
0040 #include <KLocalizedString>
0041 
0042 #include "kpDefs.h"
0043 #include "pixmapfx/kpPixmapFX.h"
0044 #include "views/kpView.h"
0045 #include "generic/kpWidgetMapper.h"
0046 
0047 //---------------------------------------------------------------------
0048 
0049 // (Pulled from out of Thurston's hat)
0050 static const int DragScrollLeftTopMargin = 0;
0051 static const int DragScrollRightBottomMargin = 16;  // scrollbarish
0052 static const int DragScrollInterval = 150;
0053 static const int DragScrollInitialInterval = DragScrollInterval * 2;
0054 static const int DragScrollNumPixels = 10;
0055 static const int DragDistanceFromRectMaxFor1stMultiplier = 50;
0056 static const int DragDistanceFromRectMaxFor2ndMultiplier = 100;
0057 
0058 //---------------------------------------------------------------------
0059 //---------------------------------------------------------------------
0060 // a transparent widget above all others in the viewport used only while resizing the document
0061 // to be able to show the resize lines above everything else
0062 
0063 class kpOverlay : public QWidget
0064 {
0065   public:
0066     kpOverlay(QWidget *parent, kpViewScrollableContainer *container)
0067       : QWidget(parent), m_container(container)
0068     {
0069     }
0070 
0071     void paintEvent(QPaintEvent *) override
0072     {
0073       m_container->drawResizeLines();
0074     }
0075 
0076   private:
0077     kpViewScrollableContainer *m_container;
0078 
0079 };
0080 
0081 //---------------------------------------------------------------------
0082 
0083 const int kpGrip::Size = 5;
0084 
0085 //---------------------------------------------------------------------
0086 
0087 kpGrip::kpGrip (GripType type, QWidget *parent)
0088     : QWidget(parent),
0089       m_type (type),
0090       m_startPoint (KP_INVALID_POINT),
0091       m_currentPoint (KP_INVALID_POINT),
0092       m_shouldReleaseMouseButtons (false)
0093 {
0094     setCursor(cursorForType(m_type));
0095 
0096     setMouseTracking(true);  // mouseMoveEvent's even when no mousebtn down
0097 
0098     setAutoFillBackground(true);
0099     setBackgroundRole(QPalette::Highlight);
0100 
0101     setFixedSize(kpGrip::Size, kpGrip::Size);
0102 }
0103 
0104 //---------------------------------------------------------------------
0105 
0106 // public
0107 kpGrip::GripType kpGrip::type () const
0108 {
0109     return m_type;
0110 }
0111 
0112 //---------------------------------------------------------------------
0113 
0114 // public static
0115 QCursor kpGrip::cursorForType (GripType type)
0116 {
0117     switch (type)
0118     {
0119     case kpGrip::Bottom:
0120         return Qt::SizeVerCursor;
0121 
0122     case kpGrip::Right:
0123         return Qt::SizeHorCursor;
0124 
0125     case kpGrip::BottomRight:
0126         return Qt::SizeFDiagCursor;
0127     }
0128 
0129     return Qt::ArrowCursor;
0130 }
0131 
0132 //---------------------------------------------------------------------
0133 
0134 // public
0135 bool kpGrip::containsCursor()
0136 {
0137     return isVisible() &&
0138            QRect(mapToGlobal(rect().topLeft()),
0139                  mapToGlobal(rect().bottomRight())).contains(QCursor::pos());
0140 }
0141 
0142 //---------------------------------------------------------------------
0143 
0144 // public
0145 bool kpGrip::isDrawing () const
0146 {
0147     return (m_startPoint != KP_INVALID_POINT);
0148 }
0149 
0150 //---------------------------------------------------------------------
0151 
0152 // public
0153 QString kpGrip::haventBegunDrawUserMessage () const
0154 {
0155     return i18n ("Left drag the handle to resize the image.");
0156 }
0157 
0158 //---------------------------------------------------------------------
0159 
0160 // public
0161 QString kpGrip::userMessage () const
0162 {
0163     return m_userMessage;
0164 }
0165 
0166 //---------------------------------------------------------------------
0167 
0168 // public
0169 void kpGrip::setUserMessage (const QString &message)
0170 {
0171     // Don't do NOP checking here since another grip might have changed
0172     // the message so an apparent NOP for this grip is not a NOP in the
0173     // global sense (kpViewScrollableContainer::slotGripStatusMessageChanged()).
0174 
0175     m_userMessage = message;
0176     Q_EMIT statusMessageChanged (message);
0177 }
0178 
0179 //---------------------------------------------------------------------
0180 
0181 // protected
0182 void kpGrip::cancel ()
0183 {
0184 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0185     qCDebug(kpLogMisc) << "kpGrip::cancel()";
0186 #endif
0187     if (m_currentPoint == KP_INVALID_POINT) {
0188         return;
0189     }
0190 
0191     m_startPoint = KP_INVALID_POINT;
0192     m_currentPoint = KP_INVALID_POINT;
0193 
0194     setUserMessage (i18n ("Resize Image: Let go of all the mouse buttons."));
0195     setCursor (Qt::ArrowCursor);
0196     m_shouldReleaseMouseButtons = true;
0197 
0198     Q_EMIT cancelledDraw ();
0199 }
0200 
0201 //---------------------------------------------------------------------
0202 
0203 // protected virtual [base QWidget]
0204 void kpGrip::keyReleaseEvent (QKeyEvent *e)
0205 {
0206     if (m_startPoint != KP_INVALID_POINT &&
0207         e->key () == Qt::Key_Escape)
0208     {
0209         cancel ();
0210     }
0211 }
0212 
0213 //---------------------------------------------------------------------
0214 
0215 // protected virtual [base QWidget]
0216 void kpGrip::mousePressEvent (QMouseEvent *e)
0217 {
0218     if (m_startPoint == KP_INVALID_POINT &&
0219         (e->buttons () & Qt::MouseButtonMask) == Qt::LeftButton)
0220     {
0221         m_startPoint = e->pos ();
0222         m_currentPoint = e->pos ();
0223         Q_EMIT beganDraw ();
0224         setFocus();  // allow to receive keyboard events to be able to handle ESC
0225 
0226         setUserMessage (i18n ("Resize Image: Right click to cancel."));
0227         setCursor (cursorForType (m_type));
0228     }
0229     else
0230     {
0231         if (m_startPoint != KP_INVALID_POINT) {
0232             cancel ();
0233         }
0234     }
0235 }
0236 
0237 //---------------------------------------------------------------------
0238 
0239 // public
0240 QPoint kpGrip::viewDeltaPoint () const
0241 {
0242     if (m_startPoint == KP_INVALID_POINT) {
0243         return KP_INVALID_POINT;
0244     }
0245 
0246     const QPoint point = mapFromGlobal (QCursor::pos ());
0247 
0248     // TODO: this is getting out of sync with m_currentPoint
0249 
0250     return  {(m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0,
0251                 (m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0};
0252 
0253 }
0254 
0255 //---------------------------------------------------------------------
0256 
0257 // public
0258 void kpGrip::mouseMovedTo (const QPoint &point, bool dueToDragScroll)
0259 {
0260     if (m_startPoint == KP_INVALID_POINT) {
0261         return;
0262     }
0263 
0264     m_currentPoint = point;
0265 
0266     Q_EMIT continuedDraw (((m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0),
0267                         ((m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0),
0268                         dueToDragScroll);
0269 }
0270 
0271 //---------------------------------------------------------------------
0272 
0273 // protected virtual [base QWidget]
0274 void kpGrip::mouseMoveEvent (QMouseEvent *e)
0275 {
0276 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0277     qCDebug(kpLogMisc) << "kpGrip::mouseMoveEvent() m_startPoint=" << m_startPoint
0278                << " stateAfter: buttons=" << (int *) (int) e->buttons ();
0279 #endif
0280 
0281     if (m_startPoint == KP_INVALID_POINT)
0282     {
0283         if ((e->buttons () & Qt::MouseButtonMask) == 0) {
0284             setUserMessage (haventBegunDrawUserMessage ());
0285         }
0286         return;
0287     }
0288 
0289     mouseMovedTo (e->pos (), false/*not due to drag scroll*/);
0290 }
0291 
0292 //---------------------------------------------------------------------
0293 
0294 // protected virtual [base QWidget]
0295 void kpGrip::mouseReleaseEvent (QMouseEvent *e)
0296 {
0297 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0298     qCDebug(kpLogMisc) << "kpGrip::mouseReleaseEvent() m_startPoint=" << m_startPoint
0299                << " stateAfter: buttons=" << (int *) (int) e->buttons ();
0300 #endif
0301 
0302     if (m_startPoint != KP_INVALID_POINT)
0303     {
0304         const int dx = m_currentPoint.x () - m_startPoint.x (),
0305                   dy = m_currentPoint.y () - m_startPoint.y ();
0306 
0307         m_currentPoint = KP_INVALID_POINT;
0308         m_startPoint = KP_INVALID_POINT;
0309 
0310         Q_EMIT endedDraw ((m_type & kpGrip::Right) ? dx : 0,
0311                         (m_type & kpGrip::Bottom) ? dy : 0);
0312     }
0313 
0314     if ((e->buttons () & Qt::MouseButtonMask) == 0)
0315     {
0316         m_shouldReleaseMouseButtons = false;
0317         setUserMessage(QString());
0318         setCursor (cursorForType (m_type));
0319 
0320         Q_EMIT releasedAllButtons ();
0321     }
0322 }
0323 
0324 //---------------------------------------------------------------------
0325 
0326 // protected virtual [base QWidget]
0327 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0328 void kpGrip::enterEvent (QEnterEvent * /*e*/)
0329 #else
0330 void kpGrip::enterEvent (QEvent * /*e*/)
0331 #endif
0332 {
0333 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0334     qCDebug(kpLogMisc) << "kpGrip::enterEvent()"
0335                << " m_startPoint=" << m_startPoint
0336                << " shouldReleaseMouseButtons="
0337                << m_shouldReleaseMouseButtons;
0338 #endif
0339 
0340     if (m_startPoint == KP_INVALID_POINT &&
0341         !m_shouldReleaseMouseButtons)
0342     {
0343     #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0344         qCDebug(kpLogMisc) << "\tsending message";
0345     #endif
0346         setUserMessage (haventBegunDrawUserMessage ());
0347     }
0348 }
0349 
0350 //---------------------------------------------------------------------
0351 
0352 // protected virtual [base QWidget]
0353 void kpGrip::leaveEvent (QEvent * /*e*/)
0354 {
0355 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0356     qCDebug(kpLogMisc) << "kpGrip::leaveEvent()"
0357                << " m_startPoint=" << m_startPoint
0358                << " shouldReleaseMouseButtons="
0359                << m_shouldReleaseMouseButtons;
0360 #endif
0361     if (m_startPoint == KP_INVALID_POINT &&
0362         !m_shouldReleaseMouseButtons)
0363     {
0364         setUserMessage(QString());
0365     }
0366 }
0367 
0368 //---------------------------------------------------------------------
0369 //---------------------------------------------------------------------
0370 //---------------------------------------------------------------------
0371 
0372 // TODO: Are we checking for m_view == 0 often enough?  Also an issue in KDE 3.
0373 kpViewScrollableContainer::kpViewScrollableContainer(QWidget *parent)
0374     : QScrollArea(parent),
0375       m_view(nullptr), m_overlay(new kpOverlay(viewport(), this)),
0376       m_docResizingGrip (nullptr),
0377       m_dragScrollTimer (new QTimer (this)),
0378       m_zoomLevel (100),
0379       m_scrollTimerRunOnce (false),
0380       m_resizeRoundedLastViewX (-1), m_resizeRoundedLastViewY (-1),
0381       m_resizeRoundedLastViewDX (0), m_resizeRoundedLastViewDY (0),
0382       m_haveMovedFromOriginalDocSize (false)
0383 
0384 {
0385     // the base widget holding the documents view plus the resize grips
0386     setWidget(new QWidget(viewport()));
0387 
0388     m_bottomGrip = new kpGrip(kpGrip::Bottom, widget());
0389     m_rightGrip = new kpGrip(kpGrip::Right, widget());
0390     m_bottomRightGrip = new kpGrip(kpGrip::BottomRight, widget());
0391 
0392     m_bottomGrip->setObjectName(QStringLiteral("Bottom Grip"));
0393     m_rightGrip->setObjectName(QStringLiteral("Right Grip"));
0394     m_bottomRightGrip->setObjectName(QStringLiteral("BottomRight Grip"));
0395 
0396     m_bottomGrip->hide ();
0397     connectGripSignals (m_bottomGrip);
0398 
0399     m_rightGrip->hide ();
0400     connectGripSignals (m_rightGrip);
0401 
0402     m_bottomRightGrip->hide ();
0403     connectGripSignals (m_bottomRightGrip);
0404 
0405 
0406     connect (horizontalScrollBar(), &QScrollBar::valueChanged,
0407              this, &kpViewScrollableContainer::slotContentsMoved);
0408 
0409     connect (verticalScrollBar(), &QScrollBar::valueChanged,
0410              this, &kpViewScrollableContainer::slotContentsMoved);
0411 
0412     connect (m_dragScrollTimer, &QTimer::timeout, this, [this]{slotDragScroll();});
0413 
0414     m_overlay->hide();
0415 }
0416 
0417 //---------------------------------------------------------------------
0418 
0419 // protected
0420 void kpViewScrollableContainer::connectGripSignals (kpGrip *grip)
0421 {
0422     connect (grip, &kpGrip::beganDraw,
0423              this, &kpViewScrollableContainer::slotGripBeganDraw);
0424 
0425     connect (grip, &kpGrip::continuedDraw,
0426              this, &kpViewScrollableContainer::slotGripContinuedDraw);
0427 
0428     connect (grip, &kpGrip::cancelledDraw,
0429              this, &kpViewScrollableContainer::slotGripCancelledDraw);
0430 
0431     connect (grip, &kpGrip::endedDraw,
0432              this, &kpViewScrollableContainer::slotGripEndedDraw);
0433 
0434     connect (grip, &kpGrip::statusMessageChanged,
0435              this, &kpViewScrollableContainer::slotGripStatusMessageChanged);
0436 
0437     connect (grip, &kpGrip::releasedAllButtons,
0438              this, &kpViewScrollableContainer::recalculateStatusMessage);
0439 }
0440 
0441 //---------------------------------------------------------------------
0442 
0443 // public
0444 QSize kpViewScrollableContainer::newDocSize () const
0445 {
0446     return newDocSize (m_resizeRoundedLastViewDX,
0447                        m_resizeRoundedLastViewDY);
0448 }
0449 
0450 //---------------------------------------------------------------------
0451 
0452 // public
0453 bool kpViewScrollableContainer::haveMovedFromOriginalDocSize () const
0454 {
0455     return m_haveMovedFromOriginalDocSize;
0456 }
0457 
0458 //---------------------------------------------------------------------
0459 
0460 // public
0461 QString kpViewScrollableContainer::statusMessage () const
0462 {
0463     return m_gripStatusMessage;
0464 }
0465 
0466 //---------------------------------------------------------------------
0467 
0468 // public
0469 void kpViewScrollableContainer::clearStatusMessage ()
0470 {
0471 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 1
0472     qCDebug(kpLogMisc) << "kpViewScrollableContainer::clearStatusMessage()";
0473 #endif
0474     m_bottomRightGrip->setUserMessage(QString());
0475     m_bottomGrip->setUserMessage(QString());
0476     m_rightGrip->setUserMessage(QString());
0477 }
0478 
0479 //---------------------------------------------------------------------
0480 
0481 // protected
0482 QSize kpViewScrollableContainer::newDocSize (int viewDX, int viewDY) const
0483 {
0484     if (!m_view) {
0485         return {};
0486     }
0487 
0488     if (!docResizingGrip ()) {
0489         return {};
0490     }
0491 
0492     const int docX = static_cast<int> (m_view->transformViewToDocX (m_view->width () + viewDX));
0493     const int docY = static_cast<int> (m_view->transformViewToDocY (m_view->height () + viewDY));
0494 
0495     return  {qMax (1, docX), qMax (1, docY)};
0496 }
0497 
0498 //---------------------------------------------------------------------
0499 
0500 // protected
0501 void kpViewScrollableContainer::calculateDocResizingGrip ()
0502 {
0503     if (m_bottomRightGrip->isDrawing ()) {
0504         m_docResizingGrip = m_bottomRightGrip;
0505     }
0506     else if (m_bottomGrip->isDrawing ()) {
0507         m_docResizingGrip = m_bottomGrip;
0508     }
0509     else if (m_rightGrip->isDrawing ()) {
0510         m_docResizingGrip = m_rightGrip;
0511     }
0512     else {
0513         m_docResizingGrip = nullptr;
0514     }
0515 }
0516 
0517 //---------------------------------------------------------------------
0518 
0519 // protected
0520 kpGrip *kpViewScrollableContainer::docResizingGrip () const
0521 {
0522     return m_docResizingGrip;
0523 }
0524 
0525 //---------------------------------------------------------------------
0526 
0527 // protected
0528 int kpViewScrollableContainer::bottomResizeLineWidth () const
0529 {
0530     if (!docResizingGrip ()) {
0531         return -1;
0532     }
0533 
0534     if (!m_view) {
0535         return -1;
0536     }
0537 
0538     if (docResizingGrip ()->type () & kpGrip::Bottom) {
0539         return qMax (m_view->zoomLevelY () / 100, 1);
0540     }
0541 
0542     return 1;
0543 }
0544 
0545 //---------------------------------------------------------------------
0546 
0547 // protected
0548 int kpViewScrollableContainer::rightResizeLineWidth () const
0549 {
0550     if (!docResizingGrip ()) {
0551         return -1;
0552     }
0553 
0554     if (!m_view) {
0555         return -1;
0556     }
0557 
0558     if (docResizingGrip ()->type () & kpGrip::Right) {
0559         return qMax (m_view->zoomLevelX () / 100, 1);
0560     }
0561 
0562     return 1;
0563 }
0564 
0565 //---------------------------------------------------------------------
0566 
0567 // protected
0568 QRect kpViewScrollableContainer::bottomResizeLineRect () const
0569 {
0570     if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) {
0571         return  {};
0572     }
0573 
0574     QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size());
0575 
0576     return QRect (QPoint (0,
0577                           m_resizeRoundedLastViewY),
0578                   QPoint (m_resizeRoundedLastViewX - 1,
0579                           m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea);
0580 }
0581 
0582 //---------------------------------------------------------------------
0583 
0584 // protected
0585 QRect kpViewScrollableContainer::rightResizeLineRect () const
0586 {
0587     if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) {
0588         return  {};
0589     }
0590 
0591     QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size());
0592 
0593     return QRect (QPoint (m_resizeRoundedLastViewX,
0594                           0),
0595                   QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1,
0596                           m_resizeRoundedLastViewY - 1)).intersected(visibleArea);
0597 }
0598 
0599 //---------------------------------------------------------------------
0600 
0601 // protected
0602 QRect kpViewScrollableContainer::bottomRightResizeLineRect () const
0603 {
0604     if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) {
0605         return  {};
0606     }
0607 
0608     QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size());
0609 
0610     return QRect (QPoint (m_resizeRoundedLastViewX,
0611                           m_resizeRoundedLastViewY),
0612                   QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1,
0613                           m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea);
0614 }
0615 
0616 //---------------------------------------------------------------------
0617 
0618 // private
0619 QRect kpViewScrollableContainer::mapViewToViewport (const QRect &viewRect)
0620 {
0621     if (!viewRect.isValid ()) {
0622         return {};
0623     }
0624 
0625     QRect ret = viewRect;
0626     ret.translate (-horizontalScrollBar()->value() - viewport()->x(), -verticalScrollBar()->value() - viewport()->y());
0627     return ret;
0628 }
0629 
0630 //---------------------------------------------------------------------
0631 
0632 void kpViewScrollableContainer::drawResizeLines ()
0633 {
0634   static const char *stipple[] =
0635   {
0636     "8 8 2 1",
0637     ". c #000000",
0638     "# c #ffffff",
0639     "....####",
0640     "....####",
0641     "....####",
0642     "....####",
0643     "####....",
0644     "####....",
0645     "####....",
0646     "####...."
0647   };
0648 
0649   QPainter p(m_overlay);
0650   p.setBackground(QPixmap(stipple));
0651 
0652   const QRect rightRect = rightResizeLineRect();
0653   if ( rightRect.isValid() )
0654   {
0655     QRect rect = mapViewToViewport(rightRect);
0656     p.setBrushOrigin(rect.x(), rect.y());
0657     p.eraseRect(rect);
0658   }
0659 
0660   const QRect bottomRect = bottomResizeLineRect();
0661   if ( bottomRect.isValid() )
0662   {
0663     QRect rect = mapViewToViewport(bottomRect);
0664     p.setBrushOrigin(rect.x(), rect.y());
0665     p.eraseRect(rect);
0666   }
0667 
0668   const QRect bottomRightRect = bottomRightResizeLineRect ();
0669   if ( bottomRightRect.isValid() )
0670   {
0671     QRect rect = mapViewToViewport(bottomRightRect);
0672     p.setBrushOrigin(rect.x(), rect.y());
0673     p.eraseRect(rect);
0674   }
0675 }
0676 
0677 //---------------------------------------------------------------------
0678 
0679 // protected
0680 void kpViewScrollableContainer::updateResizeLines (int viewX, int viewY,
0681                                                    int viewDX, int viewDY)
0682 {
0683 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0
0684     qCDebug(kpLogMisc) << "kpViewScrollableContainer::updateResizeLines("
0685                << viewX << "," << viewY << ")"
0686                << " oldViewX=" << m_resizeRoundedLastViewX
0687                << " oldViewY=" << m_resizeRoundedLastViewY
0688                << " viewDX=" << viewDX
0689                << " viewDY=" << viewDY;
0690 #endif
0691 
0692 
0693     if (viewX >= 0 && viewY >= 0)
0694     {
0695         m_resizeRoundedLastViewX =
0696                 static_cast<int> (m_view->transformDocToViewX (m_view->transformViewToDocX (viewX)));
0697 
0698         m_resizeRoundedLastViewY =
0699                 static_cast<int> (m_view->transformDocToViewY (m_view->transformViewToDocY (viewY)));
0700 
0701         m_resizeRoundedLastViewDX = viewDX;
0702         m_resizeRoundedLastViewDY = viewDY;
0703     }
0704     else
0705     {
0706         m_resizeRoundedLastViewX = -1;
0707         m_resizeRoundedLastViewY = -1;
0708 
0709         m_resizeRoundedLastViewDX = 0;
0710         m_resizeRoundedLastViewDY = 0;
0711     }
0712 
0713     m_overlay->update();
0714 }
0715 
0716 //---------------------------------------------------------------------
0717 
0718 // protected slot
0719 void kpViewScrollableContainer::slotGripBeganDraw ()
0720 {
0721     if (!m_view) {
0722         return;
0723     }
0724 
0725     m_overlay->resize(viewport()->size());  // make it cover whole viewport
0726     m_overlay->move(viewport()->pos());
0727     m_overlay->show();
0728     m_overlay->raise();  // make it top-most
0729 
0730     calculateDocResizingGrip ();
0731 
0732     m_haveMovedFromOriginalDocSize = false;
0733 
0734     updateResizeLines (m_view->width (), m_view->height (),
0735                        0/*viewDX*/, 0/*viewDY*/);
0736 
0737     Q_EMIT beganDocResize ();
0738 }
0739 
0740 //---------------------------------------------------------------------
0741 
0742 // protected slot
0743 void kpViewScrollableContainer::slotGripContinuedDraw (int inViewDX, int inViewDY,
0744                                                        bool dueToDragScroll)
0745 {
0746     int viewDX = inViewDX,
0747         viewDY = inViewDY;
0748 
0749 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0750     qCDebug(kpLogMisc) << "kpViewScrollableContainer::slotGripContinuedDraw("
0751                << viewDX << "," << viewDY << ") size="
0752                << newDocSize (viewDX, viewDY)
0753                << " dueToDragScroll=" << dueToDragScroll;
0754 #endif
0755 
0756     if (!m_view) {
0757         return;
0758     }
0759 
0760     if (!dueToDragScroll &&
0761         beginDragScroll(m_view->zoomLevelX ()))
0762     {
0763         const QPoint newViewDeltaPoint = docResizingGrip ()->viewDeltaPoint ();
0764         viewDX = newViewDeltaPoint.x ();
0765         viewDY = newViewDeltaPoint.y ();
0766     #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0767         qCDebug(kpLogMisc) << "\tdrag scrolled - new view delta point="
0768                    << newViewDeltaPoint;
0769     #endif
0770     }
0771 
0772     m_haveMovedFromOriginalDocSize = true;
0773 
0774     updateResizeLines (qMax (1, qMax (m_view->width () + viewDX, static_cast<int> (m_view->transformDocToViewX (1)))),
0775                        qMax (1, qMax (m_view->height () + viewDY, static_cast<int> (m_view->transformDocToViewY (1)))),
0776                        viewDX, viewDY);
0777 
0778     Q_EMIT continuedDocResize (newDocSize ());
0779 }
0780 
0781 //---------------------------------------------------------------------
0782 
0783 // protected slot
0784 void kpViewScrollableContainer::slotGripCancelledDraw ()
0785 {
0786     m_haveMovedFromOriginalDocSize = false;
0787 
0788     updateResizeLines (-1, -1, 0, 0);
0789 
0790     calculateDocResizingGrip ();
0791 
0792     Q_EMIT cancelledDocResize ();
0793 
0794     endDragScroll ();
0795 
0796     m_overlay->hide();
0797 }
0798 
0799 //---------------------------------------------------------------------
0800 
0801 // protected slot
0802 void kpViewScrollableContainer::slotGripEndedDraw (int viewDX, int viewDY)
0803 {
0804 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0805     qCDebug(kpLogMisc) << "kpViewScrollableContainer::slotGripEndedDraw("
0806                << viewDX << "," << viewDY << ") size="
0807                << newDocSize (viewDX, viewDY);
0808 #endif
0809 
0810     if (!m_view) {
0811         return;
0812     }
0813 
0814     const QSize newSize = newDocSize (viewDX, viewDY);
0815 
0816     m_haveMovedFromOriginalDocSize = false;
0817 
0818     // must erase lines before view size changes
0819     updateResizeLines (-1, -1, 0, 0);
0820 
0821     calculateDocResizingGrip ();
0822 
0823     Q_EMIT endedDocResize (newSize);
0824 
0825     endDragScroll ();
0826 
0827     m_overlay->hide();
0828 }
0829 
0830 //---------------------------------------------------------------------
0831 
0832 // protected slot
0833 void kpViewScrollableContainer::slotGripStatusMessageChanged (const QString &string)
0834 {
0835     if (string == m_gripStatusMessage) {
0836         return;
0837     }
0838 
0839     m_gripStatusMessage = string;
0840     Q_EMIT statusMessageChanged (string);
0841 }
0842 
0843 //---------------------------------------------------------------------
0844 
0845 // public slot
0846 void kpViewScrollableContainer::recalculateStatusMessage ()
0847 {
0848 #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER
0849     qCDebug(kpLogMisc) << "kpViewScrollabelContainer::recalculateStatusMessage()";
0850     qCDebug(kpLogMisc) << "\tQCursor::pos=" << QCursor::pos ()
0851                << " global visibleRect="
0852                << kpWidgetMapper::toGlobal (this,
0853                       QRect(0, 0, viewport->width(), viewport->height()));
0854 #endif
0855 
0856     // HACK: After dragging to a new size, handles move so that they are now
0857     //       under the mouse pointer but no mouseMoveEvent() is generated for
0858     //       any grip.  This also handles the case of canceling over any
0859     //       grip.
0860     //
0861     if (kpWidgetMapper::toGlobal (this,
0862                                   QRect(0, 0, viewport()->width(), viewport()->height()))
0863             .contains (QCursor::pos ()))
0864     {
0865         if ( m_bottomRightGrip->containsCursor() )
0866         {
0867             m_bottomRightGrip->setUserMessage (i18n ("Left drag the handle to resize the image."));
0868         }
0869         else if ( m_bottomGrip->containsCursor() )
0870         {
0871             m_bottomGrip->setUserMessage (i18n ("Left drag the handle to resize the image."));
0872         }
0873         else if ( m_rightGrip->containsCursor() )
0874         {
0875             m_rightGrip->setUserMessage (i18n ("Left drag the handle to resize the image."));
0876         }
0877         else
0878         {
0879             clearStatusMessage ();
0880         }
0881     }
0882     else
0883     {
0884         clearStatusMessage ();
0885     }
0886 }
0887 
0888 //---------------------------------------------------------------------
0889 
0890 // protected slot
0891 void kpViewScrollableContainer::slotContentsMoved ()
0892 {
0893     kpGrip *grip = docResizingGrip ();
0894     if (grip)
0895     {
0896       grip->mouseMovedTo (grip->mapFromGlobal (QCursor::pos ()),
0897                           true/*moved due to drag scroll*/);
0898     }
0899 
0900     m_overlay->move(viewport()->pos());
0901     m_overlay->update();
0902 
0903     Q_EMIT contentsMoved();
0904 }
0905 
0906 //---------------------------------------------------------------------
0907 
0908 // protected
0909 void kpViewScrollableContainer::disconnectViewSignals ()
0910 {
0911     disconnect (m_view,
0912                 static_cast<void (kpView::*)(int, int)>(&kpView::sizeChanged),
0913                 this, &kpViewScrollableContainer::updateGrips);
0914 
0915     disconnect (m_view, &kpView::destroyed,
0916                 this, &kpViewScrollableContainer::slotViewDestroyed);
0917 }
0918 
0919 //---------------------------------------------------------------------
0920 
0921 // protected
0922 void kpViewScrollableContainer::connectViewSignals ()
0923 {
0924     connect (m_view,
0925              static_cast<void (kpView::*)(int, int)>(&kpView::sizeChanged),
0926              this, &kpViewScrollableContainer::updateGrips);
0927 
0928     connect (m_view, &kpView::destroyed,
0929              this, &kpViewScrollableContainer::slotViewDestroyed);
0930 }
0931 
0932 //---------------------------------------------------------------------
0933 
0934 // public
0935 kpView *kpViewScrollableContainer::view () const
0936 {
0937     return m_view;
0938 }
0939 
0940 //---------------------------------------------------------------------
0941 
0942 // public
0943 void kpViewScrollableContainer::setView (kpView *view)
0944 {
0945     if (m_view == view) {
0946         return;
0947     }
0948 
0949     if (m_view)
0950     {
0951       disconnectViewSignals ();
0952     }
0953 
0954     m_view = view;
0955 
0956     if ( m_view )
0957     {
0958       m_view->setParent(widget());
0959       m_view->show();
0960     }
0961 
0962     updateGrips ();
0963 
0964     if (m_view)
0965     {
0966       connectViewSignals ();
0967     }
0968 }
0969 
0970 //---------------------------------------------------------------------
0971 
0972 // public slot
0973 void kpViewScrollableContainer::updateGrips ()
0974 {
0975     if (m_view)
0976     {
0977       widget()->resize(m_view->size() + m_bottomRightGrip->size());
0978 
0979       // to make the grip more easily "touchable" make it as high as the view
0980       m_rightGrip->setFixedHeight(m_view->height());
0981       m_rightGrip->move(m_view->width(), 0);
0982 
0983       // to make the grip more easily "touchable" make it as wide as the view
0984       m_bottomGrip->setFixedWidth(m_view->width());
0985       m_bottomGrip->move(0, m_view->height ());
0986 
0987       m_bottomRightGrip->move(m_view->width(), m_view->height());
0988     }
0989 
0990     m_bottomGrip->setHidden (m_view == nullptr);
0991     m_rightGrip->setHidden (m_view == nullptr);
0992     m_bottomRightGrip->setHidden (m_view == nullptr);
0993 
0994     recalculateStatusMessage ();
0995 }
0996 
0997 //---------------------------------------------------------------------
0998 
0999 // protected slot
1000 void kpViewScrollableContainer::slotViewDestroyed ()
1001 {
1002     m_view = nullptr;
1003     updateGrips ();
1004 }
1005 
1006 //---------------------------------------------------------------------
1007 
1008 // public slot
1009 bool kpViewScrollableContainer::beginDragScroll(int zoomLevel, bool *didSomething)
1010 {
1011     if (didSomething) {
1012         *didSomething = false;
1013     }
1014 
1015     m_zoomLevel = zoomLevel;
1016 
1017     const QPoint p = mapFromGlobal (QCursor::pos ());
1018 
1019     bool stopDragScroll = true;
1020     bool scrolled = false;
1021 
1022     if (!noDragScrollRect ().contains (p))
1023     {
1024         if (m_dragScrollTimer->isActive ())
1025         {
1026             if (m_scrollTimerRunOnce)
1027             {
1028                 scrolled = slotDragScroll ();
1029             }
1030         }
1031         else
1032         {
1033             m_scrollTimerRunOnce = false;
1034             m_dragScrollTimer->start (DragScrollInitialInterval);
1035         }
1036 
1037         stopDragScroll = false;
1038     }
1039 
1040     if (stopDragScroll) {
1041         m_dragScrollTimer->stop ();
1042     }
1043 
1044     if (didSomething) {
1045         *didSomething = scrolled;
1046     }
1047 
1048     return scrolled;
1049 }
1050 
1051 //---------------------------------------------------------------------
1052 
1053 // public slot
1054 bool kpViewScrollableContainer::beginDragScroll(int zoomLevel)
1055 {
1056     return beginDragScroll(zoomLevel,
1057                            nullptr/*don't want scrolled notification*/);
1058 }
1059 
1060 //---------------------------------------------------------------------
1061 
1062 // public slot
1063 bool kpViewScrollableContainer::endDragScroll ()
1064 {
1065     if (m_dragScrollTimer->isActive ())
1066     {
1067         m_dragScrollTimer->stop ();
1068         return true;
1069     }
1070 
1071     return false;
1072 }
1073 
1074 //---------------------------------------------------------------------
1075 
1076 static int distanceFromRectToMultiplier (int dist)
1077 {
1078     if (dist < 0) {
1079         return 0;
1080     }
1081 
1082     if (dist < DragDistanceFromRectMaxFor1stMultiplier) {
1083         return 1;
1084     }
1085 
1086     if (dist < DragDistanceFromRectMaxFor2ndMultiplier) {
1087         return 2;
1088     }
1089 
1090     return 4;
1091 }
1092 
1093 //---------------------------------------------------------------------
1094 
1095 // protected slot
1096 bool kpViewScrollableContainer::slotDragScroll (bool *didSomething)
1097 {
1098     bool scrolled = false;
1099 
1100     if (didSomething) {
1101         *didSomething = false;
1102     }
1103 
1104 
1105     const QRect rect = noDragScrollRect ();
1106     const QPoint pos = mapFromGlobal (QCursor::pos ());
1107 
1108     int dx = 0, dy = 0;
1109     int dxMultiplier = 0, dyMultiplier = 0;
1110 
1111     if (pos.x () < rect.left ())
1112     {
1113         dx = -DragScrollNumPixels;
1114         dxMultiplier = distanceFromRectToMultiplier (rect.left () - pos.x ());
1115     }
1116     else if (pos.x () > rect.right ())
1117     {
1118         dx = +DragScrollNumPixels;
1119         dxMultiplier = distanceFromRectToMultiplier (pos.x () - rect.right ());
1120     }
1121 
1122     if (pos.y () < rect.top ())
1123     {
1124         dy = -DragScrollNumPixels;
1125         dyMultiplier = distanceFromRectToMultiplier (rect.top () - pos.y ());
1126     }
1127     else if (pos.y () > rect.bottom ())
1128     {
1129         dy = +DragScrollNumPixels;
1130         dyMultiplier = distanceFromRectToMultiplier (pos.y () - rect.bottom ());
1131     }
1132 
1133     dx *= dxMultiplier;// * qMax (1, m_zoomLevel / 100);
1134     dy *= dyMultiplier;// * qMax (1, m_zoomLevel / 100);
1135 
1136     if (dx || dy)
1137     {
1138         const int oldContentsX = horizontalScrollBar()->value (),
1139                   oldContentsY = verticalScrollBar()->value ();
1140 
1141         horizontalScrollBar()->setValue(oldContentsX + dx);
1142         verticalScrollBar()->setValue(oldContentsY + dy);
1143 
1144         scrolled = (oldContentsX != horizontalScrollBar()->value () ||
1145                     oldContentsY != verticalScrollBar()->value ());
1146 
1147         if (scrolled)
1148         {
1149             QRegion region = QRect (horizontalScrollBar()->value (), verticalScrollBar()->value (),
1150                                     viewport()->width(), viewport()->height());
1151             region -= QRect (oldContentsX, oldContentsY,
1152                              viewport()->width(), viewport()->height());
1153 
1154             // Repaint newly exposed region immediately to reduce tearing
1155             // of scrollView.
1156             m_view->repaint (region);
1157         }
1158     }
1159 
1160     m_dragScrollTimer->start (DragScrollInterval);
1161     m_scrollTimerRunOnce = true;
1162 
1163     if (didSomething) {
1164         *didSomething = scrolled;
1165     }
1166 
1167     return scrolled;
1168 }
1169 
1170 
1171 //---------------------------------------------------------------------
1172 
1173 // protected virtual
1174 void kpViewScrollableContainer::wheelEvent (QWheelEvent *e)
1175 {
1176     e->ignore ();
1177 
1178     if (m_view) {
1179         m_view->wheelEvent (e);
1180     }
1181 
1182     if ( !e->isAccepted() ) {
1183         QScrollArea::wheelEvent(e);
1184     }
1185 }
1186 
1187 //---------------------------------------------------------------------------------
1188 
1189 QRect kpViewScrollableContainer::noDragScrollRect () const
1190 {
1191     return  {DragScrollLeftTopMargin, DragScrollLeftTopMargin,
1192                 width () - DragScrollLeftTopMargin - DragScrollRightBottomMargin,
1193                 height () - DragScrollLeftTopMargin - DragScrollRightBottomMargin};
1194 }
1195 
1196 //---------------------------------------------------------------------
1197 
1198 // protected virtual [base QScrollView]
1199 void kpViewScrollableContainer::resizeEvent (QResizeEvent *e)
1200 {
1201     QScrollArea::resizeEvent (e);
1202 
1203     Q_EMIT resized ();
1204 }
1205 
1206 //---------------------------------------------------------------------
1207 
1208 #include "moc_kpViewScrollableContainer.cpp"