1 
2 /*
3    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
4    All rights reserved.
5 
6    Redistribution and use in source and binary forms, with or without
7    modification, are permitted provided that the following conditions
8    are met:
9 
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.
15 
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.
26 */
27 
28 
29 #define DEBUG_KP_VIEW 0
30 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0)
31 
32 
33 #include "views/kpView.h"
34 #include "kpViewPrivate.h"
35 
36 #include <QPainter>
37 #include <QPaintEvent>
38 #include <QTime>
39 #include <QScrollBar>
40 
41 #include "kpLogCategories.h"
42 
43 #include "layers/selections/kpAbstractSelection.h"
44 #include "imagelib/kpColor.h"
45 #include "document/kpDocument.h"
46 #include "layers/tempImage/kpTempImage.h"
47 #include "layers/selections/text/kpTextSelection.h"
48 #include "views/manager/kpViewManager.h"
49 #include "kpViewScrollableContainer.h"
50 
51 //---------------------------------------------------------------------
52 
53 // protected
paintEventGetDocRect(const QRect & viewRect) const54 QRect kpView::paintEventGetDocRect (const QRect &viewRect) const
55 {
56 #if DEBUG_KP_VIEW_RENDERER && 1
57     qCDebug(kpLogViews) << "kpView::paintEventGetDocRect(" << viewRect << ")";
58 #endif
59 
60     QRect docRect;
61 
62     // From the "we aren't sure whether to round up or round down" department:
63 
64     if (zoomLevelX () < 100 || zoomLevelY () < 100) {
65         docRect = transformViewToDoc (viewRect);
66     }
67     else
68     {
69         // think of a grid - you need to fully cover the zoomed-in pixels
70         // when docRect is zoomed back to the view later
71         docRect = QRect (transformViewToDoc (viewRect.topLeft ()),  // round down
72                          transformViewToDoc (viewRect.bottomRight ()));  // round down
73     }
74 
75     if (zoomLevelX () % 100 || zoomLevelY () % 100)
76     {
77         // at least round up the bottom-right point and deal with matrix weirdness:
78         // - helpful because it ensures we at least cover the required area
79         //   at e.g. 67% or 573%
80         docRect.setBottomRight (docRect.bottomRight () + QPoint (2, 2));
81     }
82 
83 #if DEBUG_KP_VIEW_RENDERER && 1
84     qCDebug(kpLogViews) << "\tdocRect=" << docRect;
85 #endif
86     kpDocument *doc = document ();
87     if (doc)
88     {
89         docRect = docRect.intersected (doc->rect ());
90     #if DEBUG_KP_VIEW_RENDERER && 1
91         qCDebug(kpLogViews) << "\tintersected with doc=" << docRect;
92     #endif
93     }
94 
95     return docRect;
96 }
97 
98 //---------------------------------------------------------------------
99 
100 // public static
drawTransparentBackground(QPainter * painter,const QPoint & patternOrigin,const QRect & viewRect,bool isPreview)101 void kpView::drawTransparentBackground (QPainter *painter,
102                                         const QPoint &patternOrigin,
103                                         const QRect &viewRect,
104                                         bool isPreview)
105 {
106 #if DEBUG_KP_VIEW_RENDERER && 1
107     qCDebug(kpLogViews) << "kpView::drawTransparentBackground() patternOrigin="
108               << patternOrigin
109               << " viewRect=" << viewRect
110               << " isPreview=" << isPreview
111                << endl;
112 #endif
113 
114     const int cellSize = !isPreview ? 16 : 10;
115 
116     // TODO: % is unpredictable with negatives.
117 
118     int starty = viewRect.y ();
119     if ((starty - patternOrigin.y ()) % cellSize) {
120         starty -= ((starty - patternOrigin.y ()) % cellSize);
121     }
122 
123     int startx = viewRect.x ();
124     if ((startx - patternOrigin.x ()) % cellSize) {
125         startx -= ((startx - patternOrigin.x ()) % cellSize);
126     }
127 
128 #if DEBUG_KP_VIEW_RENDERER && 1
129     qCDebug(kpLogViews) << "\tstartXY=" << QPoint (startx, starty);
130 #endif
131 
132     painter->save ();
133 
134     // Clip to <viewRect> as we may draw outside it on all sides.
135     painter->setClipRect (viewRect, Qt::IntersectClip/*honor existing clip*/);
136 
137     for (int y = starty; y <= viewRect.bottom (); y += cellSize)
138     {
139         for (int x = startx; x <= viewRect.right (); x += cellSize)
140         {
141             bool parity = ((x - patternOrigin.x ()) / cellSize +
142                 (y - patternOrigin.y ()) / cellSize) % 2;
143             QColor col;
144 
145             if (parity)
146             {
147                 if (!isPreview) {
148                     col = QColor (213, 213, 213);
149                 }
150                 else {
151                     col = QColor (224, 224, 224);
152                 }
153             }
154             else {
155                 col = Qt::white;
156             }
157 
158             painter->fillRect (x, y, cellSize, cellSize, col);
159         }
160     }
161 
162     painter->restore ();
163 }
164 
165 //---------------------------------------------------------------------
166 
167 // protected
paintEventDrawCheckerBoard(QPainter * painter,const QRect & viewRect)168 void kpView::paintEventDrawCheckerBoard (QPainter *painter, const QRect &viewRect)
169 {
170 #if DEBUG_KP_VIEW_RENDERER && 1
171     qCDebug(kpLogViews) << "kpView(" << objectName ()
172                << ")::paintEventDrawCheckerBoard(viewRect=" << viewRect
173                << ") origin=" << origin ();
174 #endif
175 
176     kpDocument *doc = document ();
177     if (!doc) {
178         return;
179     }
180 
181     QPoint patternOrigin = origin ();
182 
183     if (scrollableContainer ())
184     {
185     #if DEBUG_KP_VIEW_RENDERER && 1
186         qCDebug(kpLogViews) << "\tscrollableContainer: contents[XY]="
187                    << QPoint (scrollableContainer ()->horizontalScrollBar()->value (),
188                               scrollableContainer ()->verticalScrollBar()->value ())
189                    << endl;
190     #endif
191         // Make checkerboard appear static relative to the scroll view.
192         // This makes it more obvious that any visible bits of the
193         // checkboard represent transparent pixels and not gray and white
194         // squares.
195         patternOrigin = QPoint (scrollableContainer ()->horizontalScrollBar()->value(),
196                                 scrollableContainer ()->verticalScrollBar()->value());
197     #if DEBUG_KP_VIEW_RENDERER && 1
198         qCDebug(kpLogViews) << "\t\tpatternOrigin=" << patternOrigin;
199     #endif
200     }
201 
202     // TODO: this static business doesn't work yet
203     patternOrigin = QPoint (0, 0);
204 
205     drawTransparentBackground (painter, patternOrigin, viewRect);
206 }
207 
208 //---------------------------------------------------------------------
209 
210 // protected
paintEventDrawSelection(QImage * destPixmap,const QRect & docRect)211 void kpView::paintEventDrawSelection (QImage *destPixmap, const QRect &docRect)
212 {
213 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
214     qCDebug(kpLogViews) << "kpView::paintEventDrawSelection() docRect=" << docRect;
215 #endif
216 
217     kpDocument *doc = document ();
218     if (!doc)
219     {
220     #if DEBUG_KP_VIEW_RENDERER && 1 || 0
221         qCDebug(kpLogViews) << "\tno doc - abort";
222     #endif
223         return;
224     }
225 
226     kpAbstractSelection *sel = doc->selection ();
227     if (!sel)
228     {
229     #if DEBUG_KP_VIEW_RENDERER && 1 || 0
230         qCDebug(kpLogViews) << "\tno sel - abort";
231     #endif
232         return;
233     }
234 
235 
236     //
237     // Draw selection pixmap (if there is one)
238     //
239 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
240     qCDebug(kpLogViews) << "\tdraw sel pixmap @ " << sel->topLeft ();
241 #endif
242     sel->paint (destPixmap, docRect);
243 
244 
245     //
246     // Draw selection border
247     //
248 
249     kpViewManager *vm = viewManager ();
250 #if DEBUG_KP_VIEW_RENDERER && 1 || 0
251     qCDebug(kpLogViews) << "\tsel border visible="
252                << vm->selectionBorderVisible ();
253 #endif
254     if (vm->selectionBorderVisible ())
255     {
256         sel->paintBorder (destPixmap, docRect, vm->selectionBorderFinished ());
257     }
258 
259 
260     //
261     // Draw text cursor
262     //
263 
264     // TODO: It would be nice to display the text cursor even if it's not
265     //       within the text box (this can happen if the text box is too
266     //       small for the text it contains).
267     //
268     //       However, too much selection repaint code assumes that it
269     //       only paints inside its kpAbstractSelection::boundingRect().
270     auto *textSel = dynamic_cast <kpTextSelection *> (sel);
271     if (textSel &&
272         vm->textCursorEnabled () &&
273         (vm->textCursorBlinkState () ||
274         // For the current main window:
275         //     As long as _any_ view has focus, blink _all_ views not just the
276         //     one with focus.
277         !vm->hasAViewWithFocus ()))  // sync: call will break when vm is not held by 1 mainWindow
278     {
279         QRect rect = vm->textCursorRect ();
280         rect = rect.intersected (textSel->textAreaRect ());
281         if (!rect.isEmpty ())
282         {
283           kpPixmapFX::fillRect(destPixmap,
284               rect.x () - docRect.x (), rect.y () - docRect.y (),
285               rect.width (), rect.height (),
286               kpColor::LightGray, kpColor::DarkGray);
287         }
288     }
289 }
290 
291 //---------------------------------------------------------------------
292 
293 // protected
paintEventDrawSelectionResizeHandles(const QRect & clipRect)294 void kpView::paintEventDrawSelectionResizeHandles (const QRect &clipRect)
295 {
296 #if DEBUG_KP_VIEW_RENDERER && 1
297     qCDebug(kpLogViews) << "kpView::paintEventDrawSelectionResizeHandles("
298                << clipRect << ")";
299 #endif
300 
301     if (!selectionLargeEnoughToHaveResizeHandles ())
302     {
303     #if DEBUG_KP_VIEW_RENDERER && 1
304         qCDebug(kpLogViews) << "\tsel not large enough to have resize handles";
305     #endif
306         return;
307     }
308 
309     kpViewManager *vm = viewManager ();
310     if (!vm || !vm->selectionBorderVisible () || !vm->selectionBorderFinished ())
311     {
312     #if DEBUG_KP_VIEW_RENDERER && 1
313         qCDebug(kpLogViews) << "\tsel border not visible or not finished";
314     #endif
315 
316         return;
317     }
318 
319     const QRect selViewRect = selectionViewRect ();
320 #if DEBUG_KP_VIEW_RENDERER && 1
321     qCDebug(kpLogViews) << "\tselViewRect=" << selViewRect;
322 #endif
323     if (!selViewRect.intersects (clipRect))
324     {
325     #if DEBUG_KP_VIEW_RENDERER && 1
326         qCDebug(kpLogViews) << "\tdoesn't intersect viewRect";
327     #endif
328         return;
329     }
330 
331     QRegion selResizeHandlesRegion = selectionResizeHandlesViewRegion (true/*for renderer*/);
332 #if DEBUG_KP_VIEW_RENDERER && 1
333     qCDebug(kpLogViews) << "\tsel resize handles view region="
334                << selResizeHandlesRegion << endl;
335 #endif
336 
337     QPainter painter(this);
338     painter.setPen(Qt::black);
339     painter.setBrush(Qt::cyan);
340 
341     for (const QRect &r : selResizeHandlesRegion)
342       painter.drawRect(r);
343 }
344 
345 //---------------------------------------------------------------------
346 
347 // protected
paintEventDrawTempImage(QImage * destPixmap,const QRect & docRect)348 void kpView::paintEventDrawTempImage (QImage *destPixmap, const QRect &docRect)
349 {
350     kpViewManager *vm = viewManager ();
351     if (!vm) {
352         return;
353     }
354 
355     const kpTempImage *tpi = vm->tempImage ();
356 #if DEBUG_KP_VIEW_RENDERER && 1
357     qCDebug(kpLogViews) << "kpView::paintEventDrawTempImage() tempImage="
358                << tpi
359                << " isVisible="
360                << (tpi ? tpi->isVisible (vm) : false)
361                << endl;
362 #endif
363 
364     if (!tpi || !tpi->isVisible (vm)) {
365         return;
366     }
367 
368     tpi->paint (destPixmap, docRect);
369 }
370 
371 //---------------------------------------------------------------------
372 
373 // protected
paintEventDrawGridLines(QPainter * painter,const QRect & viewRect)374 void kpView::paintEventDrawGridLines (QPainter *painter, const QRect &viewRect)
375 {
376   int hzoomMultiple = zoomLevelX () / 100;
377   int vzoomMultiple = zoomLevelY () / 100;
378 
379   painter->setPen(Qt::gray);
380 
381   // horizontal lines
382   int starty = viewRect.top();
383   if (starty % vzoomMultiple) {
384     starty = (starty + vzoomMultiple) / vzoomMultiple * vzoomMultiple;
385   }
386 
387   for (int y = starty; y <= viewRect.bottom(); y += vzoomMultiple) {
388     painter->drawLine(viewRect.left(), y, viewRect.right(), y);
389   }
390 
391   // vertical lines
392   int startx = viewRect.left();
393   if (startx % hzoomMultiple) {
394     startx = (startx + hzoomMultiple) / hzoomMultiple * hzoomMultiple;
395   }
396 
397   for (int x = startx; x <= viewRect.right(); x += hzoomMultiple) {
398     painter->drawLine(x, viewRect.top (), x, viewRect.bottom());
399   }
400 }
401 
402 //---------------------------------------------------------------------
403 
404 // This is called "_Unclipped" because it may draw outside of
405 // <viewRect>.
406 //
407 // There are 2 reasons for doing so:
408 //
409 // A. If, for instance:
410 //
411 //    1. <viewRect> = QRect (0, 0, 2, 3) [top-left of the view]
412 //    2. zoomLevelX() == 800
413 //    3. zoomLevelY() == 800
414 //
415 //    Then, the local variable <docRect> will be QRect (0, 0, 1, 1).
416 //    When the part of the document corresponding to <docRect>
417 //    (a single document pixel) is drawn with QPainter::scale(), the
418 //    view rectangle QRect (0, 0, 7, 7) will be overwritten due to the
419 //    8x zoom.  This view rectangle is bigger than <viewRect>.
420 //
421 //    We can't use QPainter::setClipRect() since it is buggy in Qt 4.3.1
422 //    and clips too many pixels when used in combination with scale()
423 //    [qt-bugs@trolltech.com issue N181038].  ==> MK 10.2.2011 - fixed since Qt-4.4.4
424 //
425 // B. paintEventGetDocRect() may, by design, return a larger document
426 //    rectangle than what <viewRect> corresponds to, if the zoom levels
427 //    are not perfectly divisible by 100.
428 //
429 // This over-drawing is dangerous -- see the comments in paintEvent().
430 // This over-drawing is only safe from Qt's perspective since Qt
431 // automatically clips all drawing in paintEvent() (which calls us) to
432 // QPaintEvent::region().
paintEventDrawDoc_Unclipped(const QRect & viewRect)433 void kpView::paintEventDrawDoc_Unclipped (const QRect &viewRect)
434 {
435 #if DEBUG_KP_VIEW_RENDERER
436     QTime timer;
437     timer.start ();
438     qCDebug(kpLogViews) << "\tviewRect=" << viewRect;
439 #endif
440 
441     kpViewManager *vm = viewManager ();
442     const kpDocument *doc = document ();
443 
444     Q_ASSERT (vm);
445     Q_ASSERT (doc);
446 
447     if (viewRect.isEmpty ()) {
448         return;
449     }
450 
451     QRect docRect = paintEventGetDocRect (viewRect);
452 
453 #if DEBUG_KP_VIEW_RENDERER && 1
454     qCDebug(kpLogViews) << "\tdocRect=" << docRect;
455 #endif
456 
457     QPainter painter (this);
458     //painter.setCompositionMode(QPainter::CompositionMode_Source);
459 
460     QImage docPixmap;
461     bool tempImageWillBeRendered = false;
462 
463     // LOTODO: I think <docRect> being empty would be a bug.
464     if (!docRect.isEmpty ())
465     {
466         docPixmap = doc->getImageAt (docRect);
467 
468     #if DEBUG_KP_VIEW_RENDERER && 1
469         qCDebug(kpLogViews) << "\tdocPixmap.hasAlphaChannel()="
470                   << docPixmap.hasAlphaChannel ();
471     #endif
472 
473         tempImageWillBeRendered =
474             (!doc->selection () &&
475              vm->tempImage () &&
476              vm->tempImage ()->isVisible (vm) &&
477              docRect.intersects (vm->tempImage ()->rect ()));
478 
479     #if DEBUG_KP_VIEW_RENDERER && 1
480         qCDebug(kpLogViews) << "\ttempImageWillBeRendered=" << tempImageWillBeRendered
481                    << " (sel=" << doc->selection ()
482                    << " tempImage=" << vm->tempImage ()
483                    << " tempImage.isVisible=" << (vm->tempImage () ? vm->tempImage ()->isVisible (vm) : false)
484                    << " docRect.intersects(tempImage.rect)=" << (vm->tempImage () ? docRect.intersects (vm->tempImage ()->rect ()) : false)
485                    << ")"
486                    << endl;
487     #endif
488     }
489 
490 
491     //
492     // Draw checkboard for transparent images and/or views with borders
493     //
494 
495     if (docPixmap.hasAlphaChannel() ||
496         (tempImageWillBeRendered && vm->tempImage ()->paintMayAddMask ()))
497     {
498         paintEventDrawCheckerBoard (&painter, viewRect);
499     }
500 
501     if (!docRect.isEmpty ())
502     {
503         //
504         // Draw docPixmap + tempImage
505         //
506 
507         if (doc->selection ())
508         {
509             paintEventDrawSelection (&docPixmap, docRect);
510         }
511         else if (tempImageWillBeRendered)
512         {
513             paintEventDrawTempImage (&docPixmap, docRect);
514         }
515 
516     #if DEBUG_KP_VIEW_RENDERER && 1
517         qCDebug(kpLogViews) << "\torigin=" << origin ();
518     #endif
519         // Blit scaled version of docPixmap + tempImage.
520     #if DEBUG_KP_VIEW_RENDERER && 1
521         QTime scaleTimer; scaleTimer.start ();
522     #endif
523         // This is the only troublesome part of the method that draws unclipped.
524         painter.translate (origin ().x (), origin ().y ());
525         painter.scale (double (zoomLevelX ()) / 100.0,
526                        double (zoomLevelY ()) / 100.0);
527         painter.drawImage (docRect, docPixmap);
528         //painter.resetMatrix ();  // back to 1-1 scaling
529     #if DEBUG_KP_VIEW_RENDERER && 1
530         qCDebug(kpLogViews) << "\tscale time=" << scaleTimer.elapsed ();
531     #endif
532 
533     }  // if (!docRect.isEmpty ()) {
534 
535 #if DEBUG_KP_VIEW_RENDERER && 1
536     qCDebug(kpLogViews) << "\tdrawDocRect done in: " << timer.restart () << "ms";
537 #endif
538 }
539 
540 //---------------------------------------------------------------------
541 
542 // protected virtual [base QWidget]
paintEvent(QPaintEvent * e)543 void kpView::paintEvent (QPaintEvent *e)
544 {
545     // sync: kpViewPrivate
546     // WARNING: document(), viewManager() and friends might be 0 in this method.
547     // TODO: I'm not 100% convinced that we always check if their friends are 0.
548 
549 #if DEBUG_KP_VIEW_RENDERER && 1
550     QTime timer;
551     timer.start ();
552 #endif
553 
554     kpViewManager *vm = viewManager ();
555 
556 #if DEBUG_KP_VIEW_RENDERER && 1
557     qCDebug(kpLogViews) << "kpView(" << objectName () << ")::paintEvent() vm=" << (bool) vm
558                << " queueUpdates=" << (vm && vm->queueUpdates ())
559                << " fastUpdates=" << (vm && vm->fastUpdates ())
560                << " viewRect=" << e->rect ()
561                << " topLeft=" << QPoint (x (), y ())
562                << endl;
563 #endif
564 
565     if (!vm) {
566         return;
567     }
568 
569     if (vm->queueUpdates ())
570     {
571         // OPT: if this update was due to the document,
572         //      use document coordinates (in case of a zoom change in
573         //      which view coordinates become out of date)
574         addToQueuedArea (e->region ());
575         return;
576     }
577 
578     kpDocument *doc = document ();
579     if (!doc) {
580         return;
581     }
582 
583 
584     // It seems that e->region() is already clipped by Qt to the visible
585     // part of the view (which could be quite small inside a scrollview).
586     const QRegion viewRegion = e->region ();
587 
588     // Draw all of the requested regions of the document _before_ drawing
589     // the grid lines, buddy rectangle and selection resize handles.
590     // This ordering is important since paintEventDrawDoc_Unclipped()
591     // may draw outside of the view rectangle passed to it.
592     //
593     // To illustrate this, suppose we changed each iteration of the loop
594     // to call paintEventDrawDoc_Unclipped() _and_ then,
595     // paintEventDrawGridLines().  If there are 2 or more iterations of this
596     // loop, paintEventDrawDoc_Unclipped() in one iteration may draw over
597     // parts of nearby grid lines (which were drawn in a previous iteration)
598     // with document pixels.  Those grid line parts are probably not going to
599     // be redrawn, so will appear to be missing.
600     for (const QRect &r : viewRegion)
601       paintEventDrawDoc_Unclipped (r);
602 
603     //
604     // Draw Grid Lines
605     //
606 
607     if ( isGridShown() )
608     {
609       QPainter painter(this);
610       for (const QRect &r : viewRegion)
611         paintEventDrawGridLines(&painter, r);
612     }
613 
614     const QRect r = buddyViewScrollableContainerRectangle();
615     if ( !r.isEmpty() )
616     {
617       QPainter painter(this);
618 
619       painter.setPen(QPen(Qt::lightGray, 1/*width*/, Qt::DotLine));
620       painter.setBackground(Qt::darkGray);
621       painter.setBackgroundMode(Qt::OpaqueMode);
622 
623       painter.drawRect(r.x(), r.y(), r.width() - 1, r.height() - 1);
624     }
625 
626     if (doc->selection ())
627     {
628         // Draw resize handles on top of possible grid lines
629         paintEventDrawSelectionResizeHandles (e->rect ());
630     }
631 
632 #if DEBUG_KP_VIEW_RENDERER && 1
633     qCDebug(kpLogViews) << "\tall done in: " << timer.restart () << "ms";
634 #endif
635 }
636 
637 //---------------------------------------------------------------------
638