File indexing completed on 2024-04-28 13:39:26

0001 /***************************************************************************
0002  *   Copyright (C) 1999-2005 Trolltech AS                                  *
0003  *   Copyright (C) 2006 David Saxton <david@bluehaze.org>                  *
0004  *                                                                         *
0005  *   This file may be distributed and/or modified under the terms of the   *
0006  *   GNU General Public License version 2 as published by the Free         *
0007  *   Software Foundation                                                   *
0008  ***************************************************************************/
0009 
0010 #include "canvas.h"
0011 #include "canvas_private.h"
0012 #include "ktlq3polygonscanner.h"
0013 #include "utils.h"
0014 
0015 #include <ktlqt3support/ktlq3scrollview.h>
0016 
0017 #include <QApplication>
0018 #include <QDesktopWidget>
0019 //#include "q3ptrdict.h"
0020 #include <QPainter>
0021 #include <QTimer>
0022 // #include "q3tl.h"
0023 // #include <q3pointarray.h>   // needed for q3polygonscanner
0024 
0025 #include <algorithm>
0026 
0027 #include <stdlib.h>
0028 
0029 #include <ktechlab_debug.h>
0030 
0031 using namespace std;
0032 
0033 static bool isCanvasDebugEnabled()
0034 {
0035     return false;
0036 }
0037 
0038 // BEGIN class KtlQCanvasClusterizer
0039 
0040 static void include(QRect &r, const QRect &rect)
0041 {
0042     if (rect.left() < r.left()) {
0043         r.setLeft(rect.left());
0044     }
0045     if (rect.right() > r.right()) {
0046         r.setRight(rect.right());
0047     }
0048     if (rect.top() < r.top()) {
0049         r.setTop(rect.top());
0050     }
0051     if (rect.bottom() > r.bottom()) {
0052         r.setBottom(rect.bottom());
0053     }
0054 }
0055 
0056 /*
0057 A KtlQCanvasClusterizer groups rectangles (QRects) into non-overlapping rectangles
0058 by a merging heuristic.
0059 */
0060 KtlQCanvasClusterizer::KtlQCanvasClusterizer(int maxclusters)
0061     : cluster(new QRect[maxclusters])
0062     , count(0)
0063     , maxcl(maxclusters)
0064 {
0065 }
0066 
0067 KtlQCanvasClusterizer::~KtlQCanvasClusterizer()
0068 {
0069     delete[] cluster;
0070 }
0071 
0072 void KtlQCanvasClusterizer::clear()
0073 {
0074     count = 0;
0075 }
0076 
0077 void KtlQCanvasClusterizer::add(int x, int y)
0078 {
0079     add(QRect(x, y, 1, 1));
0080 }
0081 
0082 void KtlQCanvasClusterizer::add(int x, int y, int w, int h)
0083 {
0084     add(QRect(x, y, w, h));
0085 }
0086 
0087 void KtlQCanvasClusterizer::add(const QRect &rect)
0088 {
0089     QRect biggerrect(rect.x() - 1, rect.y() - 1, rect.width() + 2, rect.height() + 2);
0090 
0091     // assert(rect.width()>0 && rect.height()>0);
0092 
0093     int cursor;
0094 
0095     for (cursor = 0; cursor < count; cursor++) {
0096         if (cluster[cursor].contains(rect)) {
0097             // Wholly contained already.
0098             return;
0099         }
0100     }
0101 
0102     int lowestcost = 9999999;
0103     int cheapest = -1;
0104     cursor = 0;
0105     while (cursor < count) {
0106         if (cluster[cursor].intersects(biggerrect)) {
0107             QRect larger = cluster[cursor];
0108             include(larger, rect);
0109             int cost = larger.width() * larger.height() - cluster[cursor].width() * cluster[cursor].height();
0110 
0111             if (cost < lowestcost) {
0112                 bool bad = false;
0113                 for (int c = 0; c < count && !bad; c++) {
0114                     bad = cluster[c].intersects(larger) && c != cursor;
0115                 }
0116 
0117                 if (!bad) {
0118                     cheapest = cursor;
0119                     lowestcost = cost;
0120                 }
0121             }
0122         }
0123         cursor++;
0124     }
0125 
0126     if (cheapest >= 0) {
0127         include(cluster[cheapest], rect);
0128         return;
0129     }
0130 
0131     if (count < maxcl) {
0132         cluster[count++] = rect;
0133         return;
0134     }
0135 
0136     // Do cheapest of:
0137     //     add to closest cluster
0138     //     do cheapest cluster merge, add to new cluster
0139 
0140     lowestcost = 9999999;
0141     cheapest = -1;
0142     cursor = 0;
0143     while (cursor < count) {
0144         QRect larger = cluster[cursor];
0145         include(larger, rect);
0146         int cost = larger.width() * larger.height() - cluster[cursor].width() * cluster[cursor].height();
0147         if (cost < lowestcost) {
0148             bool bad = false;
0149             for (int c = 0; c < count && !bad; c++) {
0150                 bad = cluster[c].intersects(larger) && c != cursor;
0151             }
0152 
0153             if (!bad) {
0154                 cheapest = cursor;
0155                 lowestcost = cost;
0156             }
0157         }
0158         cursor++;
0159     }
0160 
0161     // ###
0162     // could make an heuristic guess as to whether we need to bother
0163     // looking for a cheap merge.
0164 
0165     int cheapestmerge1 = -1;
0166     int cheapestmerge2 = -1;
0167 
0168     int merge1 = 0;
0169     while (merge1 < count) {
0170         int merge2 = 0;
0171         while (merge2 < count) {
0172             if (merge1 != merge2) {
0173                 QRect larger = cluster[merge1];
0174                 include(larger, cluster[merge2]);
0175                 int cost = larger.width() * larger.height() - cluster[merge1].width() * cluster[merge1].height() - cluster[merge2].width() * cluster[merge2].height();
0176                 if (cost < lowestcost) {
0177                     bool bad = false;
0178 
0179                     for (int c = 0; c < count && !bad; c++) {
0180                         bad = cluster[c].intersects(larger) && c != cursor;
0181                     }
0182 
0183                     if (!bad) {
0184                         cheapestmerge1 = merge1;
0185                         cheapestmerge2 = merge2;
0186                         lowestcost = cost;
0187                     }
0188                 }
0189             }
0190             merge2++;
0191         }
0192         merge1++;
0193     }
0194 
0195     if (cheapestmerge1 >= 0) {
0196         include(cluster[cheapestmerge1], cluster[cheapestmerge2]);
0197         cluster[cheapestmerge2] = cluster[count--];
0198     } else {
0199         // if (!cheapest) debugRectangles(rect);
0200         include(cluster[cheapest], rect);
0201     }
0202 
0203     // NB: clusters do not intersect (or intersection will
0204     //     overwrite). This is a result of the above algorithm,
0205     //     given the assumption that (x,y) are ordered topleft
0206     //     to bottomright.
0207 
0208     // ###
0209     //
0210     // add explicit x/y ordering to that comment, move it to the top
0211     // and rephrase it as pre-/post-conditions.
0212 }
0213 
0214 const QRect &KtlQCanvasClusterizer::operator[](int i)
0215 {
0216     return cluster[i];
0217 }
0218 
0219 // END class KtlQCanvasClusterizer
0220 
0221 static int gcd(int a, int b)
0222 {
0223     int r;
0224     while ((r = a % b)) {
0225         a = b;
0226         b = r;
0227     }
0228     return b;
0229 }
0230 
0231 static int scm(int a, int b)
0232 {
0233     int g = gcd(a, b);
0234     return a / g * b;
0235 }
0236 
0237 int KtlQCanvas::toChunkScaling(int x) const
0238 {
0239     return roundDown(x, chunksize);
0240 }
0241 
0242 void KtlQCanvas::initChunkSize(const QRect &s)
0243 {
0244     m_chunkSize = QRect(toChunkScaling(s.left()), toChunkScaling(s.top()), ((s.width() - 1) / chunksize) + 3, ((s.height() - 1) / chunksize) + 3);
0245 }
0246 
0247 void KtlQCanvas::init(int w, int h, int chunksze, int mxclusters)
0248 {
0249     init(QRect(0, 0, w, h), chunksze, mxclusters);
0250 }
0251 
0252 void KtlQCanvas::init(const QRect &r, int chunksze, int mxclusters)
0253 {
0254     m_size = r;
0255     chunksize = chunksze;
0256     maxclusters = mxclusters;
0257     initChunkSize(r);
0258     chunks = new KtlQCanvasChunk[m_chunkSize.width() * m_chunkSize.height()];
0259     update_timer = nullptr;
0260     bgcolor = Qt::white;
0261     grid = nullptr;
0262     htiles = 0;
0263     vtiles = 0;
0264     debug_redraw_areas = false;
0265 }
0266 
0267 KtlQCanvas::KtlQCanvas(QObject *parent)
0268     : QObject(parent)
0269 {
0270     init(0, 0);
0271 }
0272 
0273 KtlQCanvas::KtlQCanvas(const int w, const int h)
0274 {
0275     init(w, h);
0276 }
0277 
0278 KtlQCanvas::KtlQCanvas(QPixmap p, int h, int v, int tilewidth, int tileheight)
0279 {
0280     init(h * tilewidth, v * tileheight, scm(tilewidth, tileheight));
0281     setTiles(p, h, v, tilewidth, tileheight);
0282 }
0283 
0284 void qt_unview(KtlQCanvas *c)
0285 {
0286     for (QList<KtlQCanvasView *>::iterator itView = c->m_viewList.begin(); itView != c->m_viewList.end(); ++itView) {
0287         KtlQCanvasView *view = *itView;
0288         view->viewing = nullptr;
0289     }
0290 }
0291 
0292 KtlQCanvas::~KtlQCanvas()
0293 {
0294     qt_unview(this);
0295     KtlQCanvasItemList all = allItems();
0296     qDeleteAll(all);
0297     delete[] chunks;
0298     delete[] grid;
0299 }
0300 
0301 /*!
0302     \internal
0303     Returns the chunk at a chunk position \a i, \a j.
0304  */
0305 KtlQCanvasChunk &KtlQCanvas::chunk(int i, int j) const
0306 {
0307     i -= m_chunkSize.left();
0308     j -= m_chunkSize.top();
0309     // return chunks[i+m_chunkSize.width()*j];
0310     const int chunkOffset = i + m_chunkSize.width() * j;
0311     if ((chunkOffset < 0) || (chunkOffset >= (m_chunkSize.width() * m_chunkSize.height()))) {
0312         qCWarning(KTL_LOG) << " invalid chunk coordinates: " << i << " " << j;
0313         return chunks[0]; // at least it should not crash
0314     }
0315     return chunks[chunkOffset];
0316 }
0317 
0318 /*!
0319     \internal
0320     Returns the chunk at a pixel position \a x, \a y.
0321  */
0322 KtlQCanvasChunk &KtlQCanvas::chunkContaining(int x, int y) const
0323 {
0324     return chunk(toChunkScaling(x), toChunkScaling(y));
0325 }
0326 
0327 KtlQCanvasItemList KtlQCanvas::allItems()
0328 {
0329     KtlQCanvasItemList list;
0330     SortedCanvasItems::iterator end = m_canvasItems.end();
0331     for (SortedCanvasItems::iterator it = m_canvasItems.begin(); it != end; ++it)
0332         list << it->second;
0333     return list;
0334 }
0335 
0336 void KtlQCanvas::resize(const QRect &newSize)
0337 {
0338     if (newSize == m_size)
0339         return;
0340 
0341     //KtlQCanvasItem *item;
0342     QList<KtlQCanvasItem *> hidden;
0343     SortedCanvasItems::iterator end = m_canvasItems.end();
0344     for (SortedCanvasItems::iterator it = m_canvasItems.begin(); it != end; ++it) {
0345         KtlQCanvasItem *i = it->second;
0346         if (i->isVisible()) {
0347             i->hide();
0348             hidden.append(i);
0349         }
0350     }
0351 
0352     initChunkSize(newSize);
0353     KtlQCanvasChunk *newchunks = new KtlQCanvasChunk[m_chunkSize.width() * m_chunkSize.height()];
0354     m_size = newSize;
0355     delete[] chunks;
0356     chunks = newchunks;
0357 
0358     for (QList<KtlQCanvasItem *>::iterator itItem = hidden.begin(); itItem != hidden.end(); ++itItem) {
0359         KtlQCanvasItem *item = *itItem;
0360         item->show();
0361     }
0362     //  for (item=hidden.first(); item != 0; item=hidden.next()) {  // 2018.08.14 - use QList
0363     //      item->show();
0364     //  }
0365 
0366     setAllChanged();
0367 
0368     emit resized();
0369 }
0370 
0371 void KtlQCanvas::retune(int chunksze, int mxclusters)
0372 {
0373     maxclusters = mxclusters;
0374 
0375     if (chunksize != chunksze) {
0376         QList<KtlQCanvasItem *> hidden;
0377         SortedCanvasItems::iterator end = m_canvasItems.end();
0378         for (SortedCanvasItems::iterator it = m_canvasItems.begin(); it != end; ++it) {
0379             KtlQCanvasItem *i = it->second;
0380             if (i->isVisible()) {
0381                 i->hide();
0382                 hidden.append(i);
0383             }
0384         }
0385 
0386         chunksize = chunksze;
0387 
0388         initChunkSize(m_size);
0389         KtlQCanvasChunk *newchunks = new KtlQCanvasChunk[m_chunkSize.width() * m_chunkSize.height()];
0390         delete[] chunks;
0391         chunks = newchunks;
0392 
0393         for (QList<KtlQCanvasItem *>::iterator itItem = hidden.begin(); itItem != hidden.end(); ++itItem) {
0394             KtlQCanvasItem *item = *itItem;
0395             item->show();
0396         }
0397     }
0398 }
0399 
0400 void KtlQCanvas::addItem(KtlQCanvasItem *item)
0401 {
0402     m_canvasItems.insert(make_pair(item->z(), item));
0403 }
0404 
0405 void KtlQCanvas::removeItem(const KtlQCanvasItem *item)
0406 {
0407     SortedCanvasItems::iterator end = m_canvasItems.end();
0408     for (SortedCanvasItems::iterator it = m_canvasItems.begin(); it != end; ++it) {
0409         if (it->second == item) {
0410             m_canvasItems.erase(it);
0411             return;
0412         }
0413     }
0414 }
0415 
0416 void KtlQCanvas::addView(KtlQCanvasView *view)
0417 {
0418     m_viewList.append(view);
0419     if (htiles > 1 || vtiles > 1 || pm.isNull()) {
0420         // view->viewport()->setBackgroundColor(backgroundColor()); // 2018.11.21
0421         QPalette palette;
0422         palette.setColor(view->viewport()->backgroundRole(), backgroundColor());
0423         view->viewport()->setPalette(palette);
0424     }
0425 }
0426 
0427 void KtlQCanvas::removeView(KtlQCanvasView *view)
0428 {
0429     m_viewList.removeAll(view);
0430 }
0431 
0432 void KtlQCanvas::setUpdatePeriod(int ms)
0433 {
0434     if (ms < 0) {
0435         if (update_timer)
0436             update_timer->stop();
0437     } else {
0438         if (update_timer)
0439             delete update_timer;
0440         update_timer = new QTimer(this);
0441         connect(update_timer, &QTimer::timeout, this, &KtlQCanvas::update);
0442         update_timer->start(ms);
0443     }
0444 }
0445 
0446 // Don't call this unless you know what you're doing.
0447 // p is in the content's co-ordinate example.
0448 void KtlQCanvas::drawViewArea(KtlQCanvasView *view, QPainter *p, const QRect &vr, bool /* dbuf */ /* always false */)
0449 {
0450     QPoint tl = view->contentsToViewport(QPoint(0, 0));
0451 
0452     QTransform wm = view->worldMatrix();
0453     QTransform iwm = wm.inverted();
0454     // ivr = covers all chunks in vr
0455     QRect ivr = iwm.mapRect(vr);
0456     QTransform twm;
0457     twm.translate(tl.x(), tl.y());
0458 
0459     //  QRect all(0,0,width(),height());
0460     QRect all(m_size);
0461 
0462     if (!p->isActive()) {
0463         qCWarning(KTL_LOG) << " painter is not active";
0464     }
0465 
0466     if (!all.contains(ivr)) {
0467         // Need to clip with edge of canvas.
0468 
0469         // For translation-only transformation, it is safe to include the right
0470         // and bottom edges, but otherwise, these must be excluded since they
0471         // are not precisely defined (different bresenham paths).
0472         QPolygon a;
0473         if (wm.m12() == 0.0 && wm.m21() == 0.0 && wm.m11() == 1.0 && wm.m22() == 1.0)
0474             a = QPolygon(QRect(all.x(), all.y(), all.width() + 1, all.height() + 1));
0475         else
0476             a = QPolygon(all);
0477 
0478         a = (wm * twm).map(a);
0479 
0480         // if ( view->viewport()->backgroundMode() == Qt::NoBackground ) // 2018.12.02
0481         QWidget *vp = view->viewport();
0482         if (vp->palette().color(vp->backgroundRole()) == QColor(Qt::transparent)) {
0483             QRect cvr = vr;
0484             cvr.translate(tl.x(), tl.y());
0485             p->setClipRegion(QRegion(cvr) - QRegion(a));
0486             p->fillRect(vr, view->viewport()->palette().brush(QPalette::Active, QPalette::Window));
0487         }
0488         p->setClipRegion(a);
0489     }
0490 
0491 #if 0 // 2018.03.11 - dbuf is always false
0492     if ( dbuf ) {
0493         offscr = QPixmap(vr.width(), vr.height());
0494         offscr.x11SetScreen(p->device()->x11Screen());
0495         //QPainter dbp(&offscr);
0496         QPainter dbp;
0497         const bool isSuccess = dbp.begin(&offscr);
0498         if (!isSuccess) {
0499             qCWarning(KTL_LOG) << " painter not active";
0500         }
0501 
0502         twm.translate(-vr.x(),-vr.y());
0503         twm.translate(-tl.x(),-tl.y());
0504         dbp.setWorldMatrix( wm*twm, true );
0505 
0506         // 2015.11.27 - do not clip, in order to fix drawing of garbage on the screen.
0507         //dbp.setClipRect(0,0,vr.width(), vr.height());
0508 //      dbp.setClipRect(v);
0509         drawCanvasArea(ivr,&dbp,false);
0510         dbp.end();
0511         p->drawPixmap(vr.x(), vr.y(), offscr, 0, 0, vr.width(), vr.height());
0512     } else
0513 #endif
0514     {
0515         QRect r = vr;
0516         r.translate(tl.x(), tl.y()); // move to untransformed co-ords
0517         if (!all.contains(ivr)) {
0518             QRegion inside = p->clipRegion() & r;
0519             // QRegion outside = p->clipRegion() - r;
0520             // p->setClipRegion(outside);
0521             // p->fillRect(outside.boundingRect(),red);
0522             // 2015.11.27 - do not clip, in order to fix drawing of garbage on the screen.
0523             // p->setClipRegion(inside);
0524         } else {
0525             // 2015.11.27 - do not clip, in order to fix drawing of garbage on the screen.
0526             // p->setClipRect(r);
0527         }
0528         p->setWorldTransform(wm * twm);
0529 
0530         p->setBrushOrigin(tl.x(), tl.y());
0531         drawCanvasArea(ivr, p, false);
0532     }
0533 }
0534 
0535 void KtlQCanvas::advance()
0536 {
0537     qCWarning(KTL_LOG) << "KtlQCanvas::advance: TODO"; // TODO
0538 }
0539 
0540 /*!
0541     Repaints changed areas in all views of the canvas.
0542  */
0543 void KtlQCanvas::update()
0544 {
0545     KtlQCanvasClusterizer clusterizer(m_viewList.count());
0546     QList<QRect> doneareas;
0547 
0548     // Q3PtrListIterator<KtlQCanvasView> it(m_viewList);   // 2018.08.14 - see below
0549     // KtlQCanvasView* view;
0550     // while( (view=it.current()) != 0 ) {
0551     //  ++it;
0552     //
0553     for (QList<KtlQCanvasView *>::iterator itView = m_viewList.begin(); itView != m_viewList.end(); ++itView) {
0554         KtlQCanvasView *view = *itView;
0555 
0556         QTransform wm = view->worldMatrix();
0557 
0558         QRect area(view->contentsX(), view->contentsY(), view->visibleWidth(), view->visibleHeight());
0559         if (area.width() > 0 && area.height() > 0) {
0560             if (!wm.isIdentity()) {
0561                 // r = Visible area of the canvas where there are changes
0562                 QRect r = changeBounds(view->inverseWorldMatrix().mapRect(area));
0563                 if (!r.isEmpty()) {
0564                     // as of my testing, drawing below always fails, so just post for an update event to the widget
0565                     view->viewport()->update();
0566 
0567 #if 0
0568                     //view->viewport()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true); // note: remove this when possible
0569                     //QPainter p(view->viewport());
0570                     QPainter p;
0571                     const bool startSucces = p.begin( view->viewport() );
0572                     if (!startSucces) {
0573                         qCWarning(KTL_LOG) << " painter is not active ";
0574                     }
0575                   // Translate to the coordinate system of drawViewArea().
0576                     QPoint tl = view->contentsToViewport(QPoint(0,0));
0577                     p.translate(tl.x(),tl.y());
0578 //                  drawViewArea( view, &p, wm.map(r), true );
0579 #endif
0580                     doneareas.append(r);
0581                 }
0582             } else
0583                 clusterizer.add(area);
0584         }
0585     }
0586 
0587     for (int i = 0; i < clusterizer.clusters(); i++)
0588         drawChanges(clusterizer[i]);
0589 
0590     // for ( QRect* r=doneareas.first(); r != 0; r=doneareas.next() )        // 2018.08.14 - use iterators
0591     //  setUnchanged(*r);
0592     for (QList<QRect>::iterator itDone = doneareas.begin(); itDone != doneareas.end(); ++itDone) {
0593         setUnchanged(*itDone);
0594     }
0595 }
0596 
0597 /*!
0598     Marks the whole canvas as changed.
0599     All views of the canvas will be entirely redrawn when
0600     update() is called next.
0601  */
0602 void KtlQCanvas::setAllChanged()
0603 {
0604     setChanged(m_size);
0605 }
0606 
0607 /*!
0608     Marks \a area as changed. This \a area will be redrawn in all
0609     views that are showing it when update() is called next.
0610  */
0611 void KtlQCanvas::setChanged(const QRect &area)
0612 {
0613     QRect thearea = area.intersected(m_size);
0614 
0615     int mx = toChunkScaling(thearea.x() + thearea.width() + chunksize);
0616     int my = toChunkScaling(thearea.y() + thearea.height() + chunksize);
0617     if (mx > m_chunkSize.right())
0618         mx = m_chunkSize.right();
0619     if (my > m_chunkSize.bottom())
0620         my = m_chunkSize.bottom();
0621 
0622     int x = toChunkScaling(thearea.x());
0623     while (x < mx) {
0624         int y = toChunkScaling(thearea.y());
0625         while (y < my) {
0626             chunk(x, y).change();
0627             y++;
0628         }
0629         x++;
0630     }
0631 }
0632 
0633 /*!
0634     Marks \a area as \e unchanged. The area will \e not be redrawn in
0635     the views for the next update(), unless it is marked or changed
0636     again before the next call to update().
0637  */
0638 void KtlQCanvas::setUnchanged(const QRect &area)
0639 {
0640     QRect thearea = area.intersected(m_size);
0641 
0642     int mx = toChunkScaling(thearea.x() + thearea.width() + chunksize);
0643     int my = toChunkScaling(thearea.y() + thearea.height() + chunksize);
0644     if (mx > m_chunkSize.right())
0645         mx = m_chunkSize.right();
0646     if (my > m_chunkSize.bottom())
0647         my = m_chunkSize.bottom();
0648 
0649     int x = toChunkScaling(thearea.x());
0650     while (x < mx) {
0651         int y = toChunkScaling(thearea.y());
0652         while (y < my) {
0653             chunk(x, y).takeChange();
0654             y++;
0655         }
0656         x++;
0657     }
0658 }
0659 
0660 QRect KtlQCanvas::changeBounds(const QRect &inarea)
0661 {
0662     QRect area = inarea.intersected(m_size);
0663 
0664     int mx = toChunkScaling(area.x() + area.width() + chunksize);
0665     int my = toChunkScaling(area.y() + area.height() + chunksize);
0666     if (mx > m_chunkSize.right())
0667         mx = m_chunkSize.right();
0668     if (my > m_chunkSize.bottom())
0669         my = m_chunkSize.bottom();
0670 
0671     QRect result;
0672 
0673     int x = toChunkScaling(area.x());
0674     while (x < mx) {
0675         int y = toChunkScaling(area.y());
0676         while (y < my) {
0677             KtlQCanvasChunk &ch = chunk(x, y);
0678             if (ch.hasChanged())
0679                 result |= QRect(x, y, 1, 1);
0680             y++;
0681         }
0682         x++;
0683     }
0684 
0685     if (!result.isEmpty()) {
0686         // result.rLeft() *= chunksize; // 2018.11.18
0687         // result.rTop() *= chunksize;
0688         // result.rRight() *= chunksize;
0689         // result.rBottom() *= chunksize;
0690         // result.rRight() += chunksize;
0691         // result.rBottom() += chunksize;
0692 
0693         result.setLeft(result.left() * chunksize);
0694         result.setTop(result.top() * chunksize);
0695         result.setRight(result.right() * chunksize);
0696         result.setBottom(result.bottom() * chunksize);
0697         result.setRight(result.right() + chunksize);
0698         result.setBottom(result.bottom() + chunksize);
0699     }
0700 
0701     return result;
0702 }
0703 
0704 /*!
0705     Redraws the area \a inarea of the KtlQCanvas.
0706  */
0707 void KtlQCanvas::drawChanges(const QRect &inarea)
0708 {
0709     QRect area = inarea.intersected(m_size);
0710 
0711     KtlQCanvasClusterizer clusters(maxclusters);
0712 
0713     int mx = toChunkScaling(area.x() + area.width() + chunksize);
0714     int my = toChunkScaling(area.y() + area.height() + chunksize);
0715     if (mx > m_chunkSize.right())
0716         mx = m_chunkSize.right();
0717     if (my > m_chunkSize.bottom())
0718         my = m_chunkSize.bottom();
0719 
0720     int x = toChunkScaling(area.x());
0721     while (x < mx) {
0722         int y = toChunkScaling(area.y());
0723         while (y < my) {
0724             KtlQCanvasChunk &ch = chunk(x, y);
0725             if (ch.hasChanged())
0726                 clusters.add(x, y);
0727             y++;
0728         }
0729         x++;
0730     }
0731 
0732     for (int i = 0; i < clusters.clusters(); i++) {
0733         QRect elarea = clusters[i];
0734         elarea.setRect(elarea.left() * chunksize, elarea.top() * chunksize, elarea.width() * chunksize, elarea.height() * chunksize);
0735         drawCanvasArea(elarea, nullptr, /*true*/ false);
0736     }
0737 }
0738 
0739 /*!
0740     Paints all canvas items that are in the area \a clip to \a
0741     painter, using double-buffering if \a dbuf is true.
0742 
0743     e.g. to print the canvas to a printer:
0744     \code
0745     QPrinter pr;
0746     if ( pr.setup() ) {
0747     QPainter p(&pr);        // this code is in a comment block
0748     canvas.drawArea( canvas.rect(), &p );
0749 }
0750     \endcode
0751  */
0752 void KtlQCanvas::drawArea(const QRect &clip, QPainter *painter)
0753 {
0754     if (painter)
0755         drawCanvasArea(clip, painter, false);
0756 }
0757 
0758 void KtlQCanvas::drawCanvasArea(const QRect &inarea, QPainter *p, bool /* double_buffer */ /* 2018.03.11 - always false */)
0759 {
0760     QRect area = inarea.intersected(m_size);
0761 
0762     if (!m_viewList.first() && !p)
0763         return; // Nothing to do.
0764 
0765     int lx = toChunkScaling(area.x());
0766     int ly = toChunkScaling(area.y());
0767     int mx = toChunkScaling(area.right());
0768     int my = toChunkScaling(area.bottom());
0769     if (mx >= m_chunkSize.right())
0770         mx = m_chunkSize.right() - 1;
0771     if (my >= m_chunkSize.bottom())
0772         my = m_chunkSize.bottom() - 1;
0773 
0774     // Stores the region within area that need to be drawn. It is relative
0775     // to area.topLeft()  (so as to keep within bounds of 16-bit XRegions)
0776     QRegion rgn;
0777 
0778     for (int x = lx; x <= mx; x++) {
0779         for (int y = ly; y <= my; y++) {
0780             // Only reset change if all views updating, and
0781             // wholy within area. (conservative:  ignore entire boundary)
0782             //
0783             // Disable this to help debugging.
0784             //
0785             if (!p) {
0786                 if (chunk(x, y).takeChange()) {
0787                     // ### should at least make bands
0788                     rgn |= QRegion(x * chunksize - area.x(), y * chunksize - area.y(), chunksize, chunksize);
0789                     //                  allvisible += *chunk(x,y).listPtr();
0790                     setNeedRedraw(chunk(x, y).listPtr());
0791                     //                  chunk(x,y).listPtr()->first()->m_bNeedRedraw = true;
0792                 }
0793             } else {
0794                 //              allvisible += *chunk(x,y).listPtr();
0795                 setNeedRedraw(chunk(x, y).listPtr());
0796             }
0797         }
0798     }
0799     //  allvisible.sort();
0800 
0801 #if 0 // 2018.03.11 - double buffer is always false
0802     if ( double_buffer ) {
0803         offscr = QPixmap(area.width(), area.height());
0804         if (p) offscr.x11SetScreen(p->device()->x11Screen());
0805     }
0806     if ( double_buffer && !offscr.isNull() ) {
0807         QPainter painter;
0808         const bool isSucces = painter.begin(&offscr);
0809         if (!isSucces) {
0810             qCWarning(KTL_LOG) << " " << __LINE__ << " painter not active ";
0811         }
0812         painter.translate(-area.x(),-area.y());
0813         painter.setBrushOrigin(-area.x(),-area.y());
0814 
0815         if ( p ) {
0816             painter.setClipRect(QRect(0,0,area.width(),area.height()));
0817         } else {
0818             painter.setClipRegion(rgn);
0819         }
0820         if (!painter.isActive()) {
0821             qCWarning(KTL_LOG) << " " << __LINE__ << " painter is not active";
0822         }
0823 
0824         drawBackground(painter,area);
0825 //      allvisible.drawUnique(painter);
0826         drawChangedItems( painter );
0827         drawForeground(painter,area);
0828         painter.end();
0829 
0830         if ( p ) {
0831             p->drawPixmap( area.x(), area.y(), offscr, 0, 0, area.width(), area.height() );
0832             return;
0833         }
0834 
0835     } else
0836 #endif
0837     if (p) {
0838         drawBackground(*p, area);
0839         //      allvisible.drawUnique(*p);
0840         drawChangedItems(*p);
0841         drawForeground(*p, area);
0842         return;
0843     }
0844 
0845     QPoint trtr; // keeps track of total translation of rgn
0846 
0847     trtr -= area.topLeft();
0848 
0849     for (QList<KtlQCanvasView *>::iterator itView = m_viewList.begin(); itView != m_viewList.end(); ++itView) {
0850         KtlQCanvasView *view = *itView;
0851 
0852         if (!view->worldMatrix().isIdentity())
0853             continue; // Cannot paint those here (see callers).
0854 
0855         // as of my testing, drawing below always fails, so just post for an update event to the widget
0856         view->viewport()->update();
0857 
0858 #if 0
0859         //view->viewport()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true); // note: remove this when possible
0860         //QPainter painter(view->viewport());
0861         QPainter painter;
0862         const bool isSuccess = painter.begin(view->viewport());
0863         static int paintSuccessCount = 0;
0864         static int paintFailCount = 0;
0865         if (!isSuccess) {
0866             //qCWarning(KTL_LOG) << " on view " << view << " viewport " << view->viewport();
0867             qCWarning(KTL_LOG) << " " << __LINE__ << " painter not active, applying workaround";
0868             // TODO fix this workaround for repainting: the painter would try to draw to the widget outside of a paint event,
0869             //  which is not expected to work. Thus this code just sends an update() to the widget, ensuring correct painting
0870             ++paintFailCount;
0871             qCWarning(KTL_LOG) << " paint success: " << paintSuccessCount << ", fail: " << paintFailCount;
0872             view->viewport()->update();
0873             continue;
0874         } else {
0875             ++paintSuccessCount;
0876         }
0877         QPoint tr = view->contentsToViewport(area.topLeft());
0878         QPoint nrtr = view->contentsToViewport(QPoint(0,0)); // new translation
0879         QPoint rtr = nrtr - trtr; // extra translation of rgn
0880         trtr += rtr; // add to total
0881         
0882         if (double_buffer) {
0883             rgn.translate(rtr.x(),rtr.y());
0884             painter.setClipRegion(rgn);
0885             painter.drawPixmap(tr,offscr, QRect(QPoint(0,0),area.size()));
0886         } else {
0887             painter.translate(nrtr.x(),nrtr.y());
0888             rgn.translate(rtr.x(),rtr.y());
0889             painter.setClipRegion(rgn);
0890             drawBackground(painter,area);
0891 //          allvisible.drawUnique(painter);
0892             drawChangedItems( painter );
0893             drawForeground(painter,area);
0894             painter.translate(-nrtr.x(),-nrtr.y());
0895         }
0896 #endif
0897     }
0898 }
0899 
0900 void KtlQCanvas::setNeedRedraw(const KtlQCanvasItemList *list)
0901 {
0902     KtlQCanvasItemList::const_iterator end = list->end();
0903     for (KtlQCanvasItemList::const_iterator it = list->begin(); it != end; ++it)
0904         (*it)->setNeedRedraw(true);
0905 }
0906 
0907 void KtlQCanvas::drawChangedItems(QPainter &painter)
0908 {
0909     SortedCanvasItems::iterator end = m_canvasItems.end();
0910     for (SortedCanvasItems::iterator it = m_canvasItems.begin(); it != end; ++it) {
0911         KtlQCanvasItem *i = it->second;
0912         if (i->needRedraw()) {
0913             i->draw(painter);
0914             i->setNeedRedraw(false);
0915         }
0916     }
0917 }
0918 
0919 /*!
0920     \internal
0921     This method to informs the KtlQCanvas that a given chunk is
0922     `dirty' and needs to be redrawn in the next Update.
0923 
0924     (\a x,\a y) is a chunk location.
0925 
0926     The sprite classes call this. Any new derived class of KtlQCanvasItem
0927     must do so too. SetChangedChunkContaining can be used instead.
0928  */
0929 void KtlQCanvas::setChangedChunk(int x, int y)
0930 {
0931     if (validChunk(x, y)) {
0932         KtlQCanvasChunk &ch = chunk(x, y);
0933         ch.change();
0934     }
0935 }
0936 
0937 /*!
0938     \internal
0939     This method to informs the KtlQCanvas that the chunk containing a given
0940     pixel is `dirty' and needs to be redrawn in the next Update.
0941 
0942     (\a x,\a y) is a pixel location.
0943 
0944     The item classes call this. Any new derived class of KtlQCanvasItem must
0945     do so too. SetChangedChunk can be used instead.
0946  */
0947 void KtlQCanvas::setChangedChunkContaining(int x, int y)
0948 {
0949     if (onCanvas(x, y)) {
0950         KtlQCanvasChunk &chunk = chunkContaining(x, y);
0951         chunk.change();
0952     }
0953 }
0954 
0955 /*!
0956     \internal
0957     This method adds the KtlQCanvasItem \a g to the list of those which need to be
0958     drawn if the given chunk at location ( \a x, \a y ) is redrawn. Like
0959     SetChangedChunk and SetChangedChunkContaining, this method marks the
0960     chunk as `dirty'.
0961  */
0962 void KtlQCanvas::addItemToChunk(KtlQCanvasItem *g, int x, int y)
0963 {
0964     if (validChunk(x, y)) {
0965         chunk(x, y).add(g);
0966     }
0967 }
0968 
0969 /*!
0970     \internal
0971     This method removes the KtlQCanvasItem \a g from the list of those which need to
0972     be drawn if the given chunk at location ( \a x, \a y ) is redrawn. Like
0973     SetChangedChunk and SetChangedChunkContaining, this method marks the chunk
0974     as `dirty'.
0975  */
0976 void KtlQCanvas::removeItemFromChunk(KtlQCanvasItem *g, int x, int y)
0977 {
0978     if (validChunk(x, y)) {
0979         chunk(x, y).remove(g);
0980     }
0981 }
0982 
0983 /*!
0984     \internal
0985     This method adds the KtlQCanvasItem \a g to the list of those which need to be
0986     drawn if the chunk containing the given pixel ( \a x, \a y ) is redrawn. Like
0987     SetChangedChunk and SetChangedChunkContaining, this method marks the
0988     chunk as `dirty'.
0989  */
0990 void KtlQCanvas::addItemToChunkContaining(KtlQCanvasItem *g, int x, int y)
0991 {
0992     if (onCanvas(x, y)) {
0993         chunkContaining(x, y).add(g);
0994     }
0995 }
0996 
0997 /*!
0998     \internal
0999     This method removes the KtlQCanvasItem \a g from the list of those which need to
1000     be drawn if the chunk containing the given pixel ( \a x, \a y ) is redrawn.
1001     Like SetChangedChunk and SetChangedChunkContaining, this method
1002     marks the chunk as `dirty'.
1003  */
1004 void KtlQCanvas::removeItemFromChunkContaining(KtlQCanvasItem *g, int x, int y)
1005 {
1006     if (onCanvas(x, y)) {
1007         chunkContaining(x, y).remove(g);
1008     }
1009 }
1010 
1011 /*!
1012     Returns the color set by setBackgroundColor(). By default, this is
1013     white.
1014 
1015     This function is not a reimplementation of
1016     QWidget::backgroundColor() (KtlQCanvas is not a subclass of QWidget),
1017     but all QCanvasViews that are viewing the canvas will set their
1018     backgrounds to this color.
1019 
1020     \sa setBackgroundColor(), backgroundPixmap()
1021  */
1022 QColor KtlQCanvas::backgroundColor() const
1023 {
1024     return bgcolor;
1025 }
1026 
1027 /*!
1028     Sets the solid background to be the color \a c.
1029 
1030     \sa backgroundColor(), setBackgroundPixmap(), setTiles()
1031  */
1032 void KtlQCanvas::setBackgroundColor(const QColor &c)
1033 {
1034     if (bgcolor != c) {
1035         bgcolor = c;
1036         for (QList<KtlQCanvasView *>::iterator itView = m_viewList.begin(); itView != m_viewList.end(); ++itView) {
1037             KtlQCanvasView *view = *itView;
1038 
1039             /* XXX this doesn't look right. Shouldn't this
1040                 be more like setBackgroundPixmap? : Ian */
1041             // view->viewport()->setEraseColor( bgcolor ); // 2018.11.21
1042             QWidget *viewportWidg = view->viewport();
1043             QPalette palette;
1044             palette.setColor(viewportWidg->backgroundRole(), bgcolor);
1045             viewportWidg->setPalette(palette);
1046         }
1047         setAllChanged();
1048     }
1049 }
1050 
1051 /*!
1052     Returns the pixmap set by setBackgroundPixmap(). By default,
1053     this is a null pixmap.
1054 
1055     \sa setBackgroundPixmap(), backgroundColor()
1056  */
1057 QPixmap KtlQCanvas::backgroundPixmap() const
1058 {
1059     return pm;
1060 }
1061 
1062 /*!
1063     Sets the solid background to be the pixmap \a p repeated as
1064     necessary to cover the entire canvas.
1065 
1066     \sa backgroundPixmap(), setBackgroundColor(), setTiles()
1067  */
1068 void KtlQCanvas::setBackgroundPixmap(const QPixmap &p)
1069 {
1070     setTiles(p, 1, 1, p.width(), p.height());
1071 
1072     for (QList<KtlQCanvasView *>::iterator itView = m_viewList.begin(); itView != m_viewList.end(); ++itView) {
1073         (*itView)->updateContents();
1074     }
1075     // KtlQCanvasView* view = m_viewList.first();    // 2018.08.14 - see above
1076     // while ( view != 0 ) {
1077     //  view->updateContents();
1078     //  view = m_viewList.next();
1079     //}
1080 }
1081 
1082 /*!
1083     This virtual function is called for all updates of the canvas. It
1084     renders any background graphics using the painter \a painter, in
1085     the area \a clip. If the canvas has a background pixmap or a tiled
1086     background, that graphic is used, otherwise the canvas is cleared
1087     using the background color.
1088 
1089     If the graphics for an area change, you must explicitly call
1090     setChanged(const QRect&) for the result to be visible when
1091     update() is next called.
1092 
1093     \sa setBackgroundColor(), setBackgroundPixmap(), setTiles()
1094  */
1095 void KtlQCanvas::drawBackground(QPainter &painter, const QRect &clip)
1096 {
1097     painter.fillRect(clip, Qt::white);
1098 
1099     if (pm.isNull())
1100         painter.fillRect(clip, bgcolor);
1101 
1102     else if (!grid) {
1103         for (int x = clip.x() / pm.width(); x < (clip.x() + clip.width() + pm.width() - 1) / pm.width(); x++) {
1104             for (int y = clip.y() / pm.height(); y < (clip.y() + clip.height() + pm.height() - 1) / pm.height(); y++) {
1105                 painter.drawPixmap(x * pm.width(), y * pm.height(), pm);
1106             }
1107         }
1108     } else {
1109         const int x1 = roundDown(clip.left(), tilew);
1110         int x2 = roundDown(clip.right(), tilew);
1111         const int y1 = roundDown(clip.top(), tileh);
1112         int y2 = roundDown(clip.bottom(), tileh);
1113 
1114         const int roww = pm.width() / tilew;
1115 
1116         for (int j = y1; j <= y2; j++) {
1117             int tv = tilesVertically();
1118             int jj = ((j % tv) + tv) % tv;
1119             for (int i = x1; i <= x2; i++) {
1120                 int th = tilesHorizontally();
1121                 int ii = ((i % th) + th) % th;
1122                 int t = tile(ii, jj);
1123                 int tx = t % roww;
1124                 int ty = t / roww;
1125                 painter.drawPixmap(i * tilew, j * tileh, pm, tx * tilew, ty * tileh, tilew, tileh);
1126             }
1127         }
1128     }
1129 }
1130 
1131 void KtlQCanvas::drawForeground(QPainter &painter, const QRect &clip)
1132 {
1133     if (debug_redraw_areas) {
1134         painter.setPen(Qt::red);
1135         painter.setBrush(Qt::NoBrush);
1136         painter.drawRect(clip);
1137     }
1138 }
1139 
1140 void KtlQCanvas::setTiles(QPixmap p, int h, int v, int tilewidth, int tileheight)
1141 {
1142     if (!p.isNull() && (!tilewidth || !tileheight || p.width() % tilewidth != 0 || p.height() % tileheight != 0))
1143         return;
1144 
1145     htiles = h;
1146     vtiles = v;
1147     delete[] grid;
1148     pm = p;
1149     if (h && v && !p.isNull()) {
1150         grid = new ushort[h * v];
1151         memset(grid, 0, h * v * sizeof(ushort));
1152         tilew = tilewidth;
1153         tileh = tileheight;
1154     } else {
1155         grid = nullptr;
1156     }
1157     if (h + v > 10) {
1158         int s = scm(tilewidth, tileheight);
1159         retune(s < 128 ? s : std::max(tilewidth, tileheight));
1160     }
1161     setAllChanged();
1162 }
1163 
1164 void KtlQCanvas::setTile(int x, int y, int tilenum)
1165 {
1166     ushort &t = grid[x + y * htiles];
1167     if (t != tilenum) {
1168         t = tilenum;
1169         if (tilew == tileh && tilew == chunksize)
1170             setChangedChunk(x, y); // common case
1171         else
1172             setChanged(QRect(x * tilew, y * tileh, tilew, tileh));
1173     }
1174 }
1175 
1176 KtlQCanvasItemList KtlQCanvas::collisions(const QPoint &p) /* const */
1177 {
1178     return collisions(QRect(p, QSize(1, 1)));
1179 }
1180 
1181 KtlQCanvasItemList KtlQCanvas::collisions(const QRect &r) /* const */
1182 {
1183     KtlQCanvasRectangle *i = new KtlQCanvasRectangle(r, /*(KtlQCanvas*) */ this); // TODO verify here, why is crashing ?!
1184     i->setPen(QPen(Qt::NoPen));
1185     i->show(); // doesn't actually show, since we destroy it
1186     KtlQCanvasItemList l = i->collisions(true);
1187     delete i;
1188     l.sort();
1189     return l;
1190 }
1191 
1192 KtlQCanvasItemList KtlQCanvas::collisions(const QPolygon &chunklist, const KtlQCanvasItem *item, bool exact) const
1193 {
1194     if (isCanvasDebugEnabled()) {
1195         qCDebug(KTL_LOG) << " test item: " << item;
1196         for (SortedCanvasItems::const_iterator itIt = m_canvasItems.begin(); itIt != m_canvasItems.end(); ++itIt) {
1197             const KtlQCanvasItem *i = itIt->second;
1198             qCDebug(KTL_LOG) << "   in canvas item: " << i;
1199         }
1200         qCDebug(KTL_LOG) << "end canvas item list";
1201     }
1202 
1203     // Q3PtrDict<void> seen;
1204     QHash<KtlQCanvasItem *, bool> seen;
1205     KtlQCanvasItemList result;
1206     for (int i = 0; i < int(chunklist.count()); i++) {
1207         int x = chunklist[i].x();
1208         int y = chunklist[i].y();
1209         if (validChunk(x, y)) {
1210             const KtlQCanvasItemList *l = chunk(x, y).listPtr();
1211             for (KtlQCanvasItemList::ConstIterator it = l->begin(); it != l->end(); ++it) {
1212                 KtlQCanvasItem *g = *it;
1213                 if (g != item) {
1214                     // if ( !seen.find(g) ) {
1215                     if (seen.find(g) == seen.end()) {
1216                         // seen.replace(g,(void*)1);
1217                         seen.take(g);
1218                         seen.insert(g, true);
1219                         // if ( !exact || item->collidesWith(g) )
1220                         //  result.append(g);
1221                         if (!exact) {
1222                             result.append(g);
1223                         }
1224                         if (isCanvasDebugEnabled()) {
1225                             qCDebug(KTL_LOG) << "test collides " << item << " with " << g;
1226                         }
1227                         if (item->collidesWith(g)) {
1228                             result.append(g);
1229                         }
1230                     }
1231                 }
1232             }
1233         }
1234     }
1235     return result;
1236 }
1237 
1238 KtlQCanvasView::KtlQCanvasView(QWidget *parent, Qt::WindowFlags f)
1239     : KtlQ3ScrollView(parent, nullptr, f /* |Qt::WResizeNoErase |Qt::WStaticContents */)
1240 {
1241     setAttribute(Qt::WA_StaticContents);
1242     d = new KtlQCanvasViewData;
1243     viewing = nullptr;
1244     setCanvas(nullptr);
1245     connect(this, &KtlQCanvasView::contentsMoving, this, &KtlQCanvasView::cMoving);
1246 }
1247 
1248 KtlQCanvasView::KtlQCanvasView(KtlQCanvas *canvas, QWidget *parent, Qt::WindowFlags f)
1249     : KtlQ3ScrollView(parent, nullptr, f /* |Qt::WResizeNoErase |Qt::WA_StaticContents */)
1250 {
1251     setAttribute(Qt::WA_StaticContents);
1252     d = new KtlQCanvasViewData;
1253     viewing = nullptr;
1254     setCanvas(canvas);
1255 
1256     connect(this, &KtlQCanvasView::contentsMoving, this, &KtlQCanvasView::cMoving);
1257 }
1258 
1259 KtlQCanvasView::~KtlQCanvasView()
1260 {
1261     delete d;
1262     d = nullptr;
1263     setCanvas(nullptr);
1264 }
1265 
1266 void KtlQCanvasView::setCanvas(KtlQCanvas *canvas)
1267 {
1268     if (viewing) {
1269         disconnect(viewing);
1270         viewing->removeView(this);
1271     }
1272     viewing = canvas;
1273     if (viewing) {
1274         connect(viewing, &KtlQCanvas::resized, this, &KtlQCanvasView::updateContentsSize);
1275         viewing->addView(this);
1276     }
1277     if (d) // called by d'tor
1278         updateContentsSize();
1279 }
1280 
1281 const QTransform &KtlQCanvasView::worldMatrix() const
1282 {
1283     return d->xform;
1284 }
1285 
1286 const QTransform &KtlQCanvasView::inverseWorldMatrix() const
1287 {
1288     return d->ixform;
1289 }
1290 
1291 bool KtlQCanvasView::setWorldTransform(const QTransform &wm)
1292 {
1293     bool ok = wm.isInvertible();
1294     if (ok) {
1295         d->xform = wm;
1296         d->ixform = wm.inverted();
1297         updateContentsSize();
1298         viewport()->update();
1299     }
1300     return ok;
1301 }
1302 
1303 void KtlQCanvasView::updateContentsSize()
1304 {
1305     if (viewing) {
1306         QRect br;
1307         //          br = d->xform.map(QRect(0,0,viewing->width(),viewing->height()));
1308         br = d->xform.mapRect(viewing->rect());
1309 
1310         if (br.width() < contentsWidth()) {
1311             QRect r(contentsToViewport(QPoint(br.width(), 0)), QSize(contentsWidth() - br.width(), contentsHeight()));
1312             // viewport()->erase(r); // 2015.11.25 - not recommended to directly repaint
1313             viewport()->update(r);
1314         }
1315         if (br.height() < contentsHeight()) {
1316             QRect r(contentsToViewport(QPoint(0, br.height())), QSize(contentsWidth(), contentsHeight() - br.height()));
1317             // viewport()->erase(r);  // 2015.11.25 - not recommended to directly repaint
1318             viewport()->update(r);
1319         }
1320 
1321         resizeContents(br.width(), br.height());
1322     } else {
1323         // viewport()->erase();  // 2015.11.25 - not recommended to directly repaint
1324         viewport()->update();
1325         resizeContents(1, 1);
1326     }
1327 }
1328 
1329 void KtlQCanvasView::cMoving(int x, int y)
1330 {
1331     // A little kludge to smooth up repaints when scrolling
1332     int dx = x - contentsX();
1333     int dy = y - contentsY();
1334     d->repaint_from_moving = abs(dx) < width() / 8 && abs(dy) < height() / 8;
1335 }
1336 
1337 /*!
1338     Repaints part of the KtlQCanvas that the canvas view is showing
1339     starting at \a cx by \a cy, with a width of \a cw and a height of \a
1340     ch using the painter \a p.
1341 
1342     \warning When double buffering is enabled, drawContents() will
1343     not respect the current settings of the painter when setting up
1344     the painter for the double buffer (e.g., viewport() and
1345     window()). Also, be aware that KtlQCanvas::update() bypasses
1346     drawContents(), which means any reimplementation of
1347     drawContents() is not called.
1348 
1349     \sa setDoubleBuffering()
1350  */
1351 void KtlQCanvasView::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
1352 {
1353     QRect r(cx, cy, cw, ch);
1354     r = r.normalized();
1355 
1356     if (viewing) {
1357         // viewing->drawViewArea(this,p,r,true);
1358         viewing->drawViewArea(this, p, r, /*!d->repaint_from_moving*/ false); /* 2018.03.11 - fix build for osx */
1359         d->repaint_from_moving = false;
1360     } else {
1361         p->eraseRect(r);
1362     }
1363 }
1364 
1365 /*!
1366     \reimp
1367     \internal
1368 
1369     (Implemented to get rid of a compiler warning.)
1370  */
1371 void KtlQCanvasView::drawContents(QPainter *p)
1372 {
1373     qCDebug(KTL_LOG) << " called, although not expected";
1374     drawContents(p, 0, 0, width(), height());
1375 }
1376 
1377 /*!
1378     Suggests a size sufficient to view the entire canvas.
1379  */
1380 QSize KtlQCanvasView::sizeHint() const
1381 {
1382     if (!canvas())
1383         return KtlQ3ScrollView::sizeHint(); // TODO QT3
1384                                             // should maybe take transformations into account
1385     return (canvas()->size() + 2 * QSize(frameWidth(), frameWidth())).boundedTo(3 * QApplication::desktop()->size() / 4);
1386 }
1387 
1388 #include "moc_canvas.cpp"