File indexing completed on 2024-05-05 04:21:19

0001 
0002 /*
0003    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
0004    Copyright (c) 2005 Kazuki Ohta <mover@hct.zaq.ne.jp>
0005    Copyright (c) 2010 Tasuku Suzuki <stasuku@gmail.com>
0006    All rights reserved.
0007 
0008    Redistribution and use in source and binary forms, with or without
0009    modification, are permitted provided that the following conditions
0010    are met:
0011 
0012    1. Redistributions of source code must retain the above copyright
0013       notice, this list of conditions and the following disclaimer.
0014    2. Redistributions in binary form must reproduce the above copyright
0015       notice, this list of conditions and the following disclaimer in the
0016       documentation and/or other materials provided with the distribution.
0017 
0018    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0019    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0020    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0021    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0022    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0023    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0027    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0028 */
0029 
0030 
0031 #define DEBUG_KP_VIEW 0
0032 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0)
0033 
0034 
0035 #include "kpView.h"
0036 #include "kpViewPrivate.h"
0037 
0038 #include <cstdlib>
0039 
0040 #include <QCursor>
0041 #include <QPoint>
0042 #include <QRect>
0043 #include <QRegion>
0044 #include <QScrollBar>
0045 
0046 #include "kpLogCategories.h"
0047 
0048 #include "document/kpDocument.h"
0049 #include "layers/selections/text/kpTextSelection.h"
0050 #include "tools/kpTool.h"
0051 #include "widgets/toolbars/kpToolToolBar.h"
0052 #include "views/manager/kpViewManager.h"
0053 #include "kpViewScrollableContainer.h"
0054 
0055 //---------------------------------------------------------------------
0056 
0057 // public static
0058 const int kpView::MinZoomLevel = 1;
0059 const int kpView::MaxZoomLevel = 20000;
0060 
0061 //---------------------------------------------------------------------
0062 
0063 kpView::kpView (kpDocument *document,
0064         kpToolToolBar *toolToolBar,
0065         kpViewManager *viewManager,
0066         kpView *buddyView,
0067         kpViewScrollableContainer *scrollableContainer,
0068         QWidget *parent)
0069     : QWidget (parent),
0070       d (new kpViewPrivate ())
0071 {
0072     d->document = document;
0073     d->toolToolBar = toolToolBar;
0074     d->viewManager = viewManager;
0075     d->buddyView = buddyView;
0076     d->scrollableContainer = scrollableContainer;
0077 
0078     d->hzoom = 100;
0079     d->vzoom = 100;
0080     d->origin = QPoint (0, 0);
0081     d->showGrid = false;
0082     d->isBuddyViewScrollableContainerRectangleShown = false;
0083 
0084     // Don't waste CPU drawing default background since its overridden by
0085     // our fully opaque drawing.  In reality, this seems to make no
0086     // difference in performance.
0087     setAttribute(Qt::WA_OpaquePaintEvent, true);
0088 
0089     setFocusPolicy (Qt::WheelFocus);
0090     setMouseTracking (true);  // mouseMoveEvent's even when no mousebtn down
0091     setAttribute (Qt::WA_KeyCompression, true);
0092 }
0093 
0094 //---------------------------------------------------------------------
0095 
0096 kpView::~kpView ()
0097 {
0098     setHasMouse (false);
0099 
0100     delete d;
0101 }
0102 
0103 //---------------------------------------------------------------------
0104 
0105 // public
0106 kpDocument *kpView::document () const
0107 {
0108     return d->document;
0109 }
0110 
0111 //---------------------------------------------------------------------
0112 
0113 // protected
0114 kpAbstractSelection *kpView::selection () const
0115 {
0116     return document () ? document ()->selection () : nullptr;
0117 }
0118 
0119 //---------------------------------------------------------------------
0120 
0121 // protected
0122 kpTextSelection *kpView::textSelection () const
0123 {
0124     return document () ? document ()->textSelection () : nullptr;
0125 }
0126 
0127 //---------------------------------------------------------------------
0128 
0129 // public
0130 kpToolToolBar *kpView::toolToolBar () const
0131 {
0132     return d->toolToolBar;
0133 }
0134 
0135 // protected
0136 kpTool *kpView::tool () const
0137 {
0138     return toolToolBar () ? toolToolBar ()->tool () : nullptr;
0139 }
0140 
0141 // public
0142 kpViewManager *kpView::viewManager () const
0143 {
0144     return d->viewManager;
0145 }
0146 
0147 // public
0148 kpView *kpView::buddyView () const
0149 {
0150     return d->buddyView;
0151 }
0152 
0153 // public
0154 kpViewScrollableContainer *kpView::buddyViewScrollableContainer () const
0155 {
0156     return (buddyView () ? buddyView ()->scrollableContainer () : nullptr);
0157 }
0158 
0159 // public
0160 kpViewScrollableContainer *kpView::scrollableContainer () const
0161 {
0162     return d->scrollableContainer;
0163 }
0164 
0165 
0166 // public
0167 int kpView::zoomLevelX () const
0168 {
0169     return d->hzoom;
0170 }
0171 
0172 // public
0173 int kpView::zoomLevelY () const
0174 {
0175     return d->vzoom;
0176 }
0177 
0178 // public virtual
0179 void kpView::setZoomLevel (int hzoom, int vzoom)
0180 {
0181     hzoom = qBound (MinZoomLevel, hzoom, MaxZoomLevel);
0182     vzoom = qBound (MinZoomLevel, vzoom, MaxZoomLevel);
0183 
0184     if (hzoom == d->hzoom && vzoom == d->vzoom) {
0185         return;
0186     }
0187 
0188     d->hzoom = hzoom;
0189     d->vzoom = vzoom;
0190 
0191     if (viewManager ()) {
0192         viewManager ()->updateView (this);
0193     }
0194 
0195     Q_EMIT zoomLevelChanged (hzoom, vzoom);
0196 }
0197 
0198 
0199 // public
0200 QPoint kpView::origin () const
0201 {
0202     return d->origin;
0203 }
0204 
0205 // public virtual
0206 void kpView::setOrigin (const QPoint &origin)
0207 {
0208 #if DEBUG_KP_VIEW
0209     qCDebug(kpLogViews) << "kpView(" << objectName () << ")::setOrigin" << origin;
0210 #endif
0211 
0212     if (origin == d->origin)
0213     {
0214     #if DEBUG_KP_VIEW
0215         qCDebug(kpLogViews) << "\tNOP";
0216     #endif
0217         return;
0218     }
0219 
0220     d->origin = origin;
0221 
0222     if (viewManager ()) {
0223         viewManager ()->updateView (this);
0224     }
0225 
0226     Q_EMIT originChanged (origin);
0227 }
0228 
0229 
0230 // public
0231 bool kpView::canShowGrid () const
0232 {
0233     // (minimum zoom level < 400% would probably be reported as a bug by
0234     //  users who thought that the grid was a part of the image!)
0235     return ((zoomLevelX () >= 400 && zoomLevelX () % 100 == 0) &&
0236             (zoomLevelY () >= 400 && zoomLevelY () % 100 == 0));
0237 }
0238 
0239 // public
0240 bool kpView::isGridShown () const
0241 {
0242     return d->showGrid;
0243 }
0244 
0245 // public
0246 void kpView::showGrid (bool yes)
0247 {
0248     if (d->showGrid == yes) {
0249         return;
0250     }
0251 
0252     if (yes && !canShowGrid ()) {
0253         return;
0254     }
0255 
0256     d->showGrid = yes;
0257 
0258     if (viewManager ()) {
0259         viewManager ()->updateView (this);
0260     }
0261 }
0262 
0263 
0264 // public
0265 bool kpView::isBuddyViewScrollableContainerRectangleShown () const
0266 {
0267     return d->isBuddyViewScrollableContainerRectangleShown;
0268 }
0269 
0270 // public
0271 void kpView::showBuddyViewScrollableContainerRectangle (bool yes)
0272 {
0273     if (yes == d->isBuddyViewScrollableContainerRectangleShown) {
0274         return;
0275     }
0276 
0277     d->isBuddyViewScrollableContainerRectangleShown = yes;
0278 
0279     if (d->isBuddyViewScrollableContainerRectangleShown)
0280     {
0281         // Got these connect statements by analysing deps of
0282         // updateBuddyViewScrollableContainerRectangle() rect update code.
0283 
0284         connect (this, &kpView::zoomLevelChanged,
0285                  this, &kpView::updateBuddyViewScrollableContainerRectangle);
0286 
0287         connect (this, &kpView::originChanged,
0288                  this, &kpView::updateBuddyViewScrollableContainerRectangle);
0289 
0290         if (buddyViewScrollableContainer ())
0291         {
0292             connect (buddyViewScrollableContainer (),
0293                      &kpViewScrollableContainer::contentsMoved,
0294                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0295 
0296             connect (buddyViewScrollableContainer (), &kpViewScrollableContainer::resized,
0297                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0298         }
0299 
0300         if (buddyView ())
0301         {
0302             connect (buddyView (), &kpView::zoomLevelChanged,
0303                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0304 
0305             connect (buddyView (), &kpView::originChanged,
0306                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0307 
0308             connect (buddyView (),
0309                      static_cast<void (kpView::*)(int,int)>(&kpView::sizeChanged),
0310                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0311         }
0312 
0313     }
0314     else
0315     {
0316         disconnect (this, &kpView::zoomLevelChanged,
0317                     this, &kpView::updateBuddyViewScrollableContainerRectangle);
0318 
0319         disconnect (this, &kpView::originChanged,
0320                  this, &kpView::updateBuddyViewScrollableContainerRectangle);
0321 
0322         if (buddyViewScrollableContainer ())
0323         {
0324             disconnect (buddyViewScrollableContainer (),
0325                      &kpViewScrollableContainer::contentsMoved,
0326                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0327 
0328             disconnect (buddyViewScrollableContainer (), &kpViewScrollableContainer::resized,
0329                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0330         }
0331 
0332         if (buddyView ())
0333         {
0334             disconnect (buddyView (), &kpView::zoomLevelChanged,
0335                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0336 
0337             disconnect (buddyView (), &kpView::originChanged,
0338                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0339 
0340             disconnect (buddyView (),
0341                      static_cast<void (kpView::*)(int,int)>(&kpView::sizeChanged),
0342                      this, &kpView::updateBuddyViewScrollableContainerRectangle);
0343         }
0344 
0345     }
0346 
0347     updateBuddyViewScrollableContainerRectangle ();
0348 }
0349 
0350 
0351 // protected
0352 QRect kpView::buddyViewScrollableContainerRectangle () const
0353 {
0354     return d->buddyViewScrollableContainerRectangle;
0355 }
0356 
0357 // protected slot
0358 void kpView::updateBuddyViewScrollableContainerRectangle ()
0359 {
0360     if (viewManager ()) {
0361         viewManager ()->setQueueUpdates ();
0362     }
0363 
0364     {
0365         if (d->buddyViewScrollableContainerRectangle.isValid ())
0366         {
0367             if (viewManager ())
0368             {
0369                 // Erase last
0370                 viewManager ()->updateViewRectangleEdges (this,
0371                     d->buddyViewScrollableContainerRectangle);
0372             }
0373         }
0374 
0375 
0376         QRect newRect;
0377         if (isBuddyViewScrollableContainerRectangleShown () &&
0378             buddyViewScrollableContainer () && buddyView ())
0379         {
0380             QRect docRect = buddyView ()->transformViewToDoc (
0381                 QRect (buddyViewScrollableContainer ()->horizontalScrollBar()->value(),
0382                        buddyViewScrollableContainer ()->verticalScrollBar()->value(),
0383                        qMin (buddyView ()->width (),
0384                              buddyViewScrollableContainer ()->viewport()->width ()),
0385                        qMin (buddyView ()->height (),
0386                              buddyViewScrollableContainer ()->viewport()->height ())));
0387 
0388 
0389             QRect viewRect = this->transformDocToView (docRect);
0390 
0391 
0392             // (Surround the area of interest by moving outwards by 1 pixel in each
0393             //  direction - don't overlap area)
0394             newRect = QRect (viewRect.x () - 1,
0395                              viewRect.y () - 1,
0396                              viewRect.width () + 2,
0397                              viewRect.height () + 2);
0398         }
0399         else
0400         {
0401             newRect = QRect ();
0402         }
0403 
0404         if (newRect != d->buddyViewScrollableContainerRectangle)
0405         {
0406             // (must set before updateView() for paintEvent() to see new
0407             //  rect)
0408             d->buddyViewScrollableContainerRectangle = newRect;
0409 
0410             if (newRect.isValid ())
0411             {
0412                 if (viewManager ())
0413                 {
0414                     viewManager ()->updateViewRectangleEdges (this,
0415                         d->buddyViewScrollableContainerRectangle);
0416                 }
0417             }
0418         }
0419     }
0420 
0421     if (viewManager ()) {
0422         viewManager ()->restoreQueueUpdates ();
0423     }
0424 }
0425 
0426 //---------------------------------------------------------------------
0427 
0428 // public
0429 double kpView::transformViewToDocX (double viewX) const
0430 {
0431     return (viewX - origin ().x ()) * 100.0 / zoomLevelX ();
0432 }
0433 
0434 //---------------------------------------------------------------------
0435 
0436 // public
0437 double kpView::transformViewToDocY (double viewY) const
0438 {
0439     return (viewY - origin ().y ()) * 100.0 / zoomLevelY ();
0440 }
0441 
0442 //---------------------------------------------------------------------
0443 
0444 // public
0445 QPoint kpView::transformViewToDoc (const QPoint &viewPoint) const
0446 {
0447     return  {static_cast<int> (transformViewToDocX (viewPoint.x ())),
0448                    static_cast<int> (transformViewToDocY (viewPoint.y ()))};
0449 }
0450 
0451 //---------------------------------------------------------------------
0452 
0453 // public
0454 QRect kpView::transformViewToDoc (const QRect &viewRect) const
0455 {
0456     if (zoomLevelX () == 100 && zoomLevelY () == 100)
0457     {
0458         return  {viewRect.x () - origin ().x (), viewRect.y () - origin ().y (),
0459                     viewRect.width (), viewRect.height ()};
0460     }
0461 
0462     const QPoint docTopLeft = transformViewToDoc (viewRect.topLeft ());
0463 
0464     // (don't call transformViewToDoc[XY]() - need to round up dimensions)
0465     const auto docWidth = qRound (double (viewRect.width ()) * 100.0 / double (zoomLevelX ()));
0466     const auto docHeight = qRound (double (viewRect.height ()) * 100.0 / double (zoomLevelY ()));
0467 
0468     // (like QWMatrix::Areas)
0469     return  {docTopLeft.x (), docTopLeft.y (), docWidth, docHeight};
0470 
0471 }
0472 
0473 //---------------------------------------------------------------------
0474 
0475 // public
0476 double kpView::transformDocToViewX (double docX) const
0477 {
0478     return (docX * zoomLevelX () / 100.0) + origin ().x ();
0479 }
0480 
0481 // public
0482 double kpView::transformDocToViewY (double docY) const
0483 {
0484     return (docY * zoomLevelY () / 100.0) + origin ().y ();
0485 }
0486 
0487 // public
0488 QPoint kpView::transformDocToView (const QPoint &docPoint) const
0489 {
0490     return  {static_cast<int> (transformDocToViewX (docPoint.x ())),
0491                 static_cast<int> (transformDocToViewY (docPoint.y ()))};
0492 }
0493 
0494 // public
0495 QRect kpView::transformDocToView (const QRect &docRect) const
0496 {
0497     if (zoomLevelX () == 100 && zoomLevelY () == 100)
0498     {
0499         return  {docRect.x () + origin ().x (), docRect.y () + origin ().y (),
0500                     docRect.width (), docRect.height ()};
0501     }
0502 
0503     const QPoint viewTopLeft = transformDocToView (docRect.topLeft ());
0504 
0505     // (don't call transformDocToView[XY]() - need to round up dimensions)
0506     const int viewWidth = qRound (double (docRect.width ()) * double (zoomLevelX ()) / 100.0);
0507     const int viewHeight = qRound (double (docRect.height ()) * double (zoomLevelY ()) / 100.0);
0508 
0509     // (like QWMatrix::Areas)
0510     return QRect (viewTopLeft.x (), viewTopLeft.y (), viewWidth, viewHeight);
0511 }
0512 
0513 
0514 // public
0515 QPoint kpView::transformViewToOtherView (const QPoint &viewPoint,
0516                                          const kpView *otherView)
0517 {
0518     if (this == otherView) {
0519         return viewPoint;
0520     }
0521 
0522     const double docX = transformViewToDocX (viewPoint.x ());
0523     const double docY = transformViewToDocY (viewPoint.y ());
0524 
0525     const double otherViewX = otherView->transformDocToViewX (docX);
0526     const double otherViewY = otherView->transformDocToViewY (docY);
0527 
0528     return  {static_cast<int> (otherViewX), static_cast<int> (otherViewY)};
0529 }
0530 
0531 
0532 // public
0533 int kpView::zoomedDocWidth () const
0534 {
0535     return document () ? document ()->width () * zoomLevelX () / 100 : 0;
0536 }
0537 
0538 // public
0539 int kpView::zoomedDocHeight () const
0540 {
0541     return document () ? document ()->height () * zoomLevelY () / 100 : 0;
0542 }
0543 
0544 
0545 // public
0546 void kpView::setHasMouse (bool yes)
0547 {
0548     kpViewManager *vm = viewManager ();
0549     if (!vm) {
0550         return;
0551     }
0552 
0553 #if DEBUG_KP_VIEW && 0
0554     qCDebug(kpLogViews) << "kpView(" << objectName ()
0555                << ")::setHasMouse(" << yes
0556                << ") existing viewUnderCursor="
0557                << (vm->viewUnderCursor () ? vm->viewUnderCursor ()->objectName () : "(none)");
0558 #endif
0559     if (yes && vm->viewUnderCursor () != this) {
0560         vm->setViewUnderCursor (this);
0561     }
0562     else if (!yes && vm->viewUnderCursor () == this) {
0563         vm->setViewUnderCursor (nullptr);
0564     }
0565 }
0566 
0567 //---------------------------------------------------------------------
0568 
0569 // public
0570 void kpView::addToQueuedArea (const QRegion &region)
0571 {
0572 #if DEBUG_KP_VIEW && 0
0573     qCDebug(kpLogViews) << "kpView(" << objectName ()
0574                << ")::addToQueuedArea() already=" << d->queuedUpdateArea
0575                << " - plus - " << region
0576                << endl;
0577 #endif
0578     d->queuedUpdateArea += region;
0579 }
0580 
0581 //---------------------------------------------------------------------
0582 
0583 // public
0584 void kpView::addToQueuedArea (const QRect &rect)
0585 {
0586 #if DEBUG_KP_VIEW && 0
0587     qCDebug(kpLogViews) << "kpView(" << objectName ()
0588                << ")::addToQueuedArea() already=" << d->queuedUpdateArea
0589                << " - plus - " << rect
0590                << endl;
0591 #endif
0592     d->queuedUpdateArea += rect;
0593 }
0594 
0595 //---------------------------------------------------------------------
0596 
0597 // public
0598 void kpView::invalidateQueuedArea ()
0599 {
0600 #if DEBUG_KP_VIEW && 0
0601     qCDebug(kpLogViews) << "kpView::invalidateQueuedArea()";
0602 #endif
0603 
0604     d->queuedUpdateArea = QRegion ();
0605 }
0606 
0607 //---------------------------------------------------------------------
0608 
0609 // public
0610 void kpView::updateQueuedArea ()
0611 {
0612     kpViewManager *vm = viewManager ();
0613 #if DEBUG_KP_VIEW && 0
0614     qCDebug(kpLogViews) << "kpView(" << objectName ()
0615                << ")::updateQueuedArea() vm=" << (bool) vm
0616                << " queueUpdates=" << (vm && vm->queueUpdates ())
0617                << " fastUpdates=" << (vm && vm->fastUpdates ())
0618                << " area=" << d->queuedUpdateArea
0619                << endl;
0620 #endif
0621 
0622     if (!vm) {
0623         return;
0624     }
0625 
0626     if (vm->queueUpdates ()) {
0627         return;
0628     }
0629 
0630     if (!d->queuedUpdateArea.isEmpty ()) {
0631         vm->updateView (this, d->queuedUpdateArea);
0632     }
0633 
0634     invalidateQueuedArea ();
0635 }
0636 
0637 //---------------------------------------------------------------------
0638 
0639 // public
0640 QPoint kpView::mouseViewPoint (const QPoint &returnViewPoint) const
0641 {
0642     if (returnViewPoint != KP_INVALID_POINT) {
0643         return returnViewPoint;
0644     }
0645 
0646     // TODO: I don't think this is right for the main view since that's
0647     //       inside the scrollview (which can scroll).
0648     return mapFromGlobal (QCursor::pos ());
0649 }
0650 
0651 //---------------------------------------------------------------------
0652 
0653 // public virtual
0654 QVariant kpView::inputMethodQuery (Qt::InputMethodQuery query) const
0655 {
0656 #if DEBUG_KP_VIEW && 1
0657     qCDebug(kpLogViews) << "kpView(" << objectName () << ")::inputMethodQuery()";
0658 #endif
0659     QVariant ret;
0660     switch (query)
0661     {
0662       case Qt::ImCursorRectangle:
0663       {
0664           QRect r = d->viewManager->textCursorRect ();
0665           r.setTopLeft (r.topLeft () + origin ());
0666           r.setHeight (r.height() + 2);
0667           r = transformDocToView (r);
0668           ret = r;
0669           break;
0670       }
0671       case Qt::ImFont:
0672       {
0673         if (textSelection ())
0674         {
0675             ret = textSelection ()->textStyle ().font ();
0676         }
0677         break;
0678       }
0679       default:
0680         break;
0681     }
0682     return ret;
0683 }
0684 
0685 //---------------------------------------------------------------------
0686 
0687 #include "moc_kpView.cpp"