3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #define DEBUG_KP_VIEW 0
30 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0)
34 #include <kpViewPrivate.h>
37 #include <QPaintEvent>
42 #include <kpAbstractSelection.h>
44 #include <kpDocument.h>
45 #include <kpTempImage.h>
46 #include <kpTextSelection.h>
47 #include <kpViewManager.h>
48 #include <kpViewScrollableContainer.h>
52 bool kpView::isPaintBlank () const
54 return (d
->paintBlankCounter
> 0);
58 void kpView::setPaintBlank ()
60 d
->paintBlankCounter
++;
62 // Go from blank painting to normal painting.
63 if (d
->paintBlankCounter
== 1)
65 // We want to go blank _now_, as we are used to hide flicker in
66 // whatever operation that follows.
72 void kpView::restorePaintBlank ()
74 d
->paintBlankCounter
--;
75 Q_ASSERT (d
->paintBlankCounter
>= 0);
77 // Go from normal painting to blank painting.
78 if (d
->paintBlankCounter
== 0)
80 // Unlike setPaintBlank(), we can wait a while for the normal
81 // contents to re-appear, without any ill effects.
88 QRect
kpView::paintEventGetDocRect (const QRect
&viewRect
) const
90 #if DEBUG_KP_VIEW_RENDERER && 1
91 kDebug () << "kpView::paintEventGetDocRect(" << viewRect
<< ")";
96 // From the "we aren't sure whether to round up or round down" department:
98 if (zoomLevelX () < 100 || zoomLevelY () < 100)
99 docRect
= transformViewToDoc (viewRect
);
102 // think of a grid - you need to fully cover the zoomed-in pixels
103 // when docRect is zoomed back to the view later
104 docRect
= QRect (transformViewToDoc (viewRect
.topLeft ()), // round down
105 transformViewToDoc (viewRect
.bottomRight ())); // round down
108 if (zoomLevelX () % 100 || zoomLevelY () % 100)
110 // at least round up the bottom-right point and deal with matrix weirdness:
111 // - helpful because it ensures we at least cover the required area
112 // at e.g. 67% or 573%
113 docRect
.setBottomRight (docRect
.bottomRight () + QPoint (2, 2));
116 #if DEBUG_KP_VIEW_RENDERER && 1
117 kDebug () << "\tdocRect=" << docRect
;
119 kpDocument
*doc
= document ();
122 docRect
= docRect
.intersect (doc
->rect ());
123 #if DEBUG_KP_VIEW_RENDERER && 1
124 kDebug () << "\tintersect with doc=" << docRect
;
132 void kpView::drawTransparentBackground (QPainter
*painter
,
133 const QPoint
&patternOrigin
,
134 const QRect
&viewRect
,
137 #if DEBUG_KP_VIEW_RENDERER && 1
138 kDebug () << "kpView::drawTransparentBackground() patternOrigin="
140 << " viewRect=" << viewRect
141 << " isPreview=" << isPreview
145 const int cellSize
= !isPreview
? 16 : 10;
147 // TODO: % is unpredictable with negatives.
149 int starty
= viewRect
.y ();
150 if ((starty
- patternOrigin
.y ()) % cellSize
)
151 starty
-= ((starty
- patternOrigin
.y ()) % cellSize
);
153 int startx
= viewRect
.x ();
154 if ((startx
- patternOrigin
.x ()) % cellSize
)
155 startx
-= ((startx
- patternOrigin
.x ()) % cellSize
);
157 #if DEBUG_KP_VIEW_RENDERER && 1
158 kDebug () << "\tstartXY=" << QPoint (startx
, starty
);
163 // Clip to <viewRect> as we may draw outside it on all sides.
164 painter
->setClipRect (viewRect
, Qt::IntersectClip
/*honor existing clip*/);
166 for (int y
= starty
; y
<= viewRect
.bottom (); y
+= cellSize
)
168 for (int x
= startx
; x
<= viewRect
.right (); x
+= cellSize
)
170 bool parity
= ((x
- patternOrigin
.x ()) / cellSize
+
171 (y
- patternOrigin
.y ()) / cellSize
) % 2;
177 col
= QColor (213, 213, 213);
179 col
= QColor (224, 224, 224);
184 painter
->fillRect (x
, y
, cellSize
, cellSize
, col
);
192 void kpView::paintEventDrawCheckerBoard (QPainter
*painter
, const QRect
&viewRect
)
194 #if DEBUG_KP_VIEW_RENDERER && 1
195 kDebug () << "kpView(" << objectName ()
196 << ")::paintEventDrawCheckerBoard(viewRect=" << viewRect
197 << ") origin=" << origin () << endl
;
200 kpDocument
*doc
= document ();
204 QPoint patternOrigin
= origin ();
206 if (scrollableContainer ())
208 #if DEBUG_KP_VIEW_RENDERER && 1
209 kDebug () << "\tscrollableContainer: contents[XY]="
210 << QPoint (scrollableContainer ()->contentsX (),
211 scrollableContainer ()->contentsY ())
212 << " contents[XY]Soon="
213 << QPoint (scrollableContainer ()->contentsXSoon (),
214 scrollableContainer ()->contentsYSoon ())
217 // Make checkerboard appear static relative to the scroll view.
218 // This makes it more obvious that any visible bits of the
219 // checkboard represent transparent pixels and not gray and white
221 patternOrigin
= QPoint (scrollableContainer ()->contentsXSoon (),
222 scrollableContainer ()->contentsYSoon ());
223 #if DEBUG_KP_VIEW_RENDERER && 1
224 kDebug () << "\t\tpatternOrigin=" << patternOrigin
;
228 // TODO: this static business doesn't work yet
229 patternOrigin
= QPoint (0, 0);
231 drawTransparentBackground (painter
, patternOrigin
, viewRect
);
235 void kpView::paintEventDrawSelection (QPixmap
*destPixmap
, const QRect
&docRect
)
237 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
238 kDebug () << "kpView::paintEventDrawSelection() docRect=" << docRect
;
241 kpDocument
*doc
= document ();
244 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
245 kDebug () << "\tno doc - abort";
250 kpAbstractSelection
*sel
= doc
->selection ();
253 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
254 kDebug () << "\tno sel - abort";
261 // Draw selection pixmap (if there is one)
263 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
264 kDebug () << "\tdraw sel pixmap @ " << sel
->topLeft ();
266 sel
->paint (destPixmap
, docRect
);
270 // Draw selection border
273 kpViewManager
*vm
= viewManager ();
274 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
275 kDebug () << "\tsel border visible="
276 << vm
->selectionBorderVisible ()
279 if (vm
->selectionBorderVisible ())
281 sel
->paintBorder (destPixmap
, docRect
, vm
->selectionBorderFinished ());
289 // TODO: It would be nice to display the text cursor even if it's not
290 // within the text box (this can happen if the text box is too
291 // small for the text it contains).
293 // However, too much selection repaint code assumes that it
294 // only paints inside its kpAbstractSelection::boundingRect().
295 kpTextSelection
*textSel
= dynamic_cast <kpTextSelection
*> (sel
);
297 vm
->textCursorEnabled () &&
298 (vm
->textCursorBlinkState () ||
299 // For the current main window:
300 // As long as _any_ view has focus, blink _all_ views not just the
302 !vm
->hasAViewWithFocus ())) // sync: call will break when vm is not held by 1 mainWindow
304 QRect rect
= vm
->textCursorRect ();
305 rect
= rect
.intersect (textSel
->textAreaRect ());
306 if (!rect
.isEmpty ())
308 kpPixmapFX::fillXORRect (destPixmap
,
309 rect
.x () - docRect
.x (), rect
.y () - docRect
.y (),
310 rect
.width (), rect
.height (),
311 kpColor::White
/*XOR color*/,
312 kpColor::LightGray
/*1st hint color if XOR not supported*/,
313 kpColor::DarkGray
/*2nd hint color if XOR not supported*/);
319 bool kpView::selectionResizeHandleAtomicSizeCloseToZoomLevel () const
321 return (abs (selectionResizeHandleAtomicSize () - zoomLevelX () / 100) < 3);
325 void kpView::paintEventDrawSelectionResizeHandles (const QRect
&clipRect
)
327 #if DEBUG_KP_VIEW_RENDERER && 1
328 kDebug () << "kpView::paintEventDrawSelectionResizeHandles("
329 << clipRect
<< ")" << endl
;
332 if (!selectionLargeEnoughToHaveResizeHandles ())
334 #if DEBUG_KP_VIEW_RENDERER && 1
335 kDebug () << "\tsel not large enough to have resize handles";
340 kpViewManager
*vm
= viewManager ();
341 if (!vm
|| !vm
->selectionBorderVisible () || !vm
->selectionBorderFinished ())
343 #if DEBUG_KP_VIEW_RENDERER && 1
344 kDebug () << "\tsel border not visible or not finished";
350 const QRect selViewRect
= selectionViewRect ();
351 #if DEBUG_KP_VIEW_RENDERER && 1
352 kDebug () << "\tselViewRect=" << selViewRect
;
354 if (!selViewRect
.intersects (clipRect
))
356 #if DEBUG_KP_VIEW_RENDERER && 1
357 kDebug () << "\tdoesn't intersect viewRect";
362 QRegion selResizeHandlesRegion
= selectionResizeHandlesViewRegion (true/*for renderer*/);
363 #if DEBUG_KP_VIEW_RENDERER && 1
364 kDebug () << "\tsel resize handles view region="
365 << selResizeHandlesRegion
<< endl
;
368 const bool doXor
= !selectionResizeHandleAtomicSizeCloseToZoomLevel ();
369 foreach (const QRect
&r
, selResizeHandlesRegion
.rects ())
371 QRect s
= r
.intersect (clipRect
);
377 kpPixmapFX::widgetFillXORRect (this,
378 s
.x (), s
.y (), s
.width (), s
.height (),
379 kpColor::White
/*XOR color*/,
380 kpColor::Aqua
/*1st hint color if XOR not supported*/,
381 kpColor::Red
/*2nd hint color if XOR not supported*/);
386 p
.fillRect (s
, Qt::cyan
);
392 void kpView::paintEventDrawTempImage (QPixmap
*destPixmap
, const QRect
&docRect
)
394 kpViewManager
*vm
= viewManager ();
398 const kpTempImage
*tpi
= vm
->tempImage ();
399 #if DEBUG_KP_VIEW_RENDERER && 1
400 kDebug () << "kpView::paintEventDrawTempImage() tempImage="
403 << (tpi
? tpi
->isVisible (vm
) : false)
407 if (!tpi
|| !tpi
->isVisible (vm
))
410 tpi
->paint (kpImage::CastPixmapPtr (destPixmap
), docRect
);
414 void kpView::paintEventDrawGridLines (QPainter
*painter
, const QRect
&viewRect
)
416 int hzoomMultiple
= zoomLevelX () / 100;
417 int vzoomMultiple
= zoomLevelY () / 100;
419 QPen
ordinaryPen (Qt::gray
);
420 QPen
tileBoundaryPen (Qt::lightGray
);
422 painter
->setPen (ordinaryPen
);
425 int starty
= viewRect
.top ();
426 if (starty
% vzoomMultiple
)
427 starty
= (starty
+ vzoomMultiple
) / vzoomMultiple
* vzoomMultiple
;
429 int tileHeight
= 16 * vzoomMultiple
; // CONFIG
431 for (int y
= starty
; y
<= viewRect
.bottom (); y
+= vzoomMultiple
)
434 if (tileHeight
> 0 && (y
- viewRect
.y ()) % tileHeight
== 0)
436 painter
->setPen (tileBoundaryPen
);
437 //painter.setRasterOp (Qt::XorROP);
441 painter
->drawLine (viewRect
.left (), y
, viewRect
.right (), y
);
444 if (tileHeight
> 0 && (y
- viewRect
.y ()) % tileHeight
== 0)
446 painter
->setPen (ordinaryPen
);
447 //painter.setRasterOp (Qt::CopyROP);
453 int startx
= viewRect
.left ();
454 if (startx
% hzoomMultiple
)
455 startx
= (startx
+ hzoomMultiple
) / hzoomMultiple
* hzoomMultiple
;
457 int tileWidth
= 16 * hzoomMultiple
; // CONFIG
459 for (int x
= startx
; x
<= viewRect
.right (); x
+= hzoomMultiple
)
462 if (tileWidth
> 0 && (x
- viewRect
.x ()) % tileWidth
== 0)
464 painter
->setPen (tileBoundaryPen
);
465 //painter.setRasterOp (Qt::XorROP);
469 painter
->drawLine (x
, viewRect
.top (), x
, viewRect
.bottom ());
472 if (tileWidth
> 0 && (x
- viewRect
.x ()) % tileWidth
== 0)
474 painter
->setPen (ordinaryPen
);
475 //painter.setRasterOp (Qt::CopyROP);
481 // This is called "_Unclipped" because it may draw outside of
484 // There are 2 reasons for doing so:
486 // A. If, for instance:
488 // 1. <viewRect> = QRect (0, 0, 2, 3) [top-left of the view]
489 // 2. zoomLevelX() == 800
490 // 3. zoomLevelY() == 800
492 // Then, the local variable <docRect> will be QRect (0, 0, 1, 1).
493 // When the part of the document corresponding to <docRect>
494 // (a single document pixel) is drawn with QPainter::scale(), the
495 // view rectangle QRect (0, 0, 7, 7) will be overwritten due to the
496 // 8x zoom. This view rectangle is bigger than <viewRect>.
498 // We can't use QPainter::setClipRect() since it is buggy in Qt 4.3.1
499 // and clips too many pixels when used in combination with scale()
500 // [qt-bugs@trolltech.com issue N181038].
502 // B. paintEventGetDocRect() may, by design, return a larger document
503 // rectangle than what <viewRect> corresponds to, if the zoom levels
504 // are not perfectly divisible by 100.
506 // This over-drawing is dangerous -- see the comments in paintEvent().
507 // This over-drawing is only safe from Qt's perspective since Qt
508 // automatically clips all drawing in paintEvent() (which calls us) to
509 // QPaintEvent::region().
510 void kpView::paintEventDrawDoc_Unclipped (const QRect
&viewRect
)
512 #if DEBUG_KP_VIEW_RENDERER
515 kDebug () << "\tviewRect=" << viewRect
;
518 kpViewManager
*vm
= viewManager ();
519 const kpDocument
*doc
= document ();
525 if (viewRect
.isEmpty ())
529 QRect docRect
= paintEventGetDocRect (viewRect
);
531 #if DEBUG_KP_VIEW_RENDERER && 1
532 kDebug () << "\tdocRect=" << docRect
;
536 QPainter
painter (this);
540 bool tempImageWillBeRendered
= false;
542 // LOTODO: I think <docRect> being empty would be a bug.
543 if (!docRect
.isEmpty ())
545 docPixmap
= doc
->getImageAt (docRect
);
546 KP_PFX_CHECK_NO_ALPHA_CHANNEL (docPixmap
);
548 #if DEBUG_KP_VIEW_RENDERER && 1
549 kDebug () << "\tdocPixmap.hasAlpha()="
550 << docPixmap
.hasAlpha () << endl
;
553 tempImageWillBeRendered
=
554 (!doc
->selection () &&
556 vm
->tempImage ()->isVisible (vm
) &&
557 docRect
.intersects (vm
->tempImage ()->rect ()));
559 #if DEBUG_KP_VIEW_RENDERER && 1
560 kDebug () << "\ttempImageWillBeRendered=" << tempImageWillBeRendered
561 << " (sel=" << doc
->selection ()
562 << " tempImage=" << vm
->tempImage ()
563 << " tempImage.isVisible=" << (vm
->tempImage () ? vm
->tempImage ()->isVisible (vm
) : false)
564 << " docRect.intersects(tempImage.rect)=" << (vm
->tempImage () ? docRect
.intersects (vm
->tempImage ()->rect ()) : false)
572 // Draw checkboard for transparent images and/or views with borders
575 if (kpPixmapFX::hasMask (docPixmap
) ||
576 (tempImageWillBeRendered
&& vm
->tempImage ()->paintMayAddMask ()))
578 #if DEBUG_KP_VIEW_RENDERER && 1
579 kDebug () << "\tmask=" << kpPixmapFX::hasMask (docPixmap
)
582 paintEventDrawCheckerBoard (&painter
, viewRect
);
586 #if DEBUG_KP_VIEW_RENDERER && 1
587 kDebug () << "\tno mask";
592 if (!docRect
.isEmpty ())
595 // Draw docPixmap + tempImage
598 if (doc
->selection ())
600 paintEventDrawSelection (&docPixmap
, docRect
);
602 else if (tempImageWillBeRendered
)
604 paintEventDrawTempImage (&docPixmap
, docRect
);
607 #if DEBUG_KP_VIEW_RENDERER && 1
608 kDebug () << "\torigin=" << origin ();
610 // Blit scaled version of docPixmap + tempImage.
611 #if DEBUG_KP_VIEW_RENDERER && 1
612 QTime scaleTimer
; scaleTimer
.start ();
614 // This is the only troublesome part of the method that draws unclipped.
615 painter
.translate (origin ().x (), origin ().y ());
616 painter
.scale (double (zoomLevelX ()) / 100.0,
617 double (zoomLevelY ()) / 100.0);
618 painter
.drawPixmap (docRect
, docPixmap
);
619 painter
.resetMatrix (); // back to 1-1 scaling
620 #if DEBUG_KP_VIEW_RENDERER && 1
621 kDebug () << "\tscale time=" << scaleTimer
.elapsed ();
624 } // if (!docRect.isEmpty ()) {
626 #if DEBUG_KP_VIEW_RENDERER && 1
627 kDebug () << "\tdrawDocRect done in: " << timer
.restart () << "ms";
631 // protected virtual [base QWidget]
632 void kpView::paintEvent (QPaintEvent
*e
)
634 // sync: kpViewPrivate
635 // WARNING: document(), viewManager() and friends might be 0 in this method.
636 // TODO: I'm not 100% convinced that we always check if their friends are 0.
638 #if DEBUG_KP_VIEW_RENDERER && 1
643 kpViewManager
*vm
= viewManager ();
645 #if DEBUG_KP_VIEW_RENDERER && 1
646 kDebug () << "kpView(" << objectName () << ")::paintEvent() vm=" << (bool) vm
647 << " queueUpdates=" << (vm
&& vm
->queueUpdates ())
648 << " fastUpdates=" << (vm
&& vm
->fastUpdates ())
649 << " viewRect=" << e
->rect ()
650 << " topLeft=" << QPoint (x (), y ())
657 if (vm
->queueUpdates ())
659 // OPT: if this update was due to the document,
660 // use document coordinates (in case of a zoom change in
661 // which view coordinates become out of date)
662 addToQueuedArea (e
->region ());
671 kpDocument
*doc
= document ();
676 // It seems that e->region() is already clipped by Qt to the visible
677 // part of the view (which could be quite small inside a scrollview).
678 QRegion viewRegion
= e
->region ();
679 QVector
<QRect
> rects
= viewRegion
.rects ();
680 #if DEBUG_KP_VIEW_RENDERER && 1
681 kDebug () << "\t#rects = " << rects
.count ();
684 // Draw all of the requested regions of the document _before_ drawing
685 // the grid lines, buddy rectangle and selection resize handles.
686 // This ordering is important since paintEventDrawDoc_Unclipped()
687 // may draw outside of the view rectangle passed to it.
689 // To illustrate this, suppose we changed each iteration of the loop
690 // to call paintEventDrawDoc_Unclipped() _and_ then,
691 // paintEventDrawGridLines(). If there are 2 or more iterations of this
692 // loop, paintEventDrawDoc_Unclipped() in one iteration may draw over
693 // parts of nearby grid lines (which were drawn in a previous iteration)
694 // with document pixels. Those grid line parts are probably not going to
695 // be redrawn, so will appear to be missing.
696 foreach (const QRect
&r
, rects
)
698 paintEventDrawDoc_Unclipped (r
);
708 QPainter
painter (this);
709 #if DEBUG_KP_VIEW_RENDERER && 1
710 QTime gridTimer
; gridTimer
.start ();
712 foreach (const QRect
&r
, rects
)
713 paintEventDrawGridLines (&painter
, r
);
714 #if DEBUG_KP_VIEW_RENDERER && 1
715 kDebug () << "\tgrid time=" << gridTimer
.elapsed ();
720 const QRect bvsvRect
= buddyViewScrollableContainerRectangle ();
721 if (!bvsvRect
.isEmpty ())
723 kpPixmapFX::widgetDrawStippledXORRect (this,
724 bvsvRect
.x (), bvsvRect
.y (), bvsvRect
.width (), bvsvRect
.height (),
725 kpColor::White
, kpColor::White
, // Stippled XOR colors
726 kpColor::LightGray
, kpColor::DarkGray
, // Hint colors if XOR not supported
731 if (doc
->selection ())
733 // Draw resize handles on top of possible grid lines
734 paintEventDrawSelectionResizeHandles (e
->rect ());
737 #if DEBUG_KP_VIEW_RENDERER && 1
738 kDebug () << "\tall done in: " << timer
.restart () << "ms";