make sure to attach to the document also when a resize event is received prior of...
[kdegraphics.git] / kolourpaint / views / kpView_Paint.cpp
blob148c46a07c3051d1bfd1fb48c10cb8f61a078436
2 /*
3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
4 All rights reserved.
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
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)
33 #include <kpView.h>
34 #include <kpViewPrivate.h>
36 #include <QPainter>
37 #include <QPaintEvent>
38 #include <QTime>
40 #include <KDebug>
42 #include <kpAbstractSelection.h>
43 #include <kpColor.h>
44 #include <kpDocument.h>
45 #include <kpTempImage.h>
46 #include <kpTextSelection.h>
47 #include <kpViewManager.h>
48 #include <kpViewScrollableContainer.h>
51 // public
52 bool kpView::isPaintBlank () const
54 return (d->paintBlankCounter > 0);
57 // public
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.
67 repaint ();
71 // public
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.
82 update ();
87 // protected
88 QRect kpView::paintEventGetDocRect (const QRect &viewRect) const
90 #if DEBUG_KP_VIEW_RENDERER && 1
91 kDebug () << "kpView::paintEventGetDocRect(" << viewRect << ")";
92 #endif
94 QRect docRect;
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);
100 else
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;
118 #endif
119 kpDocument *doc = document ();
120 if (doc)
122 docRect = docRect.intersect (doc->rect ());
123 #if DEBUG_KP_VIEW_RENDERER && 1
124 kDebug () << "\tintersect with doc=" << docRect;
125 #endif
128 return docRect;
131 // public static
132 void kpView::drawTransparentBackground (QPainter *painter,
133 const QPoint &patternOrigin,
134 const QRect &viewRect,
135 bool isPreview)
137 #if DEBUG_KP_VIEW_RENDERER && 1
138 kDebug () << "kpView::drawTransparentBackground() patternOrigin="
139 << patternOrigin
140 << " viewRect=" << viewRect
141 << " isPreview=" << isPreview
142 << endl;
143 #endif
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);
159 #endif
161 painter->save ();
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;
172 QColor col;
174 if (parity)
176 if (!isPreview)
177 col = QColor (213, 213, 213);
178 else
179 col = QColor (224, 224, 224);
181 else
182 col = Qt::white;
184 painter->fillRect (x, y, cellSize, cellSize, col);
188 painter->restore ();
191 // protected
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;
198 #endif
200 kpDocument *doc = document ();
201 if (!doc)
202 return;
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 ())
215 << endl;
216 #endif
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
220 // squares.
221 patternOrigin = QPoint (scrollableContainer ()->contentsXSoon (),
222 scrollableContainer ()->contentsYSoon ());
223 #if DEBUG_KP_VIEW_RENDERER && 1
224 kDebug () << "\t\tpatternOrigin=" << patternOrigin;
225 #endif
228 // TODO: this static business doesn't work yet
229 patternOrigin = QPoint (0, 0);
231 drawTransparentBackground (painter, patternOrigin, viewRect);
234 // protected
235 void kpView::paintEventDrawSelection (QPixmap *destPixmap, const QRect &docRect)
237 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
238 kDebug () << "kpView::paintEventDrawSelection() docRect=" << docRect;
239 #endif
241 kpDocument *doc = document ();
242 if (!doc)
244 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
245 kDebug () << "\tno doc - abort";
246 #endif
247 return;
250 kpAbstractSelection *sel = doc->selection ();
251 if (!sel)
253 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
254 kDebug () << "\tno sel - abort";
255 #endif
256 return;
261 // Draw selection pixmap (if there is one)
263 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
264 kDebug () << "\tdraw sel pixmap @ " << sel->topLeft ();
265 #endif
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 ()
277 << endl;
278 #endif
279 if (vm->selectionBorderVisible ())
281 sel->paintBorder (destPixmap, docRect, vm->selectionBorderFinished ());
286 // Draw text cursor
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);
296 if (textSel &&
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
301 // one with focus.
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*/);
318 // protected
319 bool kpView::selectionResizeHandleAtomicSizeCloseToZoomLevel () const
321 return (abs (selectionResizeHandleAtomicSize () - zoomLevelX () / 100) < 3);
324 // protected
325 void kpView::paintEventDrawSelectionResizeHandles (const QRect &clipRect)
327 #if DEBUG_KP_VIEW_RENDERER && 1
328 kDebug () << "kpView::paintEventDrawSelectionResizeHandles("
329 << clipRect << ")" << endl;
330 #endif
332 if (!selectionLargeEnoughToHaveResizeHandles ())
334 #if DEBUG_KP_VIEW_RENDERER && 1
335 kDebug () << "\tsel not large enough to have resize handles";
336 #endif
337 return;
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";
345 #endif
347 return;
350 const QRect selViewRect = selectionViewRect ();
351 #if DEBUG_KP_VIEW_RENDERER && 1
352 kDebug () << "\tselViewRect=" << selViewRect;
353 #endif
354 if (!selViewRect.intersects (clipRect))
356 #if DEBUG_KP_VIEW_RENDERER && 1
357 kDebug () << "\tdoesn't intersect viewRect";
358 #endif
359 return;
362 QRegion selResizeHandlesRegion = selectionResizeHandlesViewRegion (true/*for renderer*/);
363 #if DEBUG_KP_VIEW_RENDERER && 1
364 kDebug () << "\tsel resize handles view region="
365 << selResizeHandlesRegion << endl;
366 #endif
368 const bool doXor = !selectionResizeHandleAtomicSizeCloseToZoomLevel ();
369 foreach (const QRect &r, selResizeHandlesRegion.rects ())
371 QRect s = r.intersect (clipRect);
372 if (s.isEmpty ())
373 continue;
375 if (doXor)
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*/);
383 else
385 QPainter p (this);
386 p.fillRect (s, Qt::cyan);
391 // protected
392 void kpView::paintEventDrawTempImage (QPixmap *destPixmap, const QRect &docRect)
394 kpViewManager *vm = viewManager ();
395 if (!vm)
396 return;
398 const kpTempImage *tpi = vm->tempImage ();
399 #if DEBUG_KP_VIEW_RENDERER && 1
400 kDebug () << "kpView::paintEventDrawTempImage() tempImage="
401 << tpi
402 << " isVisible="
403 << (tpi ? tpi->isVisible (vm) : false)
404 << endl;
405 #endif
407 if (!tpi || !tpi->isVisible (vm))
408 return;
410 tpi->paint (kpImage::CastPixmapPtr (destPixmap), docRect);
413 // protected
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);
424 // horizontal lines
425 int starty = viewRect.top ();
426 if (starty % vzoomMultiple)
427 starty = (starty + vzoomMultiple) / vzoomMultiple * vzoomMultiple;
428 #if 0
429 int tileHeight = 16 * vzoomMultiple; // CONFIG
430 #endif
431 for (int y = starty; y <= viewRect.bottom (); y += vzoomMultiple)
433 #if 0
434 if (tileHeight > 0 && (y - viewRect.y ()) % tileHeight == 0)
436 painter->setPen (tileBoundaryPen);
437 //painter.setRasterOp (Qt::XorROP);
439 #endif
441 painter->drawLine (viewRect.left (), y, viewRect.right (), y);
443 #if 0
444 if (tileHeight > 0 && (y - viewRect.y ()) % tileHeight == 0)
446 painter->setPen (ordinaryPen);
447 //painter.setRasterOp (Qt::CopyROP);
449 #endif
452 // vertical lines
453 int startx = viewRect.left ();
454 if (startx % hzoomMultiple)
455 startx = (startx + hzoomMultiple) / hzoomMultiple * hzoomMultiple;
456 #if 0
457 int tileWidth = 16 * hzoomMultiple; // CONFIG
458 #endif
459 for (int x = startx; x <= viewRect.right (); x += hzoomMultiple)
461 #if 0
462 if (tileWidth > 0 && (x - viewRect.x ()) % tileWidth == 0)
464 painter->setPen (tileBoundaryPen);
465 //painter.setRasterOp (Qt::XorROP);
467 #endif
469 painter->drawLine (x, viewRect.top (), x, viewRect.bottom ());
471 #if 0
472 if (tileWidth > 0 && (x - viewRect.x ()) % tileWidth == 0)
474 painter->setPen (ordinaryPen);
475 //painter.setRasterOp (Qt::CopyROP);
477 #endif
481 // This is called "_Unclipped" because it may draw outside of
482 // <viewRect>.
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
513 QTime timer;
514 timer.start ();
515 kDebug () << "\tviewRect=" << viewRect;
516 #endif
518 kpViewManager *vm = viewManager ();
519 const kpDocument *doc = document ();
521 Q_ASSERT (vm);
522 Q_ASSERT (doc);
525 if (viewRect.isEmpty ())
526 return;
529 QRect docRect = paintEventGetDocRect (viewRect);
531 #if DEBUG_KP_VIEW_RENDERER && 1
532 kDebug () << "\tdocRect=" << docRect;
533 #endif
536 QPainter painter (this);
539 QPixmap docPixmap;
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;
551 #endif
553 tempImageWillBeRendered =
554 (!doc->selection () &&
555 vm->tempImage () &&
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)
565 << ")"
566 << endl;
567 #endif
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)
580 << endl;
581 #endif
582 paintEventDrawCheckerBoard (&painter, viewRect);
584 else
586 #if DEBUG_KP_VIEW_RENDERER && 1
587 kDebug () << "\tno mask";
588 #endif
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 ();
609 #endif
610 // Blit scaled version of docPixmap + tempImage.
611 #if DEBUG_KP_VIEW_RENDERER && 1
612 QTime scaleTimer; scaleTimer.start ();
613 #endif
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 ();
622 #endif
624 } // if (!docRect.isEmpty ()) {
626 #if DEBUG_KP_VIEW_RENDERER && 1
627 kDebug () << "\tdrawDocRect done in: " << timer.restart () << "ms";
628 #endif
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
639 QTime timer;
640 timer.start ();
641 #endif
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 ())
651 << endl;
652 #endif
654 if (!vm)
655 return;
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 ());
663 return;
667 if (isPaintBlank ())
668 return;
671 kpDocument *doc = document ();
672 if (!doc)
673 return;
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 ();
682 #endif
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);
703 // Draw Grid Lines
706 if (isGridShown ())
708 QPainter painter (this);
709 #if DEBUG_KP_VIEW_RENDERER && 1
710 QTime gridTimer; gridTimer.start ();
711 #endif
712 foreach (const QRect &r, rects)
713 paintEventDrawGridLines (&painter, r);
714 #if DEBUG_KP_VIEW_RENDERER && 1
715 kDebug () << "\tgrid time=" << gridTimer.elapsed ();
716 #endif
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
727 e->rect ());
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";
739 #endif