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 ®ion) 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"