ginebra2/ContentViews/TiledWebView.cpp
changeset 16 3c88a81ff781
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ginebra2/ContentViews/TiledWebView.cpp	Fri Oct 15 17:30:59 2010 -0400
@@ -0,0 +1,1108 @@
+
+#include "TiledWebView.h"
+
+#include <QPainter>
+#include <QPixmap>
+#include <QStyleOptionGraphicsItem>
+#include <QWebFrame>
+#include <QApplication>
+#include <QGraphicsView>
+#include <QGraphicsSceneResizeEvent>
+
+
+
+const int cTileSize = 64;
+const qreal cBigSideTileOverHead = 2;
+const qreal cSmallSideTileOverHead = 2.5;
+const int cTileUpdateTimerTick = 50;
+const int cPaintIdleTimeout = cTileUpdateTimerTick * 2;
+const int cTileRectRecenterTimeout = 500;
+const int cTileScaleUpdateTimeout = 250;
+const int cIdleTileUpdateChunkSize = 4;
+const int cInPaintTileUpdateTimeout = 18;
+
+
+TiledWebView::TiledWebView(QGraphicsItem* parent) : QGraphicsWebView(parent)
+{
+    m_tilesPool.clear();
+    m_tilesField = 0;
+    m_inUpdate = false;
+    m_tilesRectCentered = false;
+    m_tilesFrozen = false;
+    m_needViewportTilesUpdate = false;
+    m_needScaleCommit = false;
+    m_needTilesFieldRebuild = false;
+    m_lastScrollDelta = QPoint(0, 0);
+#ifdef USE_ASSISTANT_ITEM
+    m_assistant = new TiledWebViewAssistant();
+    m_assistant->setParentItem(this);
+    m_assistant->m_master = this;
+
+    setFlag(QGraphicsItem::ItemHasNoContents, true);
+    setAttribute(Qt::WA_OpaquePaintEvent, true);
+#endif
+
+    connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTimeout()));
+    connect(this, SIGNAL(scaleChanged()), this, SLOT(scheduleScaleUpdate()));
+    m_userPaintTS.start();
+
+}
+
+TiledWebView::~TiledWebView()
+{
+    delete[] m_tilesField;
+    m_tilesField = 0;
+    for(int i = 0; i < m_tilesPool.count(); i++) {
+        delete m_tilesPool[i];
+        m_tilesPool[i] = 0;
+    }
+}
+
+void TiledWebView::loadStarted()
+{
+//    resetTiles(QRect(QPoint(0, 0), m_tilesDim), false);
+    m_needViewportTilesUpdate = true;
+
+    startUpdateTimer();
+}
+
+void TiledWebView::setPage(QWebPage* page)
+{
+    bool doneOnce = false;
+    
+    if(!doneOnce) {
+        doneOnce = true;
+        page->setProperty("_q_RepaintThrottlingPreset", QVariant::fromValue(QString("Medium")));
+    }
+    
+    page->setProperty("_q_HTMLTokenizerChunkSize", 1024);
+    page->setProperty("_q_HTMLTokenizerTimeDelay", 0.750);
+    resetTiles(QRect(QPoint(0, 0), m_tilesDim), false);
+    m_needViewportTilesUpdate = true;
+
+    QGraphicsWebView::setPage(page);
+
+    QPalette pal = palette();
+    pal.setColor(QPalette::ButtonText,Qt::black);
+    pal.setBrush(QPalette::Base, Qt::white);
+    setPalette(pal);
+    page->setPalette(pal);
+
+    connect(page, SIGNAL(repaintRequested(QRect)), this, SLOT(repaintRequested(QRect)));
+    connect(page, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
+    m_needViewportTilesUpdate = true;
+    startUpdateTimer();
+}
+
+//#define TILEPOOL_DEBUG
+TiledWebView::Tile::Tile() : img(cTileSize,cTileSize), ready(0), used(0)
+{
+}
+
+TiledWebView::Tile* TiledWebView::tileAt(const QPoint& p) const
+{
+    Q_ASSERT(p.x() < m_tilesDim.width() && p.y() < m_tilesDim.height());
+    Q_ASSERT(m_tilesField);
+
+    return m_tilesField[m_tilesDim.width() * p.y() + p.x()];
+}
+
+void TiledWebView::setTileAt(const QPoint& p,Tile* t)
+{
+    m_tilesField[m_tilesDim.width() * p.y() + p.x()] = t;
+}
+
+static void boundPoint(const QPoint& min, QPoint& p, const QPoint &max)
+{
+    p.setX(qBound(min.x(),p.x(),max.x()));
+    p.setY(qBound(min.y(),p.y(),max.y()));
+}
+
+static QPoint topPoint(const QSize& s)
+{
+    return QPoint(s.width() - 1, s.height() - 1);
+}
+
+TiledWebView::Tile* TiledWebView::createTile(const QPoint& p)
+{
+    int i;
+    for(i = 0; i < m_tilesPool.count(); i++)
+        if(!m_tilesPool[i] || !m_tilesPool[i]->used)
+            break;
+
+    Tile* ret;
+    if(i < m_tilesPool.count()) {
+        if(!m_tilesPool[i]) {
+            m_tilesPool[i] = new Tile();
+        }
+        setTileAt(p, m_tilesPool[i]);
+        ret = m_tilesPool[i];
+    } else {
+        ret = new Tile();
+        m_tilesPool.append(ret);
+        setTileAt(p, ret);
+    }
+
+    ret->used = true;
+
+#ifdef TILEPOOL_DEBUG
+    checkTilesField();
+#endif
+    return ret;
+}
+
+QPoint TiledWebView::tileAtPoint(const QPointF& p) const
+{
+    QPointF tmp = mapToTileCoords(p - m_tilesRect.topLeft());
+    tmp /= cTileSize;
+    QPoint ret((int)tmp.x(),(int)tmp.y());
+    return ret;
+}
+
+QRectF TiledWebView::tileRect(const QPoint& t) const
+{
+    QRectF tRect = mapToTileCoords(m_tilesRect);
+    QRectF ret(tRect.topLeft() + t * cTileSize,QSizeF(cTileSize,cTileSize));
+
+    return ret;
+}
+
+static int calcD(const QPoint p1, QPoint p2)
+{
+    int d1 = p2.x() - p1.x();
+    int d2 = p2.y() - p1.y();
+    return d1 * d1 + d2 * d2;
+}
+QRectF TiledWebView::viewPortRect() const
+{
+    return mapRectFromParent(QRectF(QPointF(0, 0),static_cast<QGraphicsWidget*>(parentItem())->size()));
+}
+
+void TiledWebView::boundTile(QPoint& t) const
+{
+    boundPoint(QPoint(0,0),t,topPoint(m_tilesDim));
+}
+
+QPoint TiledWebView::findTile4Update(bool inView, bool addDirty) const
+{
+    QRectF vpRect = viewPortRect();
+    QRectF updateRect = m_tilesRect;
+    if(inView)
+        updateRect = updateRect.intersect(vpRect);
+    QPoint found(-1, -1);
+    if(updateRect.isEmpty()) return found;
+
+    QPoint topLeft = tileAtPoint(updateRect.topLeft());
+    QPoint bottomRight = tileAtPoint(updateRect.bottomRight());
+    QPoint center = tileAtPoint(vpRect.center());
+
+    bottomRight += QPoint(1, 1);
+    boundTile(topLeft);
+    boundTile(bottomRight);
+    boundPoint(topLeft,center,bottomRight);
+
+    int d = 1000000; // just big enough value;
+    int tmpD;
+    for(int j = topLeft.y(); j <= bottomRight.y(); j++)
+        for(int i = topLeft.x(); i <= bottomRight.x(); i++) {
+            QPoint p(i,j);
+            Tile* t = tileAt(p);
+            if(!t || !t->ready || (addDirty && !t->dirtyRect.isEmpty())) {
+                tmpD = calcD(center, p);
+                if(tmpD < d) {
+                    d = tmpD;
+                    found = p;
+                }
+            }
+        }
+
+    return found;
+}
+
+QList<QPoint> TiledWebView::findTileLine4Update(bool dirty, bool inView, bool useScrollDirection) const
+{
+    QRectF vpRect = viewPortRect();
+    QRectF updateRect = m_tilesRect;
+    if(inView)
+        updateRect = updateRect.intersect(vpRect);
+    QList<QPoint> ret;
+    if(updateRect.isEmpty()) return ret;
+
+    QPoint topLeft = tileAtPoint(updateRect.topLeft());
+    QPoint bottomRight = tileAtPoint(updateRect.bottomRight());
+    QPoint topVPLeft = tileAtPoint(vpRect.topLeft());
+    QPoint bottomVPRight = tileAtPoint(vpRect.bottomRight());
+    QPoint center = tileAtPoint(vpRect.center());
+
+    bottomRight += QPoint(1, 1);
+    bottomVPRight += QPoint(1, 1);
+    boundTile(topLeft);
+    boundTile(bottomRight);
+    boundTile(topVPLeft);
+    boundTile(bottomVPRight);
+    boundPoint(topLeft,center,bottomRight);
+
+    int maxCount = qAbs(topLeft.x() - center.x());
+    maxCount = qMax(maxCount, qAbs(topLeft.y() - center.y()));
+    maxCount = qMax(maxCount, qAbs(bottomRight.x() - center.x()));
+    maxCount = qMax(maxCount, qAbs(bottomRight.y() - center.y()));
+
+    for(int i = 0; i < maxCount; i++) {
+        int j;
+        if((m_lastScrollDelta.y() > 0 || !useScrollDirection) &&
+           center.y() + i <= bottomRight.y())
+            for(j = topVPLeft.x(); j <= bottomVPRight.x(); j++) {
+                QPoint p(j, center.y() + i);
+                Tile* t = tileAt(p);
+                if(!t || !t->ready || (dirty && !t->dirtyRect.isEmpty())) {
+                    ret += p;
+                }
+            }
+        if(!ret.isEmpty())
+            break;
+
+        if((m_lastScrollDelta.y() < 0 || !useScrollDirection) &&
+           center.y() - i >= topLeft.y())
+            for(j = topVPLeft.x(); j <= bottomVPRight.x(); j++) {
+                QPoint p(j, center.y() - i);
+                Tile* t = tileAt(p);
+                if(!t || !t->ready || (dirty && !t->dirtyRect.isEmpty())) {
+                    ret += p;
+                }
+            }
+            if(!ret.isEmpty())
+                break;
+
+            if((m_lastScrollDelta.x() > 0 || !useScrollDirection) &&
+               center.x() + i <= bottomRight.x())
+                for(j = topVPLeft.y(); j <= bottomVPRight.y(); j++) {
+                    QPoint p(center.x() + i, j);
+                    Tile* t = tileAt(p);
+                    if(!t || !t->ready || (dirty && !t->dirtyRect.isEmpty())) {
+                        ret += p;
+                    }
+                }
+            if(!ret.isEmpty())
+                break;
+
+            if((m_lastScrollDelta.x() < 0  || !useScrollDirection) &&
+               center.x() - i >= topLeft.x())
+                for(j = topVPLeft.y(); j <= bottomVPRight.y(); j++) {
+                    QPoint p(center.x() - i, j);
+                Tile* t = tileAt(p);
+                if(!t || !t->ready || (dirty && !t->dirtyRect.isEmpty())) {
+                    ret += p;
+                }
+            }
+            if(!ret.isEmpty())
+                break;
+    }
+
+    return ret;
+}
+
+QRectF TiledWebView::updateTile(const QPoint& t)
+{
+    m_inUpdate = true;
+    Tile* tile = tileAt(t);
+    if(!tile) tile = createTile(t);
+
+    QPainter p(&(tile->img));
+    QRectF tRect = mapFromTileCoords(tileRect(t));
+    QRectF tDirtyRect = mapFromTileCoords(tile->dirtyRect);
+    QRectF updateRect = !tile->ready || tDirtyRect.isEmpty() ? tRect : tDirtyRect;
+
+
+    //if(!tile->ready)
+    //    p.fillRect(0, 0, cTileSize, cTileSize, Qt::white);
+
+    tile->ready = true;
+    tile->dirtyRect = QRectF();
+
+    p.scale(m_tilesScale,m_tilesScale);
+
+    qreal adjust = 2 + m_tilesScale;
+
+    // adjust rect to cover rouding errors
+    updateRect.adjust(-adjust, -adjust, adjust, adjust);
+
+    QRegion clip(updateRect.toRect());
+    p.translate(-tRect.topLeft());
+    p.setClipRegion(clip);
+//    p.fillRect(clip.boundingRect(), Qt::white);
+
+//    p.setRenderHint(QPainter::SmoothPixmapTransform); // cause assert now
+//    p.setRenderHint(QPainter::Antialiasing);
+
+    page()->mainFrame()->render(&p, QWebFrame::ContentsLayer, clip);
+
+    m_inUpdate = false;
+    return updateRect;
+}
+
+void TiledWebView::repaintRequested(QRect r)
+{
+    if(!m_tilesField) return;
+
+    if(r.isEmpty())
+        r = m_tilesRect.adjusted(-1, -1, 1, 1).toRect();
+
+    QPoint topLeftTile = tileAtPoint(r.topLeft());
+    QPoint bottomRightTile = tileAtPoint(r.bottomRight());
+    boundTile(topLeftTile);
+    boundTile(bottomRightTile);
+    QRectF repaintClip = mapToTileCoords(r);
+    bool needUpdate = false;
+    QRectF vpRect = mapToTileCoords(viewPortRect());
+
+    for(int j = topLeftTile.y(); j <= bottomRightTile.y(); j++)
+        for(int i = topLeftTile.x(); i <= bottomRightTile.x(); i++) {
+            QPoint t(i, j);
+            QRectF clip = tileRect(t) & repaintClip;
+            if(!clip.isEmpty()) {
+
+                Tile* tile = tileAt(t);
+                if(tile && tile->ready) {
+                    if(!tile->dirtyRect.isEmpty())
+                        clip |= tile->dirtyRect;
+                    tile->dirtyRect = clip;
+//                    if(vpRect.intersects(tile->dirtyRect)) {
+//                        m_needViewportTilesUpdate = true;
+//                    }
+                }
+                needUpdate = true;
+            }
+        }
+
+    if(needUpdate)
+        startUpdateTimer();
+
+}
+
+QPixmap* TiledWebView::getUnprepPixmap()
+{
+    static const int squareMult = 1;
+    static const int cellSize = 16;
+    static QPixmap pixmap(squareMult * cellSize, squareMult * cellSize);
+    static bool init = false;
+
+    if(!init) {
+        init = true;
+        QPainter p(&pixmap);
+        p.fillRect(pixmap.rect(),Qt::lightGray);
+        // p.setPen(QColor(182,242,255));
+        // p.setPen(QColor(Qt::darkBlue));
+        p.setPen(Qt::darkGray);
+        for(int i = -1; i < squareMult + 1; i++) {
+            int xoffs = i * cellSize;
+            for(int j = -1; j < squareMult + 1; j++) {
+                int yoffs = j * cellSize;
+                p.drawLine(xoffs + 7, yoffs - 4, xoffs + 7, yoffs + 2);
+                p.drawLine(xoffs + 4, yoffs + 7, xoffs + 10, yoffs + 7);
+                p.drawLine(xoffs + 15, yoffs + 4, xoffs + 15, yoffs + 10);
+                p.drawLine(xoffs + 12, yoffs + 15, xoffs + 18, yoffs + 15);
+            }
+        }
+    }
+
+    return &pixmap;
+}
+
+QSize TiledWebView::getTileFieldDim()
+{
+    QSizeF vpSize = static_cast<QGraphicsWidget*>(parentItem())->size();
+    qreal heightMult = cBigSideTileOverHead;
+    qreal widthMult = cSmallSideTileOverHead;
+    if(vpSize.width() > vpSize.height())
+        qSwap(widthMult, heightMult);
+    return QSize((int)((vpSize.width() * widthMult + cTileSize) / cTileSize),
+                 (int)((vpSize.height() * heightMult + cTileSize) / cTileSize));
+}
+
+void TiledWebView::createTileField()
+{
+    QRectF vpRect = viewPortRect();
+
+    m_tilesDim = getTileFieldDim();
+    m_tilesPool.reserve(m_tilesDim.width() * m_tilesDim.height());
+
+
+    m_tilesField = new Tile*[m_tilesDim.width() * m_tilesDim.height()];
+    memset(m_tilesField, 0, sizeof(Tile*) * m_tilesDim.width() * m_tilesDim.height());
+    m_tilesScale = scale();
+
+    adjustTilesToViewPort(true);// mapFromTileCoords(QRectF(QPointF(3, 5) * cTileSize, m_tilesDim * cTileSize));
+}
+
+QRectF TiledWebView::validateTileRect(const QRectF& rect, const QSize& dim) const
+{
+    QRectF ret(rect);
+    QRectF vpRect = viewPortRect();
+    qreal tileSize = cTileSize / m_tilesScale;
+
+    if(ret.bottom() > size().height() + tileSize)
+        ret.moveBottom(size().height() + tileSize);
+    if(ret.top() < 0)
+        ret.moveTop(0);
+
+    if(ret.right() > size().width() + tileSize)
+        ret.moveRight(size().width() + tileSize);
+    if(ret.left() < 0)
+        ret.moveLeft(0);
+
+    if(ret.width() < vpRect.width())
+        ret.setLeft(0);
+
+    if(ret.height() < vpRect.height())
+        ret.setTop(0);
+
+    QPointF p = mapToTileCoords(ret.topLeft());
+    // allign coordinates to tile boundary
+    QPoint pp = (p / cTileSize).toPoint();
+    p = QPointF(pp) * cTileSize;
+
+    return mapFromTileCoords(QRectF(p, dim * cTileSize));
+}
+
+QRectF TiledWebView::adjustedTileRect(const QSize& dim) const
+{
+    QRectF ret(m_tilesRect);
+    if(ret.isEmpty())
+        ret = QRectF(QPoint(0,0),mapFromTileCoords(dim * cTileSize));
+    // no repositioning and scaling and tile dropping during scaling
+    if(!qFuzzyCompare(m_tilesScale,scale()))
+        return ret;
+
+    QRectF vpRect = viewPortRect();
+    qreal tileSize = cTileSize / m_tilesScale;
+    if(vpRect.bottom() > ret.bottom())
+        ret.moveTop(vpRect.top() - tileSize);
+    else if(vpRect.top() < ret.top())
+        ret.moveBottom(vpRect.bottom() + tileSize);
+
+    if(vpRect.right() > ret.right())
+        ret.moveLeft(vpRect.left() - tileSize);
+    else if(vpRect.left() < ret.left())
+        ret.moveRight(vpRect.right() + tileSize);
+
+    return validateTileRect(ret, dim);
+}
+
+QRectF TiledWebView::centeredTileRect(const QSize& dim) const
+{
+    QRectF vpRect = viewPortRect();
+    QSizeF tilesSize = mapFromTileCoords(dim * cTileSize);
+    QPoint centerOffset(tilesSize.width() / 2, tilesSize.height() / 2);
+    QRectF centeredRect(vpRect.center() - centerOffset,tilesSize);
+
+    return validateTileRect(centeredRect, dim);
+}
+
+void TiledWebView::adjustTilesToViewPort(bool center)
+{
+    QRectF newTilesRect = center ? centeredTileRect(m_tilesDim) :
+                                   adjustedTileRect(m_tilesDim);
+    m_tilesRectCentered = center;
+
+    moveTilesRect(newTilesRect);
+}
+
+void TiledWebView::moveTilesRect(const QRectF& newTilesRect)
+{
+    QRectF trNew = mapToTileCoords(newTilesRect);
+    QRectF trOld = mapToTileCoords(m_tilesRect);
+
+    if(trNew == trOld) return;
+
+    if(trNew.intersects(trOld)) {
+        QPoint trDiff = ((trNew.topLeft() - trOld.topLeft()) / cTileSize).toPoint();
+        scrollTileField(-trDiff);
+    } else {
+        resetTiles(QRect(QPoint(0,0), m_tilesDim), false);
+#ifdef TILEPOOL_DEBUG
+        checkTilesField();
+#endif
+    }
+    m_tilesRect = newTilesRect;
+}
+
+void TiledWebView::resetTiles(const QRect& r, bool remove)
+{
+    for(int j = r.top(); j <= r.bottom(); j++)
+        for(int i = r.left(); i <= r.right(); i++) {
+            QPoint t(i, j);
+            Tile* tile = tileAt(t);
+            if(tile) {
+                tile->ready = false;
+                tile->dirtyRect = QRect();
+                if(remove) {
+                    tile->used = false;
+                    setTileAt(t, 0);
+                }
+            }
+        }
+}
+
+void TiledWebView::scrollTileField(const QPoint& diff)
+{
+#ifdef TILEPOOL_DEBUG
+    checkTilesField();
+#endif
+    if(qAbs(diff.x()) > m_tilesDim.width() || qAbs(diff.y()) > m_tilesDim.height())
+        return;
+
+    if(diff.x() > 0) {
+        resetTiles(QRect(QPoint(m_tilesDim.width() - diff.x(),0),
+                         QSize(diff.x(), m_tilesDim.height())), true);
+
+        for(int i = m_tilesDim.width() - diff.x() - 1; i >= 0; i--) {
+            int dstNum = i + diff.x();
+
+            for(int j = 0; j < m_tilesDim.height(); j++) {
+                setTileAt(dstNum, j, tileAt(i, j));
+                setTileAt(i, j, 0);
+            }
+        }
+    } else if(diff.x() < 0) {
+        resetTiles(QRect(QPoint(0,0),
+                         QSize(-diff.x(), m_tilesDim.height())), true);
+
+        for(int i = -diff.x(); i < m_tilesDim.width(); i++) {
+            int dstNum = i + diff.x();
+
+            for(int j = 0; j < m_tilesDim.height(); j++) {
+                setTileAt(dstNum, j, tileAt(i, j));
+                setTileAt(i, j, 0);
+            }
+        }
+    }
+
+    if(diff.y() > 0) {
+        resetTiles(QRect(QPoint(0, m_tilesDim.height() - diff.y()),
+                         QSize(m_tilesDim.width(), diff.y())), true);
+
+        for(int i = m_tilesDim.height() - diff.y() - 1; i >= 0; i--) {
+            int dstNum = i + diff.y();
+            Tile **srcLine = m_tilesField + m_tilesDim.width() * i;
+            Tile **dstLine = m_tilesField + m_tilesDim.width() * dstNum;
+            memcpy(dstLine, srcLine, m_tilesDim.width() * sizeof(Tile*));
+            memset(srcLine, 0, m_tilesDim.width() * sizeof(Tile*));
+        }
+    } else if(diff.y() < 0) {
+        resetTiles(QRect(QPoint(0, 0),
+                         QSize(m_tilesDim.width(), -diff.y())), true);
+
+        for(int i = -diff.y(); i < m_tilesDim.height(); i++) {
+            int dstNum = i + diff.y();
+            Tile **srcLine = m_tilesField + m_tilesDim.width() * i;
+            Tile **dstLine = m_tilesField + m_tilesDim.width() * dstNum;
+            memcpy(dstLine, srcLine, m_tilesDim.width() * sizeof(Tile*));
+            memset(srcLine, 0, m_tilesDim.width() * sizeof(Tile*));
+        }
+    }
+#ifdef TILEPOOL_DEBUG
+    checkTilesField();
+#endif
+}
+
+QList<QRectF> TiledWebView::updateViewportTiles(QList<TileSet> *updatedTiles)
+{
+    QList<QRectF> ret;
+    // update all visible tiles
+    QRectF vpRect = viewPortRect();
+
+    QPoint topLeft = tileAtPoint(vpRect.topLeft());
+    QPoint bottomRight = tileAtPoint(vpRect.bottomRight());
+
+    bottomRight += QPoint(1, 1);
+    boundTile(topLeft);
+    boundTile(bottomRight);
+    for(int j = topLeft.y(); j <= bottomRight.y(); j++)
+        for(int i = topLeft.x(); i <= bottomRight.x(); i++) {
+            QPoint t(i, j);
+            Tile *tile = tileAt(t);
+            if(!tile || !tile->ready || !tile->dirtyRect.isEmpty()) {
+                QRectF r = updateTile(t);
+                ret += r;
+                if(updatedTiles)
+                    *updatedTiles += TileSet(t, r);
+            }
+        }
+
+    m_needViewportTilesUpdate = false;
+    return ret;
+}
+
+void TiledWebView::doScaleCommit()
+{
+    m_needScaleCommit = false;
+    if(qFuzzyCompare(m_tilesScale, scale()))
+        return;
+
+    resetTiles(QRect(QPoint(0,0), m_tilesDim), true);
+#ifdef TILEPOOL_DEBUG
+   checkTilesField();
+#endif
+    m_tilesScale = scale();
+    adjustTilesToViewPort(true);
+    m_needViewportTilesUpdate = true;
+}
+
+void TiledWebView::commitZoom()
+{
+    m_needScaleCommit = true;
+
+    startUpdateTimer();
+}
+
+QList<QRectF> TiledWebView::updateScrollAreaTilesChunk(QList<TileSet> *updatedTiles, bool inPaint)
+{
+    QList<QRectF> dirtyRects;
+    QList<QPoint> lst = findTileLine4Update(false, true);
+    if(lst.isEmpty())
+        lst = findTileLine4Update(false, false);
+    if(lst.isEmpty())
+        lst = findTileLine4Update(false, true, false);
+//    if(!inPaint) {
+    {
+        if(lst.isEmpty())
+            lst = findTileLine4Update(true, true);
+        if(lst.isEmpty())
+            lst = findTileLine4Update(true, false);
+        if(lst.isEmpty())
+            lst = findTileLine4Update(true, false, false);
+    }
+
+    QTime ts;
+    ts.start();
+    foreach(QPoint t, lst) {
+        QRectF r = updateTile(t);
+        dirtyRects += r;
+        if(updatedTiles)
+            *updatedTiles += TileSet(t, r);
+        if(inPaint && ts.elapsed() > cInPaintTileUpdateTimeout)
+            break;
+    }
+
+    return dirtyRects;
+}
+
+
+void TiledWebView::updateTimeout()
+{
+    if(m_tilesFrozen) return;
+    if(m_inUpdate) return;
+
+    int elapsed = m_userPaintTS.elapsed();
+    QList<QRectF> dirtyTiles;
+
+    if(m_needTilesFieldRebuild) {
+        doTilesFieldRebuild();
+    } else if(m_needScaleCommit) {
+        doScaleCommit();
+    } else if(m_needViewportTilesUpdate) {
+/*  just do nothing, because it will update tiles below
+        dirtyTiles += updateViewportTiles(); */
+    } else if(elapsed < cPaintIdleTimeout) {
+//        updateSceneRects(updateScrollAreaTilesChunk);
+
+/*        QList<QRectF> rects = updateViewportTiles();
+        foreach(QRectF r, rects)
+            update(r); */
+        return;
+    }
+
+    if(elapsed > cTileScaleUpdateTimeout && !qFuzzyCompare(m_tilesScale, scale())) {
+         doScaleCommit();
+    }
+//    else if(elapsed > cTileRectRecenterTimeout && !m_tilesRectCentered)
+//        adjustTilesToViewPort(true);
+
+    dirtyTiles += updateViewportTiles();
+    if(dirtyTiles.isEmpty())
+        dirtyTiles = updateScrollAreaTilesChunk();
+
+    if(dirtyTiles.isEmpty())
+        for(int i = 0; i < cIdleTileUpdateChunkSize; i++) {
+            // 1st try to paint not ready tiles in view
+            QPoint oneDirtyTile = findTile4Update(true);
+            // 2nd update dirty tiles in view
+            if(oneDirtyTile.x() < 0) oneDirtyTile = findTile4Update(true, true);
+            // 3rd try to paint not ready tiles everywhere else
+            if(oneDirtyTile.x() < 0) oneDirtyTile = findTile4Update(false);
+            // 4th update all other dirty tiles
+            if(oneDirtyTile.x() < 0) oneDirtyTile = findTile4Update(false, true);
+            if(oneDirtyTile.x() >= 0)
+                dirtyTiles += updateTile(oneDirtyTile);
+            else if(/*m_tilesRectCentered && */qFuzzyCompare(m_tilesScale, scale())) {
+                stopUpdateTimer();
+                break;
+            }
+        }
+
+    m_inUpdate = true;
+
+    updateSceneRects(dirtyTiles);
+
+    m_inUpdate = false;
+    
+    // restart timer if some of update flags was set during recursive calls from webkit render
+    if(m_needTilesFieldRebuild || m_needScaleCommit || m_needViewportTilesUpdate)
+        startUpdateTimer();
+}
+
+void TiledWebView::updateSceneRects(const QList<QRectF>& dirtyTiles)
+{
+    QGraphicsScene* s = scene();
+    QList<QGraphicsView*> gvList = s->views();
+
+    QRectF vpRect = viewPortRect();
+
+    foreach(QGraphicsView* v, gvList) {
+        QRegion reg;
+        foreach(QRectF r, dirtyTiles) {
+            r = r.intersected(vpRect);
+            update(r);
+            r = mapRectToScene(r);
+            r = v->mapFromScene(r).boundingRect();
+            reg += r.toRect();
+        }
+//        v->repaint(reg);
+    }
+}
+
+void TiledWebView::scheduleScaleUpdate()
+{
+    if(!m_tilesField) return;
+
+    startUpdateTimer();
+}
+
+void TiledWebView::checkTilesField()
+{
+    int usedCount1 = 0;
+    for(int j = 0; j < m_tilesDim.height(); j++)
+        for(int i = 0; i < m_tilesDim.width(); i++) {
+            Tile* t = tileAt(i, j);
+            if(t) {
+                Q_ASSERT(t->used);
+                usedCount1++;
+            }
+        }
+
+    int usedCount2 = 0;
+    for(int i = 0; i < m_tilesPool.count(); i++)
+        if(m_tilesPool[i] && m_tilesPool[i]->used)
+            usedCount2++;
+
+    Q_ASSERT(usedCount1 == usedCount2);
+}
+
+void TiledWebView::doTilesFieldRebuild()
+{
+    QSize oldDim = m_tilesDim;
+    QSize newDim = getTileFieldDim();
+    
+    if(!qFuzzyCompare(m_tilesScale, scale())) {
+        resetTiles(QRect(QPoint(0,0), m_tilesDim), true);
+        m_tilesScale = scale();
+        
+        QRectF newRect = adjustedTileRect(newDim);
+            
+        Tile** newField = new Tile*[newDim.width() * newDim.height()];
+        memset(newField, 0, sizeof(Tile*) * newDim.width() * newDim.height());
+        delete[] m_tilesField;
+        m_tilesField = newField;
+        m_tilesDim = newDim;
+        m_tilesRect = newRect;
+
+        m_needViewportTilesUpdate = true;
+
+    } else { 
+        Tile** oldField = m_tilesField;
+        QRectF newRect = adjustedTileRect(newDim);
+        
+        QRectF trNew = mapToTileCoords(newRect);
+        QRectF trOld = mapToTileCoords(m_tilesRect);
+    
+        if(trNew != trOld) {
+    
+            Tile** newField = new Tile*[newDim.width() * newDim.height()];
+            memset(newField, 0, sizeof(Tile*) * newDim.width() * newDim.height());
+            QRectF trCommon = trNew.intersect(trOld);
+    
+            if(!trCommon.isEmpty()) {
+                QSize copySize = (trCommon.size() / cTileSize).toSize();
+                QPoint oldOffs = ((trCommon.topLeft() - trOld.topLeft()) / cTileSize).toPoint();
+                QPoint newOffs = ((trCommon.topLeft() - trNew.topLeft()) / cTileSize).toPoint();
+                if(trNew.size().width() - newOffs.x() < copySize.width())
+                    copySize.setWidth(trNew.size().width() - newOffs.x());
+                if(trNew.size().height() - newOffs.y() < copySize.height())
+                    copySize.setHeight(trNew.size().height() - newOffs.y());
+                if(trOld.size().width() - oldOffs.x() < copySize.width())
+                    copySize.setWidth(trOld.size().width() - oldOffs.x());
+                if(trOld.size().height() - oldOffs.y() < copySize.height())
+                    copySize.setHeight(trOld.size().height() - oldOffs.y());
+    
+                for(int j = 0; j < copySize.height(); j++)
+                    for(int i = 0; i < copySize.width(); i++) {
+                    QPoint cPoint(i, j);
+                    QPoint oldPos = cPoint + oldOffs;
+                    Tile *tile = tileAt(oldPos);
+                    setTileAt(oldPos, 0);
+                    QPoint newPos = cPoint + newOffs;
+                    Q_ASSERT(newPos.x() >= 0 && newPos.y() >=0 &&
+                             newPos.x() < newDim.width() && newPos.y() < newDim.height());
+                    *(newField + newPos.y() * newDim.width() + newPos.x()) = tile;
+                }
+            }
+    
+            // release remaining tiles in old field
+            resetTiles(QRect(QPoint(0,0), m_tilesDim), true);
+    
+            delete[] m_tilesField;
+            m_tilesField = newField;
+            m_tilesDim = newDim;
+            m_tilesRect = newRect;
+    
+            int newTileCount = m_tilesDim.height() * m_tilesDim.width();
+            while(m_tilesPool.count() > newTileCount) {
+                bool deleted = false;
+                for(int i = m_tilesPool.count() - 1; i >= 0; i--) {
+                    Tile* tile = m_tilesPool[i];
+                    if(!tile->used) {
+                        deleted = true;
+                        if(tile) delete tile;
+                        m_tilesPool.remove(i);
+                        break;
+                    }
+                }
+                Q_ASSERT(deleted);
+            }
+
+            m_needViewportTilesUpdate = true;
+        }
+    }
+
+    m_needTilesFieldRebuild = false;
+}
+
+void TiledWebView::viewportUpdated()
+{
+    if(!m_tilesField) {
+        createTileField();
+        commitZoom();
+    }
+    else {
+        m_needTilesFieldRebuild = true;
+        startUpdateTimer();
+    }
+}
+
+// #define DRAW_TILE_BOUNDS
+void TiledWebView::paintTile(QPainter* painter, const QPoint& t, const QRectF& clipRect, QRegion& dirtyRegion)
+{
+    QRectF tRectOrig = tileRect(t);
+    qreal adjust = 1; // + m_tilesScale;
+    QRectF tRect = tRectOrig.adjusted(-adjust, -adjust, adjust, adjust);
+    QRectF drawRect = clipRect.intersected(tRect);
+    //painter->drawPixmap(tRectOrig,tileAt(t)->img, tRectOrig.translated(-tRectOrig.topLeft()));
+    painter->drawPixmap(tRectOrig.topLeft(),tileAt(t)->img);
+#ifdef DRAW_TILE_BOUNDS
+    painter->setPen(Qt::red);
+    painter->drawRect(tileRect(t));
+#endif // DRAW_TILE_BOUNDS
+    dirtyRegion = dirtyRegion.subtract(QRegion(mapFromTileCoords(drawRect).toRect()));
+}
+
+void TiledWebView::paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget)
+{
+
+    if(!m_tilesField) {
+        QGraphicsWebView::paint(painter, options, widget );
+        return;
+    }
+
+    painter->save();
+    QRectF clipRect = viewPortRect().adjusted(-1, -1, 1, 1);
+    if(options && !options->exposedRect.isEmpty())
+        clipRect &= options->exposedRect;
+
+    QList<QRectF> updatedTileRects;
+    QList<TileSet> updatedTiles;
+    if(!m_inUpdate && !m_tilesFrozen) {
+        QList<QRectF> lst;
+        if(m_userPaintTS.elapsed() > cPaintIdleTimeout || m_needViewportTilesUpdate) {
+            lst = updateViewportTiles(&updatedTiles);
+            m_needViewportTilesUpdate = false;
+        } else
+            lst = updateScrollAreaTilesChunk(&updatedTiles, true);
+        QRectF vpRect =  viewPortRect().adjusted(-1, -1, 1, 1);
+        foreach(QRectF r, lst) {
+            r = r.intersected(vpRect);
+            if(!r.isEmpty()) {
+                if(clipRect.contains(r)) {
+                    // do nothing, it will be updated in any case
+                } else if(r.contains(clipRect)) {
+                    clipRect = r;
+                } else if(clipRect.intersects(r)) {
+                    clipRect = clipRect.unite(r);
+                } else {
+                    updatedTileRects += r;
+                }
+            }
+        }
+    }
+
+    painter->setBackgroundMode(Qt::OpaqueMode);
+
+    painter->setClipRect(clipRect, Qt::IntersectClip);
+
+    QRectF tileClipRect = mapToTileCoords(clipRect);
+    QRegion dirtyRgn(clipRect.toRect());
+    QPoint topLeftTile = tileAtPoint(clipRect.topLeft());
+    QPoint rightBottomTile = tileAtPoint(clipRect.bottomRight());
+
+    boundPoint(QPoint(0,0),topLeftTile,topPoint(m_tilesDim));
+    boundPoint(QPoint(0,0),rightBottomTile,topPoint(m_tilesDim));
+
+    qreal sc = scale();
+    QPointF p = pos() / sc;
+
+    QRectF scr(clipRect.topLeft() + p, clipRect.size() * sc);
+
+    QRegion notReadyClip(scr.toRect()); //.adjusted(-2, -2, 2, 2).toRect());
+    for(int j = topLeftTile.y(); j <= rightBottomTile.y(); j++)
+        for(int i = topLeftTile.x(); i <= rightBottomTile.x(); i++) {
+            QPoint t(i,j);
+            if(tileAt(t) && tileAt(t)->ready) {
+                QRectF r = mapFromTileCoords(tileRect(t));
+                r = QRectF(r.topLeft() * sc, r.size() * sc);
+                r.translate(pos());
+                notReadyClip = notReadyClip.subtract(r.toRect());
+//               painter->setPen(Qt::red);
+//                painter->drawRect(r);
+            }
+        }
+
+//    painter->setPen(Qt::red);
+//    painter->drawRect(scr.adjusted(10,10,-10,-10));
+
+    QVector<QRect> rList = notReadyClip.rects();
+    if(!rList.isEmpty()) {
+        painter->save();
+        painter->translate(-p);
+        painter->scale(1/sc, 1/sc);
+        foreach(QRect r, rList) {
+            painter->fillRect(r, QBrush(*getUnprepPixmap()));
+            //        painter->setPen(Qt::red);
+            //        painter->drawRect(r.adjusted(10, 10, -10, -10));
+        }
+        painter->restore();
+    }
+
+    painter->scale(1 / m_tilesScale, 1 / m_tilesScale);
+
+    for(int j = topLeftTile.y(); j <= rightBottomTile.y(); j++)
+        for(int i = topLeftTile.x(); i <= rightBottomTile.x(); i++) {
+            QPoint t(i,j);
+            if(tileAt(t) && tileAt(t)->ready)
+                paintTile(painter, t, tileClipRect, dirtyRgn);
+        }
+    QRect clippedRectTiles(topLeftTile,rightBottomTile);
+    foreach(TileSet ts, updatedTiles)
+        if(!clippedRectTiles.contains(ts.t))
+            paintTile(painter, ts.t, ts.r, dirtyRgn);
+
+//    if(!m_inUpdate)
+//        m_userPaintTS.start();
+
+    if(!m_tilesFrozen && !m_tilesRect.contains(viewPortRect()) && qFuzzyCompare(scale(),m_tilesScale)) {
+        adjustTilesToViewPort();
+        startUpdateTimer();
+    }
+/*    painter->setPen(Qt::red);
+    painter->drawLine(0, 0, 100, 100);
+*/
+    painter->restore();
+}
+
+void TiledWebView::setTiledBackingStoreFrozen(bool frozen)
+{
+    m_tilesFrozen = frozen;
+    if(frozen) {
+        if(m_updateTimer.isActive())
+            stopUpdateTimer();
+    } else {
+        if(!qFuzzyCompare(scale(),m_tilesScale))
+            commitZoom();
+        else {
+            // m_needViewportTilesUpdate = true;
+            m_tilesRectCentered = false;
+        }
+
+        startUpdateTimer();
+    }
+}
+
+void TiledWebView::startUpdateTimer()
+{
+    if(!m_updateTimer.isActive() && !m_tilesFrozen) {
+        m_updateTimer.start(cTileUpdateTimerTick);
+    }
+}
+
+void TiledWebView::stopUpdateTimer()
+{
+    m_updateTimer.stop();
+}
+
+void TiledWebView::userActivity()
+{
+    m_userPaintTS.start();
+}
+
+void TiledWebView::viewScrolled(QPoint& scrollPos, QPoint& delta)
+{
+    m_lastScrollDelta = delta;
+
+    userActivity();
+    if(!m_tilesField) return;
+
+    QRectF ret(m_tilesRect);
+    if(ret.isEmpty())
+        ret = QRectF(QPoint(0,0),mapFromTileCoords(m_tilesDim * cTileSize));
+    // no repositioning and scaling and tile dropping during scaling
+    if(!qFuzzyCompare(m_tilesScale,scale()))
+        return;
+
+    QRectF vpRect = viewPortRect();
+    qreal tileSize = cTileSize / m_tilesScale;
+    vpRect.adjust(-tileSize, -tileSize, tileSize, tileSize);
+    if(vpRect.bottom() > ret.bottom() && delta.y() > 0)
+        ret.moveTop(vpRect.top() - tileSize);
+    else if(vpRect.top() < ret.top() && delta.y() < 0)
+        ret.moveBottom(vpRect.bottom() + tileSize);
+
+    if(vpRect.right() > ret.right() && delta.x() > 0)
+        ret.moveLeft(vpRect.left() - tileSize);
+    else if(vpRect.left() < ret.left() & delta.x() < 0)
+        ret.moveRight(vpRect.right() + tileSize);
+
+    ret = validateTileRect(ret, m_tilesDim);
+
+    moveTilesRect(ret);
+}
+
+#ifdef USE_ASSISTANT_ITEM
+
+void TiledWebView::resizeEvent(QGraphicsSceneResizeEvent *event)
+{
+    m_assistant->setGeometry(QRectF(QPoint(0,0), event->newSize()));
+}
+
+void TiledWebViewAssistant::paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget)
+{
+    m_master->paint(painter, options, widget);
+}
+#endif