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