~mutse-young/ubuntu-docviewer-app/trunk

« back to all changes in this revision

Viewing changes to src/plugin/poppler-qml-plugin/verticalview.cpp

  • Committer: Stefano Verzegnassi
  • Date: 2015-01-30 19:20:39 UTC
  • mto: (63.3.3 30-new-header-style)
  • mto: This revision was merged to the branch mainline in revision 69.
  • Revision ID: stefano92.100@gmail.com-20150130192039-4hxxt4enfv4t3evz
Add VerticalView class in popplerqmlplugin (but not use it for now)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2013-2015 Canonical, Ltd.
 
3
 *
 
4
 * This program is free software; you can redistribute it and/or modify
 
5
 * it under the terms of the GNU General Public License as published by
 
6
 * the Free Software Foundation; version 3.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful,
 
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
 * GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License
 
14
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 */
 
16
 
 
17
/*
 
18
 * Some documentation on how this thing works:
 
19
 *
 
20
 * A flickable has two very important concepts that define the top and
 
21
 * height of the flickable area.
 
22
 * The top is returned in minYExtent()
 
23
 * The height is set using setContentHeight()
 
24
 * By changing those two values we can make the list grow up or down
 
25
 * as needed. e.g. if we are in the middle of the list
 
26
 * and something that is above the viewport grows, since we do not
 
27
 * want to change the viewport because of that we just adjust the
 
28
 * minYExtent so that the list grows up.
 
29
 *
 
30
 * The implementation on the list relies on the delegateModel doing
 
31
 * most of the instantiation work. You call createItem() when you
 
32
 * need to create an item asking for it async or not. If returns null
 
33
 * it means the item will be created async and the model will call the
 
34
 * itemCreated slot with the item.
 
35
 *
 
36
 * updatePolish is the central point of dispatch for the work of the
 
37
 * class. It is called by the scene graph just before drawing the class.
 
38
 * In it we:
 
39
 *  * Make sure all items are positioned correctly
 
40
 *  * Add/Remove items if needed
 
41
 *  * Update the content height if it was dirty
 
42
 *
 
43
 * m_visibleItems contains all the items we have created at the moment.
 
44
 * Actually not all of them are visible since it includes the ones
 
45
 * in the cache area we create asynchronously to help performance.
 
46
 * The first item in m_visibleItems has the m_firstVisibleIndex in
 
47
 * the model. If you actually want to know what is the first
 
48
 * item in the viewport you have to find the first non culled element
 
49
 * in m_visibleItems
 
50
 *
 
51
 * The first item of m_visibleItems is the one that defines the
 
52
 * positions of all the rest of items (see updatePolish()) and
 
53
 * this is why sometimes we move it even if it's not the item
 
54
 * that has triggered the function (i.e. in itemGeometryChanged())
 
55
 *
 
56
 * m_visibleItems is a list of ListItem. Each ListItem
 
57
 * will contain a item and potentially a sectionItem. The sectionItem
 
58
 * is only there when the list is using sectionDelegate+sectionProperty
 
59
 * and this is the first item of the section. Each ListItem is vertically
 
60
 * layouted with the sectionItem first and then the item.
 
61
 *
 
62
 * Note that minYExtent and height are not always totally accurate, since
 
63
 * we don't have the items created we can't guess their heights
 
64
 * so we can only guarantee the values are correct when the first/last
 
65
 * items of the list are visible, otherwise we just live with good enough
 
66
 * values that make the list scrollable
 
67
 *
 
68
 * There are a few things that are not really implemented or tested properly
 
69
 * which we don't use at the moment like changing the model, etc.
 
70
 * The known missing features are marked with TODOs along the code.
 
71
 */
 
72
 
 
73
#include "verticalview.h"
 
74
 
 
75
#include <QCoreApplication>
 
76
#include <QDebug>
 
77
#include <qqmlinfo.h>
 
78
#include <qqmlengine.h>
 
79
#pragma GCC diagnostic push
 
80
#pragma GCC diagnostic ignored "-pedantic"
 
81
#include <private/qqmldelegatemodel_p.h>
 
82
#include <private/qqmlglobal_p.h>
 
83
#include <private/qquickitem_p.h>
 
84
#include <private/qquickanimation_p.h>
 
85
#pragma GCC diagnostic pop
 
86
 
 
87
qreal VerticalView::ListItem::height() const
 
88
{
 
89
    return m_item->height();
 
90
}
 
91
 
 
92
qreal VerticalView::ListItem::y() const
 
93
{
 
94
    return m_item->y();
 
95
}
 
96
 
 
97
void VerticalView::ListItem::setY(qreal newY)
 
98
{
 
99
    m_item->setY(newY);
 
100
}
 
101
 
 
102
bool VerticalView::ListItem::culled() const
 
103
{
 
104
    return QQuickItemPrivate::get(m_item)->culled;
 
105
}
 
106
 
 
107
void VerticalView::ListItem::setCulled(bool culled)
 
108
{
 
109
    QQuickItemPrivate::get(m_item)->setCulled(culled);
 
110
}
 
111
 
 
112
VerticalView::VerticalView()
 
113
 : m_delegateModel(nullptr)
 
114
 , m_asyncRequestedIndex(-1)
 
115
 , m_delegateValidated(false)
 
116
 , m_firstVisibleIndex(-1)
 
117
 , m_currentPageIndex(-1)
 
118
 , m_minYExtent(0)
 
119
 , m_contentHeightDirty(false)
 
120
 , m_previousContentY(0)
 
121
 , m_inLayout(false)
 
122
 , m_cacheBuffer(0)
 
123
 , m_spacing(0)
 
124
{
 
125
    connect(this, SIGNAL(heightChanged()), this, SLOT(_q_heightChanged()));
 
126
    connect(this, SIGNAL(contentYChanged()), this, SLOT(_q_updateCurrentPageIndex()));
 
127
 
 
128
    setFlickableDirection(QQuickFlickable::HorizontalAndVerticalFlick);
 
129
}
 
130
 
 
131
VerticalView::~VerticalView()
 
132
{
 
133
}
 
134
 
 
135
QAbstractItemModel *VerticalView::model() const
 
136
{
 
137
    return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
 
138
}
 
139
 
 
140
void VerticalView::setModel(QAbstractItemModel *model)
 
141
{
 
142
    if (model != this->model()) {
 
143
        if (!m_delegateModel) {
 
144
            createDelegateModel();
 
145
        } else {
 
146
            disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
 
147
        }
 
148
        m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
 
149
        connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
 
150
        Q_EMIT modelChanged();
 
151
        polish();
 
152
        // TODO?
 
153
//         Q_EMIT contentHeightChanged();
 
154
//         Q_EMIT contentYChanged();
 
155
    }
 
156
}
 
157
 
 
158
QQmlComponent *VerticalView::delegate() const
 
159
{
 
160
    return m_delegateModel ? m_delegateModel->delegate() : nullptr;
 
161
}
 
162
 
 
163
void VerticalView::setDelegate(QQmlComponent *delegate)
 
164
{
 
165
    if (delegate != this->delegate()) {
 
166
        if (!m_delegateModel) {
 
167
            createDelegateModel();
 
168
        }
 
169
 
 
170
        // Cleanup the existing items
 
171
        Q_FOREACH(ListItem *item, m_visibleItems)
 
172
            releaseItem(item);
 
173
        m_visibleItems.clear();
 
174
        m_firstVisibleIndex = -1;
 
175
        adjustMinYExtent();
 
176
        setContentY(0);
 
177
 
 
178
        m_delegateModel->setDelegate(delegate);
 
179
 
 
180
        Q_EMIT delegateChanged();
 
181
        m_delegateValidated = false;
 
182
        m_contentHeightDirty = true;
 
183
        polish();
 
184
    }
 
185
}
 
186
 
 
187
int VerticalView::cacheBuffer() const
 
188
{
 
189
    return m_cacheBuffer;
 
190
}
 
191
 
 
192
void VerticalView::setCacheBuffer(int cacheBuffer)
 
193
{
 
194
    if (cacheBuffer < 0) {
 
195
        qmlInfo(this) << "Cannot set a negative cache buffer";
 
196
        return;
 
197
    }
 
198
 
 
199
    if (cacheBuffer != m_cacheBuffer) {
 
200
        m_cacheBuffer = cacheBuffer;
 
201
        Q_EMIT cacheBufferChanged();
 
202
        polish();
 
203
    }
 
204
}
 
205
 
 
206
qreal VerticalView::spacing() const
 
207
{
 
208
    return m_spacing;
 
209
}
 
210
 
 
211
void VerticalView::setSpacing(qreal spacing)
 
212
{
 
213
    if (spacing < 0) {
 
214
        qmlInfo(this) << "Cannot set a negative spacing";
 
215
        return;
 
216
    }
 
217
 
 
218
    if (spacing != m_spacing) {
 
219
        m_spacing = spacing;
 
220
        Q_EMIT spacingChanged();
 
221
        polish();
 
222
    }
 
223
}
 
224
 
 
225
int VerticalView::count() const
 
226
{
 
227
    if (m_delegateModel)
 
228
        return m_delegateModel->count();
 
229
    else
 
230
        return 0;
 
231
}
 
232
 
 
233
int VerticalView::currentPageIndex() const
 
234
{
 
235
    return m_currentPageIndex;
 
236
}
 
237
 
 
238
QQuickItem *VerticalView::currentPageItem() const
 
239
{
 
240
    return itemAt(m_currentPageIndex);
 
241
}
 
242
 
 
243
void VerticalView::_q_updateCurrentPageIndex()
 
244
{
 
245
    if (!m_visibleItems.isEmpty()) {
 
246
        qreal pos = this->contentY() + (this->height() * 0.5);
 
247
 
 
248
        int oldCurrentPageIndex = m_currentPageIndex;
 
249
        int i = 0;
 
250
 
 
251
        Q_FOREACH(ListItem * item, m_visibleItems) {
 
252
            if (item->y() < pos && item->y() + item->height() > pos)
 
253
                break;
 
254
 
 
255
            i++;
 
256
        }
 
257
 
 
258
        // If spacing is set, there may be no page on posY position,
 
259
        // and the Q_FOREACH loop keep on running until the end.
 
260
        if (i != m_visibleItems.length())
 
261
            m_currentPageIndex = m_firstVisibleIndex + i;
 
262
 
 
263
        if (m_currentPageIndex != oldCurrentPageIndex) {
 
264
            Q_EMIT currentPageIndexChanged();
 
265
            Q_EMIT currentPageItemChanged();
 
266
        }
 
267
 
 
268
    }
 
269
}
 
270
 
 
271
void VerticalView::positionAtBeginning()
 
272
{
 
273
    if (m_delegateModel->count() <= 0)
 
274
        return;
 
275
 
 
276
    if (m_firstVisibleIndex != 0) {
 
277
        // TODO This could be optimized by trying to reuse the interesection
 
278
        // of items that may end up intersecting between the existing
 
279
        // m_visibleItems and the items we are creating in the next loop
 
280
        Q_FOREACH(ListItem *item, m_visibleItems)
 
281
            releaseItem(item);
 
282
        m_visibleItems.clear();
 
283
        m_firstVisibleIndex = -1;
 
284
 
 
285
        // Create the item 0, it will be already correctly positioned at createItem()
 
286
        ListItem *item = createItem(0, false);
 
287
        // Create the subsequent items
 
288
        int modelIndex = 1;
 
289
        qreal pos = item->y() + item->height();
 
290
        const qreal bufferTo = height() + m_cacheBuffer;
 
291
        while (modelIndex < m_delegateModel->count() && pos <= bufferTo) {
 
292
            if (!(item = createItem(modelIndex, false)))
 
293
                break;
 
294
            pos += item->height();
 
295
            ++modelIndex;
 
296
        }
 
297
 
 
298
        m_previousContentY = m_visibleItems.first()->y();
 
299
    }
 
300
    setContentY(m_visibleItems.first()->y());
 
301
}
 
302
 
 
303
void VerticalView::positionAtIndex(int index)
 
304
{
 
305
    if (m_delegateModel->count() <= 0)
 
306
        return;
 
307
 
 
308
    if (index < m_firstVisibleIndex || index > m_firstVisibleIndex + m_visibleItems.length()) {
 
309
        // TODO This could be optimized by trying to reuse the interesection
 
310
        // of items that may end up intersecting between the existing
 
311
        // m_visibleItems and the items we are creating in the next loop
 
312
        Q_FOREACH(ListItem *item, m_visibleItems)
 
313
            releaseItem(item);
 
314
        m_visibleItems.clear();
 
315
        m_firstVisibleIndex = -1;
 
316
 
 
317
        // Create the item with the given index, it will be already correctly positioned at createItem()
 
318
        // Other items are created when createdItem() signal is emitted.
 
319
        createItem(index, false);
 
320
 
 
321
        m_previousContentY = m_visibleItems.first()->y();
 
322
    }
 
323
 
 
324
    setContentY(itemAt(index)->y());
 
325
}
 
326
 
 
327
void VerticalView::positionAtEnd()
 
328
{
 
329
    if (m_delegateModel->count() <= 0)
 
330
        return;
 
331
 
 
332
    if (m_firstVisibleIndex != m_delegateModel->count() - 1) {
 
333
        // TODO This could be optimized by trying to reuse the interesection
 
334
        // of items that may end up intersecting between the existing
 
335
        // m_visibleItems and the items we are creating in the next loop
 
336
        Q_FOREACH(ListItem *item, m_visibleItems)
 
337
            releaseItem(item);
 
338
        m_visibleItems.clear();
 
339
        m_firstVisibleIndex = -1;
 
340
 
 
341
        // Create the item 0, it will be already correctly positioned at createItem()
 
342
        ListItem *item = createItem(m_delegateModel->count() - 1, false);
 
343
        // Create the prior items
 
344
        int modelIndex = m_delegateModel->count() - 2;
 
345
        qreal pos = item->y() + item->height();
 
346
        const qreal bufferFrom = contentY() - m_cacheBuffer;
 
347
        while (modelIndex > -1 && pos >= bufferFrom) {
 
348
            if (!(item = createItem(modelIndex, false)))
 
349
                break;
 
350
            pos += item->height();
 
351
            --modelIndex;
 
352
        }
 
353
 
 
354
        m_previousContentY = m_visibleItems.first()->y();
 
355
    }
 
356
    setContentY(m_visibleItems.first()->y());
 
357
}
 
358
 
 
359
static inline bool uFuzzyCompare(qreal r1, qreal r2)
 
360
{
 
361
    return qFuzzyCompare(r1, r2) || (qFuzzyIsNull(r1) && qFuzzyIsNull(r2));
 
362
}
 
363
 
 
364
QQuickItem *VerticalView::itemAt(int modelIndex) const
 
365
{
 
366
    ListItem *item = itemAtIndex(modelIndex);
 
367
    if (item)
 
368
        return item->m_item;
 
369
    else
 
370
        return nullptr;
 
371
}
 
372
 
 
373
qreal VerticalView::minYExtent() const
 
374
{
 
375
    return m_minYExtent;
 
376
}
 
377
 
 
378
void VerticalView::componentComplete()
 
379
{
 
380
    if (m_delegateModel)
 
381
        m_delegateModel->componentComplete();
 
382
 
 
383
    QQuickFlickable::componentComplete();
 
384
 
 
385
    polish();
 
386
}
 
387
 
 
388
void VerticalView::viewportMoved(Qt::Orientations orient)
 
389
{
 
390
    // Check we are not being taken down and don't paint anything
 
391
    // TODO Check if we still need this in 5.2
 
392
    // For reproduction just inifnite loop testDash or testDashContent
 
393
    if (!QQmlEngine::contextForObject(this)->parentContext())
 
394
        return;
 
395
 
 
396
    QQuickFlickable::viewportMoved(orient);
 
397
    m_previousContentY = contentY();
 
398
    layout();
 
399
    polish();
 
400
}
 
401
 
 
402
void VerticalView::createDelegateModel()
 
403
{
 
404
    m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
 
405
    connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_itemCreated(int,QObject*)));
 
406
    connect(m_delegateModel, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
 
407
    if (isComponentComplete())
 
408
        m_delegateModel->componentComplete();
 
409
    updateWatchedRoles();
 
410
}
 
411
 
 
412
void VerticalView::refill()
 
413
{
 
414
    if (m_inLayout) {
 
415
        return;
 
416
    }
 
417
    if (!isComponentComplete()) {
 
418
        return;
 
419
    }
 
420
 
 
421
    const qreal from = contentY();
 
422
    const qreal to = from + height();
 
423
    const qreal bufferFrom = from - m_cacheBuffer;
 
424
    const qreal bufferTo = to + m_cacheBuffer;
 
425
 
 
426
    bool added = addVisibleItems(from, to, false);
 
427
    bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
 
428
    added |= addVisibleItems(bufferFrom, bufferTo, true);
 
429
 
 
430
    if (added || removed) {
 
431
        m_contentHeightDirty = true;
 
432
    }
 
433
}
 
434
 
 
435
bool VerticalView::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
 
436
{
 
437
    if (!delegate())
 
438
        return false;
 
439
 
 
440
    if (m_delegateModel->count() == 0)
 
441
        return false;
 
442
 
 
443
    ListItem *item;
 
444
    int modelIndex = 0;
 
445
    qreal pos = 0;
 
446
    if (!m_visibleItems.isEmpty()) {
 
447
        modelIndex = m_firstVisibleIndex + m_visibleItems.count();
 
448
        item = m_visibleItems.last();
 
449
        pos = item->y() + item->height() + m_spacing;
 
450
    }
 
451
    bool changed = false;
 
452
 
 
453
    while (modelIndex < m_delegateModel->count() && pos <= fillTo) {
 
454
        if (!(item = createItem(modelIndex, asynchronous)))
 
455
            break;
 
456
        pos += item->height() + m_spacing;
 
457
        ++modelIndex;
 
458
        changed = true;
 
459
    }
 
460
 
 
461
    modelIndex = 0;
 
462
    pos = 0;
 
463
    if (!m_visibleItems.isEmpty()) {
 
464
        modelIndex = m_firstVisibleIndex - 1;
 
465
        item = m_visibleItems.first();
 
466
        pos = item->y();
 
467
    }
 
468
    while (modelIndex >= 0 && pos > fillFrom) {
 
469
        if (!(item = createItem(modelIndex, asynchronous)))
 
470
            break;
 
471
        pos -= item->height() + m_spacing;
 
472
        --modelIndex;
 
473
        changed = true;
 
474
    }
 
475
 
 
476
    return changed;
 
477
}
 
478
 
 
479
void VerticalView::reallyReleaseItem(ListItem *listItem)
 
480
{
 
481
    QQuickItem *item = listItem->m_item;
 
482
    QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
 
483
    if (flags & QQmlDelegateModel::Destroyed) {
 
484
        item->setParentItem(nullptr);
 
485
    }
 
486
    delete listItem;
 
487
}
 
488
 
 
489
void VerticalView::releaseItem(ListItem *listItem)
 
490
{
 
491
    QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(listItem->m_item);
 
492
    itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
 
493
    m_itemsToRelease << listItem;
 
494
}
 
495
 
 
496
void VerticalView::updateWatchedRoles()
 
497
{
 
498
    if (m_delegateModel) {
 
499
        QList<QByteArray> roles;
 
500
        m_delegateModel->setWatchedRoles(roles);
 
501
    }
 
502
}
 
503
 
 
504
bool VerticalView::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
 
505
{
 
506
    // Do not remove items if we are overshooting up or down, since we'll come back
 
507
    // to the "stable" position and delete/create items without any reason
 
508
    if (contentY() < -m_minYExtent) {
 
509
        return false;
 
510
    } else if (contentY() + height() > contentHeight()) {
 
511
        return false;
 
512
    }
 
513
    bool changed = false;
 
514
 
 
515
    bool foundVisible = false;
 
516
    int i = 0;
 
517
    int removedItems = 0;
 
518
    const auto oldFirstVisibleIndex = m_firstVisibleIndex;
 
519
    while (i < m_visibleItems.count()) {
 
520
        ListItem *item = m_visibleItems[i];
 
521
        const qreal pos = item->y() + m_spacing;
 
522
        if (pos + item->height() < bufferFrom || pos > bufferTo) {
 
523
            releaseItem(item);
 
524
            m_visibleItems.removeAt(i);
 
525
            changed = true;
 
526
            ++removedItems;
 
527
        } else {
 
528
            if (!foundVisible) {
 
529
                foundVisible = true;
 
530
                const int itemIndex = m_firstVisibleIndex + removedItems + i;
 
531
                m_firstVisibleIndex = itemIndex;
 
532
            }
 
533
            ++i;
 
534
        }
 
535
    }
 
536
    if (!foundVisible) {
 
537
        m_firstVisibleIndex = -1;
 
538
    }
 
539
    if (m_firstVisibleIndex != oldFirstVisibleIndex) {
 
540
        adjustMinYExtent();
 
541
    }
 
542
 
 
543
    return changed;
 
544
}
 
545
 
 
546
VerticalView::ListItem *VerticalView::createItem(int modelIndex, bool asynchronous)
 
547
{
 
548
    if (asynchronous && m_asyncRequestedIndex != -1)
 
549
        return nullptr;
 
550
 
 
551
    m_asyncRequestedIndex = -1;
 
552
    QObject* object = m_delegateModel->object(modelIndex, asynchronous);
 
553
    QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
 
554
    if (!item) {
 
555
        if (object) {
 
556
            m_delegateModel->release(object);
 
557
            if (!m_delegateValidated) {
 
558
                m_delegateValidated = true;
 
559
                QObject* delegateObj = delegate();
 
560
                qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
 
561
            }
 
562
        } else {
 
563
            m_asyncRequestedIndex = modelIndex;
 
564
        }
 
565
        return 0;
 
566
    } else {
 
567
        ListItem *listItem = new ListItem;
 
568
        listItem->m_item = item;
 
569
        QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
 
570
        ListItem *prevItem = itemAtIndex(modelIndex - 1);
 
571
        bool lostItem = false; // Is an item that we requested async but because of model changes
 
572
                               // it is no longer attached to any of the existing items (has no prev nor next item)
 
573
                               // nor is the first item
 
574
        if (prevItem) {
 
575
            listItem->setY(prevItem->y() + prevItem->height() + m_spacing);
 
576
        } else {
 
577
            ListItem *currItem = itemAtIndex(modelIndex);
 
578
            if (currItem) {
 
579
                // There's something already in m_visibleItems at out index, meaning this is an insert, so attach to its top
 
580
                listItem->setY(currItem->y() - listItem->height() - m_spacing);
 
581
            } else {
 
582
                ListItem *nextItem = itemAtIndex(modelIndex + 1);
 
583
                if (nextItem) {
 
584
                    listItem->setY(nextItem->y() - listItem->height() - m_spacing);
 
585
                } else if (modelIndex == 0) {
 
586
                    listItem->setY(560);
 
587
                } else if (!m_visibleItems.isEmpty()) {
 
588
                    lostItem = true;
 
589
                }
 
590
            }
 
591
        }
 
592
        if (lostItem) {
 
593
            listItem->setCulled(true);
 
594
            releaseItem(listItem);
 
595
            listItem = nullptr;
 
596
        } else {
 
597
            listItem->setCulled(listItem->y() + listItem->height() + m_spacing <= contentY() || listItem->y() >= contentY() + height());
 
598
            if (m_visibleItems.isEmpty()) {
 
599
                m_visibleItems << listItem;
 
600
            } else {
 
601
                m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
 
602
            }
 
603
            if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
 
604
                m_firstVisibleIndex = modelIndex;
 
605
                polish();
 
606
            }
 
607
            adjustMinYExtent();
 
608
            m_contentHeightDirty = true;
 
609
        }
 
610
        return listItem;
 
611
    }
 
612
}
 
613
 
 
614
void VerticalView::_q_itemCreated(int modelIndex, QObject *object)
 
615
{
 
616
    QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
 
617
    if (!item) {
 
618
        qWarning() << "VerticalView::itemCreated got a non item for index" << modelIndex;
 
619
        return;
 
620
    }
 
621
 
 
622
    // Check we are not being taken down and don't paint anything
 
623
    // TODO Check if we still need this in 5.2
 
624
    // For reproduction just inifnite loop testDash or testDashContent
 
625
    if (!QQmlEngine::contextForObject(this)->parentContext())
 
626
        return;
 
627
 
 
628
    item->setParentItem(contentItem());
 
629
    QQmlContext *context = QQmlEngine::contextForObject(item)->parentContext();
 
630
    context->setContextProperty(QLatin1String("VerticalView"), this);
 
631
    context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
 
632
    if (modelIndex == m_asyncRequestedIndex) {
 
633
        createItem(modelIndex, false);
 
634
        refill();
 
635
    }
 
636
}
 
637
 
 
638
void VerticalView::_q_heightChanged()
 
639
{
 
640
    polish();
 
641
}
 
642
 
 
643
void VerticalView::_q_modelUpdated(const QQmlChangeSet &changeSet, bool /*reset*/)
 
644
{
 
645
    // TODO Do something with reset
 
646
    const auto oldFirstVisibleIndex = m_firstVisibleIndex;
 
647
 
 
648
    Q_FOREACH(const QQmlChangeSet::Change &remove, changeSet.removes()) {
 
649
        if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
 
650
            const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
 
651
            // If all the items we are removing are either not created or culled
 
652
            // we have to grow down to avoid viewport changing
 
653
            bool growDown = true;
 
654
            for (int i = 0; growDown && i < remove.count; ++i) {
 
655
                const int modelIndex = remove.index + i;
 
656
                ListItem *item = itemAtIndex(modelIndex);
 
657
                if (item && !item->culled()) {
 
658
                    growDown = false;
 
659
                }
 
660
            }
 
661
            for (int i = remove.count - 1; i >= 0; --i) {
 
662
                const int visibleIndex = remove.index + i - m_firstVisibleIndex;
 
663
                if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
 
664
                    ListItem *item = m_visibleItems[visibleIndex];
 
665
                    releaseItem(item);
 
666
                    m_visibleItems.removeAt(visibleIndex);
 
667
                }
 
668
            }
 
669
            if (growDown) {
 
670
                adjustMinYExtent();
 
671
            } else if (remove.index <= m_firstVisibleIndex) {
 
672
                if (!m_visibleItems.isEmpty()) {
 
673
                    // We removed the first item that is the one that positions the rest
 
674
                    // position the new first item correctly
 
675
                    m_visibleItems.first()->setY(oldFirstValidIndexPos);
 
676
                } else {
 
677
                    m_firstVisibleIndex = -1;
 
678
                }
 
679
            }
 
680
        } else if (remove.index + remove.count <= m_firstVisibleIndex) {
 
681
            m_firstVisibleIndex -= remove.count;
 
682
        }
 
683
        for (int i = remove.count - 1; i >= 0; --i) {
 
684
            const int modelIndex = remove.index + i;
 
685
            if (modelIndex == m_asyncRequestedIndex) {
 
686
                m_asyncRequestedIndex = -1;
 
687
            } else if (modelIndex < m_asyncRequestedIndex) {
 
688
                m_asyncRequestedIndex--;
 
689
            }
 
690
        }
 
691
    }
 
692
 
 
693
    Q_FOREACH(const QQmlChangeSet::Change &insert, changeSet.inserts()) {
 
694
        const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
 
695
        const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y()  > contentY();
 
696
        if (insertingInValidIndexes || firstItemWithViewOnTop)
 
697
        {
 
698
            // If the items we are adding won't be really visible
 
699
            // we grow up instead of down to not change the viewport
 
700
            bool growUp = false;
 
701
            if (!firstItemWithViewOnTop) {
 
702
                for (int i = 0; i < m_visibleItems.count(); ++i) {
 
703
                    if (!m_visibleItems[i]->culled()) {
 
704
                        if (insert.index <= m_firstVisibleIndex + i) {
 
705
                            growUp = true;
 
706
                        }
 
707
                        break;
 
708
                    }
 
709
                }
 
710
            }
 
711
 
 
712
            const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
 
713
            for (int i = insert.count - 1; i >= 0; --i) {
 
714
                const int modelIndex = insert.index + i;
 
715
                ListItem *item = createItem(modelIndex, false);
 
716
                if (growUp) {
 
717
                    ListItem *firstItem = m_visibleItems.first();
 
718
                    firstItem->setY(firstItem->y() - item->height());
 
719
                }
 
720
            }
 
721
            if (firstItemWithViewOnTop) {
 
722
                ListItem *firstItem = m_visibleItems.first();
 
723
                firstItem->setY(oldFirstValidIndexPos);
 
724
            }
 
725
            adjustMinYExtent();
 
726
        } else if (insert.index <= m_firstVisibleIndex) {
 
727
            m_firstVisibleIndex += insert.count;
 
728
        }
 
729
 
 
730
        for (int i = insert.count - 1; i >= 0; --i) {
 
731
            const int modelIndex = insert.index + i;
 
732
            if (modelIndex <= m_asyncRequestedIndex) {
 
733
                m_asyncRequestedIndex++;
 
734
            }
 
735
        }
 
736
    }
 
737
 
 
738
    if (m_firstVisibleIndex != oldFirstVisibleIndex) {
 
739
        adjustMinYExtent();
 
740
    }
 
741
 
 
742
    layout();
 
743
    polish();
 
744
    m_contentHeightDirty = true;
 
745
}
 
746
 
 
747
void VerticalView::itemGeometryChanged(QQuickItem * /*item*/, const QRectF &newGeometry, const QRectF &oldGeometry)
 
748
{
 
749
    const qreal heightDiff = newGeometry.height() - oldGeometry.height();
 
750
    if (heightDiff != 0) {
 
751
        if (oldGeometry.y() + oldGeometry.height() <= contentY() && !m_visibleItems.isEmpty()) {
 
752
            ListItem *firstItem = m_visibleItems.first();
 
753
            firstItem->setY(firstItem->y() - heightDiff);
 
754
            adjustMinYExtent();
 
755
            layout();
 
756
        }
 
757
        refill();
 
758
        adjustMinYExtent();
 
759
        polish();
 
760
        m_contentHeightDirty = true;
 
761
    }
 
762
}
 
763
 
 
764
void VerticalView::adjustMinYExtent()
 
765
{
 
766
    if (m_visibleItems.isEmpty()) {
 
767
        m_minYExtent = 0;
 
768
    } else {
 
769
        qreal nonCreatedHeight = 0;
 
770
        if (m_firstVisibleIndex != 0) {
 
771
            // Calculate the average height of items to estimate the position of the list start
 
772
            const int visibleItems = m_visibleItems.count();
 
773
            qreal visibleItemsHeight = 0;
 
774
            Q_FOREACH(ListItem *item, m_visibleItems) {
 
775
                visibleItemsHeight += item->height() + m_spacing;
 
776
            }
 
777
            nonCreatedHeight = m_firstVisibleIndex * visibleItemsHeight / visibleItems;
 
778
        }
 
779
        m_minYExtent = nonCreatedHeight - m_visibleItems.first()->y();
 
780
        if (m_minYExtent != 0 && qFuzzyIsNull(m_minYExtent)) {
 
781
            m_minYExtent = 0;
 
782
            m_visibleItems.first()->setY(nonCreatedHeight);
 
783
        }
 
784
    }
 
785
}
 
786
 
 
787
VerticalView::ListItem *VerticalView::itemAtIndex(int modelIndex) const
 
788
{
 
789
    const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
 
790
    if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
 
791
        return m_visibleItems[visibleIndexedModelIndex];
 
792
 
 
793
    return nullptr;
 
794
}
 
795
 
 
796
void VerticalView::layout()
 
797
{
 
798
    if (m_inLayout)
 
799
        return;
 
800
 
 
801
    m_inLayout = true;
 
802
    if (!m_visibleItems.isEmpty()) {
 
803
        const qreal visibleFrom = contentY();
 
804
        const qreal visibleTo = contentY() + height();
 
805
 
 
806
        qreal pos = m_visibleItems.first()->y();
 
807
 
 
808
//         qDebug() << "VerticalView::layout Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
 
809
        int firstReallyVisibleItem = -1;
 
810
        int modelIndex = m_firstVisibleIndex;
 
811
        Q_FOREACH(ListItem *item, m_visibleItems) {
 
812
            const bool cull = pos + item->height() + m_spacing <= visibleFrom || pos >= visibleTo;
 
813
            item->setCulled(cull);
 
814
            item->setY(pos);
 
815
            if (!cull && firstReallyVisibleItem == -1) {
 
816
                firstReallyVisibleItem = modelIndex;
 
817
            }
 
818
            QQmlContext *context = QQmlEngine::contextForObject(item->m_item)->parentContext();
 
819
            const qreal clipFrom = visibleFrom;
 
820
            if (!cull && pos < clipFrom) {
 
821
                context->setContextProperty(QLatin1String("heightToClip"), clipFrom - pos);
 
822
            } else {
 
823
                context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
 
824
            }
 
825
//             qDebug() << "VerticalView::layout" << item->m_item;
 
826
            pos += item->height() + m_spacing;
 
827
            ++modelIndex;
 
828
        }
 
829
    }
 
830
    m_inLayout = false;
 
831
}
 
832
 
 
833
void VerticalView::updatePolish()
 
834
{
 
835
    // Check we are not being taken down and don't paint anything
 
836
    // TODO Check if we still need this in 5.2
 
837
    // For reproduction just inifnite loop testDash or testDashContent
 
838
    if (!QQmlEngine::contextForObject(this)->parentContext())
 
839
        return;
 
840
 
 
841
    Q_FOREACH(ListItem *item, m_itemsToRelease)
 
842
        reallyReleaseItem(item);
 
843
    m_itemsToRelease.clear();
 
844
 
 
845
    if (!model())
 
846
        return;
 
847
 
 
848
    layout();
 
849
 
 
850
    refill();
 
851
 
 
852
    if (m_contentHeightDirty) {
 
853
        qreal contentHeight;
 
854
        if (m_visibleItems.isEmpty()) {
 
855
            contentHeight = 0;
 
856
        } else {
 
857
            const int modelCount = model()->rowCount();
 
858
            const int visibleItems = m_visibleItems.count();
 
859
            const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
 
860
            qreal nonCreatedHeight = 0;
 
861
            if (lastValidIndex != modelCount - 1) {
 
862
                const int visibleItems = m_visibleItems.count();
 
863
                qreal visibleItemsHeight = 0;
 
864
                Q_FOREACH(ListItem *item, m_visibleItems) {
 
865
                    visibleItemsHeight += item->height() + m_spacing;
 
866
                }
 
867
                const int unknownSizes = modelCount - (m_firstVisibleIndex + visibleItems);
 
868
                nonCreatedHeight = unknownSizes * visibleItemsHeight / visibleItems;
 
869
            }
 
870
            ListItem *item = m_visibleItems.last();
 
871
            contentHeight = nonCreatedHeight + item->y() + item->height();
 
872
            if (m_firstVisibleIndex != 0) {
 
873
                // Make sure that if we are shrinking we tell the view we still fit
 
874
                m_minYExtent = qMax(m_minYExtent, -(contentHeight - height()));
 
875
            }
 
876
        }
 
877
 
 
878
        m_contentHeightDirty = false;
 
879
        adjustMinYExtent();
 
880
        setContentHeight(contentHeight);
 
881
    }
 
882
}
 
883
 
 
884
#include "moc_verticalview.cpp"