File indexing completed on 2024-12-01 11:20:42
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"