~ubuntu-branches/ubuntu/wily/kitemviews/wily-proposed

« back to all changes in this revision

Viewing changes to src/kcategorizedview.cpp

  • Committer: Package Import Robot
  • Author(s): Rohan Garg
  • Date: 2014-07-10 15:49:38 UTC
  • Revision ID: package-import@ubuntu.com-20140710154938-c6kke30uicz0ypvo
Tags: upstream-5.0.0
Import upstream version 5.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
  * This file is part of the KDE project
 
3
  * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
 
4
  *
 
5
  * This library is free software; you can redistribute it and/or
 
6
  * modify it under the terms of the GNU Library General Public
 
7
  * License as published by the Free Software Foundation; either
 
8
  * version 2 of the License, or (at your option) any later version.
 
9
  *
 
10
  * This library is distributed in the hope that it will be useful,
 
11
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
  * Library General Public License for more details.
 
14
  *
 
15
  * You should have received a copy of the GNU Library General Public License
 
16
  * along with this library; see the file COPYING.LIB.  If not, write to
 
17
  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
18
  * Boston, MA 02110-1301, USA.
 
19
  */
 
20
 
 
21
/**
 
22
  * IMPLEMENTATION NOTES:
 
23
  *
 
24
  * QListView::setRowHidden() and QListView::isRowHidden() are not taken into
 
25
  * account. This methods should actually not exist. This effect should be handled
 
26
  * by an hypothetical QSortFilterProxyModel which filters out the desired rows.
 
27
  *
 
28
  * In case this needs to be implemented, contact me, but I consider this a faulty
 
29
  * design.
 
30
  */
 
31
 
 
32
#include "kcategorizedview.h"
 
33
#include "kcategorizedview_p.h"
 
34
 
 
35
#include <QPainter>
 
36
#include <QScrollBar>
 
37
#include <QPaintEvent>
 
38
 
 
39
#include "kcategorydrawer.h"
 
40
#include "kcategorizedsortfilterproxymodel.h"
 
41
 
 
42
//BEGIN: Private part
 
43
 
 
44
struct KCategorizedView::Private::Item {
 
45
    Item()
 
46
        : topLeft(QPoint())
 
47
        , size(QSize())
 
48
    {
 
49
    }
 
50
 
 
51
    QPoint topLeft;
 
52
    QSize size;
 
53
};
 
54
 
 
55
struct KCategorizedView::Private::Block {
 
56
    Block()
 
57
        : topLeft(QPoint())
 
58
        , height(-1)
 
59
        , firstIndex(QModelIndex())
 
60
        , quarantineStart(QModelIndex())
 
61
        , items(QList<Item>())
 
62
        , outOfQuarantine(false)
 
63
        , alternate(false)
 
64
        , collapsed(false)
 
65
    {
 
66
    }
 
67
 
 
68
    bool operator!=(const Block &rhs) const
 
69
    {
 
70
        return firstIndex != rhs.firstIndex;
 
71
    }
 
72
 
 
73
    static bool lessThan(const Block &left, const Block &right)
 
74
    {
 
75
        Q_ASSERT(left.firstIndex.isValid());
 
76
        Q_ASSERT(right.firstIndex.isValid());
 
77
        return left.firstIndex.row() < right.firstIndex.row();
 
78
    }
 
79
 
 
80
    QPoint topLeft;
 
81
    int height;
 
82
    QPersistentModelIndex firstIndex;
 
83
    // if we have n elements on this block, and we inserted an element at position i. The quarantine
 
84
    // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the
 
85
    // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine
 
86
    // will only affect the current block, since the rest of blocks can be affected only in the way
 
87
    // that the whole block will have different offset, but items will keep the same relative position
 
88
    // in terms of their parent blocks.
 
89
    QPersistentModelIndex quarantineStart;
 
90
    QList<Item> items;
 
91
 
 
92
    // this affects the whole block, not items separately. items contain the topLeft point relative
 
93
    // to the block. Because of insertions or removals a whole block can be moved, so the whole block
 
94
    // will enter in quarantine, what is faster than moving all items in absolute terms.
 
95
    bool outOfQuarantine;
 
96
 
 
97
    // should we alternate its color ? is just a hint, could not be used
 
98
    bool alternate;
 
99
    bool collapsed;
 
100
};
 
101
 
 
102
KCategorizedView::Private::Private(KCategorizedView *q)
 
103
    : q(q)
 
104
    , proxyModel(0)
 
105
    , categoryDrawer(0)
 
106
    , categorySpacing(5)
 
107
    , alternatingBlockColors(false)
 
108
    , collapsibleBlocks(false)
 
109
    , hoveredBlock(new Block())
 
110
    , hoveredIndex(QModelIndex())
 
111
    , pressedPosition(QPoint())
 
112
    , rubberBandRect(QRect())
 
113
{
 
114
}
 
115
 
 
116
KCategorizedView::Private::~Private()
 
117
{
 
118
    delete hoveredBlock;
 
119
}
 
120
 
 
121
bool KCategorizedView::Private::isCategorized() const
 
122
{
 
123
    return proxyModel && categoryDrawer && proxyModel->isCategorizedModel();
 
124
}
 
125
 
 
126
QStyleOptionViewItemV4 KCategorizedView::Private::blockRect(const QModelIndex &representative)
 
127
{
 
128
    QStyleOptionViewItemV4 option(q->viewOptions());
 
129
    const int height = categoryDrawer->categoryHeight(representative, option);
 
130
    const QString categoryDisplay = representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
 
131
    QPoint pos = blockPosition(categoryDisplay);
 
132
    pos.ry() -= height;
 
133
    option.rect.setTopLeft(pos);
 
134
    option.rect.setWidth(viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin());
 
135
    option.rect.setHeight(height + blockHeight(categoryDisplay));
 
136
    option.rect = mapToViewport(option.rect);
 
137
 
 
138
    return option;
 
139
}
 
140
 
 
141
QPair<QModelIndex, QModelIndex> KCategorizedView::Private::intersectingIndexesWithRect(const QRect &_rect) const
 
142
{
 
143
    const int rowCount = proxyModel->rowCount();
 
144
 
 
145
    const QRect rect = _rect.normalized();
 
146
 
 
147
    // binary search to find out the top border
 
148
    int bottom = 0;
 
149
    int top = rowCount - 1;
 
150
    while (bottom <= top) {
 
151
        const int middle = (bottom + top) / 2;
 
152
        const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
 
153
        const QRect itemRect = q->visualRect(index);
 
154
        if (itemRect.bottomRight().y() <= rect.topLeft().y()) {
 
155
            bottom = middle + 1;
 
156
        } else {
 
157
            top = middle - 1;
 
158
        }
 
159
    }
 
160
 
 
161
    const QModelIndex bottomIndex = proxyModel->index(bottom, q->modelColumn(), q->rootIndex());
 
162
 
 
163
    // binary search to find out the bottom border
 
164
    bottom = 0;
 
165
    top = rowCount - 1;
 
166
    while (bottom <= top) {
 
167
        const int middle = (bottom + top) / 2;
 
168
        const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
 
169
        const QRect itemRect = q->visualRect(index);
 
170
        if (itemRect.topLeft().y() <= rect.bottomRight().y()) {
 
171
            bottom = middle + 1;
 
172
        } else {
 
173
            top = middle - 1;
 
174
        }
 
175
    }
 
176
 
 
177
    const QModelIndex topIndex = proxyModel->index(top, q->modelColumn(), q->rootIndex());
 
178
 
 
179
    return qMakePair(bottomIndex, topIndex);
 
180
}
 
181
 
 
182
QPoint KCategorizedView::Private::blockPosition(const QString &category)
 
183
{
 
184
    Block &block = blocks[category];
 
185
 
 
186
    if (block.outOfQuarantine && !block.topLeft.isNull()) {
 
187
        return block.topLeft;
 
188
    }
 
189
 
 
190
    QPoint res(categorySpacing, 0);
 
191
 
 
192
    const QModelIndex index = block.firstIndex;
 
193
 
 
194
    for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
 
195
        Block &block = *it;
 
196
        const QModelIndex categoryIndex = block.firstIndex;
 
197
        if (index.row() < categoryIndex.row()) {
 
198
            continue;
 
199
        }
 
200
        res.ry() += categoryDrawer->categoryHeight(categoryIndex, q->viewOptions()) + categorySpacing;
 
201
        if (index.row() == categoryIndex.row()) {
 
202
            continue;
 
203
        }
 
204
        res.ry() += blockHeight(it.key());
 
205
    }
 
206
 
 
207
    block.outOfQuarantine = true;
 
208
    block.topLeft = res;
 
209
 
 
210
    return res;
 
211
}
 
212
 
 
213
int KCategorizedView::Private::blockHeight(const QString &category)
 
214
{
 
215
    Block &block = blocks[category];
 
216
 
 
217
    if (block.collapsed) {
 
218
        return 0;
 
219
    }
 
220
 
 
221
    if (block.height > -1) {
 
222
        return block.height;
 
223
    }
 
224
 
 
225
    const QModelIndex firstIndex = block.firstIndex;
 
226
    const QModelIndex lastIndex = proxyModel->index(firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
 
227
    const QRect topLeft = q->visualRect(firstIndex);
 
228
    QRect bottomRight = q->visualRect(lastIndex);
 
229
 
 
230
    if (hasGrid()) {
 
231
        bottomRight.setHeight(qMax(bottomRight.height(), q->gridSize().height()));
 
232
    } else {
 
233
        if (!q->uniformItemSizes()) {
 
234
            bottomRight.setHeight(highestElementInLastRow(block) + q->spacing() * 2);
 
235
        }
 
236
    }
 
237
 
 
238
    const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1;
 
239
    block.height = height;
 
240
 
 
241
    return height;
 
242
}
 
243
 
 
244
int KCategorizedView::Private::viewportWidth() const
 
245
{
 
246
    return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin();
 
247
}
 
248
 
 
249
void KCategorizedView::Private::regenerateAllElements()
 
250
{
 
251
    for (QHash<QString, Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
 
252
        Block &block = *it;
 
253
        block.outOfQuarantine = false;
 
254
        block.quarantineStart = block.firstIndex;
 
255
        block.height = -1;
 
256
    }
 
257
}
 
258
 
 
259
void KCategorizedView::Private::rowsInserted(const QModelIndex &parent, int start, int end)
 
260
{
 
261
    if (!isCategorized()) {
 
262
        return;
 
263
    }
 
264
 
 
265
    for (int i = start; i <= end; ++i) {
 
266
        const QModelIndex index = proxyModel->index(i, q->modelColumn(), parent);
 
267
 
 
268
        Q_ASSERT(index.isValid());
 
269
 
 
270
        const QString category = categoryForIndex(index);
 
271
 
 
272
        Block &block = blocks[category];
 
273
 
 
274
        //BEGIN: update firstIndex
 
275
        // save as firstIndex in block if
 
276
        //     - it forced the category creation (first element on this category)
 
277
        //     - it is before the first row on that category
 
278
        const QModelIndex firstIndex = block.firstIndex;
 
279
        if (!firstIndex.isValid() || index.row() < firstIndex.row()) {
 
280
            block.firstIndex = index;
 
281
        }
 
282
        //END: update firstIndex
 
283
 
 
284
        Q_ASSERT(block.firstIndex.isValid());
 
285
 
 
286
        const int firstIndexRow = block.firstIndex.row();
 
287
 
 
288
        block.items.insert(index.row() - firstIndexRow, Private::Item());
 
289
        block.height = -1;
 
290
 
 
291
        q->visualRect(index);
 
292
        q->viewport()->update();
 
293
    }
 
294
 
 
295
    //BEGIN: update the items that are in quarantine in affected categories
 
296
    {
 
297
        const QModelIndex lastIndex = proxyModel->index(end, q->modelColumn(), parent);
 
298
        const QString category = categoryForIndex(lastIndex);
 
299
        Private::Block &block = blocks[category];
 
300
        block.quarantineStart = block.firstIndex;
 
301
    }
 
302
    //END: update the items that are in quarantine in affected categories
 
303
 
 
304
    //BEGIN: mark as in quarantine those categories that are under the affected ones
 
305
    {
 
306
        const QModelIndex firstIndex = proxyModel->index(start, q->modelColumn(), parent);
 
307
        const QString category = categoryForIndex(firstIndex);
 
308
        const QModelIndex firstAffectedCategory = blocks[category].firstIndex;
 
309
        //BEGIN: order for marking as alternate those blocks that are alternate
 
310
        QList<Block> blockList = blocks.values();
 
311
        qSort(blockList.begin(), blockList.end(), Block::lessThan);
 
312
        QList<int> firstIndexesRows;
 
313
        foreach (const Block &block, blockList) {
 
314
            firstIndexesRows << block.firstIndex.row();
 
315
        }
 
316
        //END: order for marking as alternate those blocks that are alternate
 
317
        for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
 
318
            Private::Block &block = *it;
 
319
            if (block.firstIndex.row() > firstAffectedCategory.row()) {
 
320
                block.outOfQuarantine = false;
 
321
                block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
 
322
            } else if (block.firstIndex.row() == firstAffectedCategory.row()) {
 
323
                block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
 
324
            }
 
325
        }
 
326
    }
 
327
    //END: mark as in quarantine those categories that are under the affected ones
 
328
}
 
329
 
 
330
QRect KCategorizedView::Private::mapToViewport(const QRect &rect) const
 
331
{
 
332
    const int dx = -q->horizontalOffset();
 
333
    const int dy = -q->verticalOffset();
 
334
    return rect.adjusted(dx, dy, dx, dy);
 
335
}
 
336
 
 
337
QRect KCategorizedView::Private::mapFromViewport(const QRect &rect) const
 
338
{
 
339
    const int dx = q->horizontalOffset();
 
340
    const int dy = q->verticalOffset();
 
341
    return rect.adjusted(dx, dy, dx, dy);
 
342
}
 
343
 
 
344
int KCategorizedView::Private::highestElementInLastRow(const Block &block) const
 
345
{
 
346
    //Find the highest element in the last row
 
347
    const QModelIndex lastIndex = proxyModel->index(block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
 
348
    const QRect prevRect = q->visualRect(lastIndex);
 
349
    int res = prevRect.height();
 
350
    QModelIndex prevIndex = proxyModel->index(lastIndex.row() - 1, q->modelColumn(), q->rootIndex());
 
351
    if (!prevIndex.isValid()) {
 
352
        return res;
 
353
    }
 
354
    Q_FOREVER {
 
355
    const QRect tempRect = q->visualRect(prevIndex);
 
356
        if (tempRect.topLeft().y() < prevRect.topLeft().y())
 
357
        {
 
358
            break;
 
359
        }
 
360
        res = qMax(res, tempRect.height());
 
361
        if (prevIndex == block.firstIndex)
 
362
        {
 
363
            break;
 
364
        }
 
365
        prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
 
366
    }
 
367
 
 
368
    return res;
 
369
}
 
370
 
 
371
bool KCategorizedView::Private::hasGrid() const
 
372
{
 
373
    const QSize gridSize = q->gridSize();
 
374
    return gridSize.isValid() && !gridSize.isNull();
 
375
}
 
376
 
 
377
QString KCategorizedView::Private::categoryForIndex(const QModelIndex &index) const
 
378
{
 
379
    const QModelIndex categoryIndex = index.model()->index(index.row(), proxyModel->sortColumn(), index.parent());
 
380
    return categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
 
381
}
 
382
 
 
383
void KCategorizedView::Private::leftToRightVisualRect(const QModelIndex &index, Item &item,
 
384
        const Block &block, const QPoint &blockPos) const
 
385
{
 
386
    const int firstIndexRow = block.firstIndex.row();
 
387
 
 
388
    if (hasGrid()) {
 
389
        const int relativeRow = index.row() - firstIndexRow;
 
390
        const int maxItemsPerRow = qMax(viewportWidth() / q->gridSize().width(), 1);
 
391
        if (q->layoutDirection() == Qt::LeftToRight) {
 
392
            item.topLeft.rx() = (relativeRow % maxItemsPerRow) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin();
 
393
        } else {
 
394
            item.topLeft.rx() = viewportWidth() - ((relativeRow % maxItemsPerRow) + 1) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing;
 
395
        }
 
396
        item.topLeft.ry() = (relativeRow / maxItemsPerRow) * q->gridSize().height();
 
397
    } else {
 
398
        if (q->uniformItemSizes()) {
 
399
            const int relativeRow = index.row() - firstIndexRow;
 
400
            const QSize itemSize = q->sizeHintForIndex(index);
 
401
            const int maxItemsPerRow = qMax((viewportWidth() - q->spacing()) / (itemSize.width() + q->spacing()), 1);
 
402
            if (q->layoutDirection() == Qt::LeftToRight) {
 
403
                item.topLeft.rx() = (relativeRow % maxItemsPerRow) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
 
404
            } else {
 
405
                item.topLeft.rx() = viewportWidth() - (relativeRow % maxItemsPerRow) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing;
 
406
            }
 
407
            item.topLeft.ry() = (relativeRow / maxItemsPerRow) * itemSize.height();
 
408
        } else {
 
409
            const QSize currSize = q->sizeHintForIndex(index);
 
410
            if (index != block.firstIndex) {
 
411
                const int viewportW = viewportWidth() - q->spacing();
 
412
                QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
 
413
                QRect prevRect = q->visualRect(prevIndex);
 
414
                prevRect = mapFromViewport(prevRect);
 
415
                if ((prevRect.bottomRight().x() + 1) + currSize.width() - blockPos.x() + q->spacing()  > viewportW) {
 
416
                    // we have to check the whole previous row, and see which one was the
 
417
                    // highest.
 
418
                    Q_FOREVER {
 
419
                    prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
 
420
                        const QRect tempRect = q->visualRect(prevIndex);
 
421
                        if (tempRect.topLeft().y() < prevRect.topLeft().y())
 
422
                        {
 
423
                            break;
 
424
                        }
 
425
                        if (tempRect.bottomRight().y() > prevRect.bottomRight().y())
 
426
                        {
 
427
                            prevRect = tempRect;
 
428
                        }
 
429
                        if (prevIndex == block.firstIndex)
 
430
                        {
 
431
                            break;
 
432
                        }
 
433
                    }
 
434
                    if (q->layoutDirection() == Qt::LeftToRight) {
 
435
                        item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing();
 
436
                    } else {
 
437
                        item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
 
438
                    }
 
439
                    item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
 
440
                } else {
 
441
                    if (q->layoutDirection() == Qt::LeftToRight) {
 
442
                        item.topLeft.rx() = (prevRect.bottomRight().x() + 1) + q->spacing();
 
443
                    } else {
 
444
                        item.topLeft.rx() = (prevRect.bottomLeft().x() - 1) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing;
 
445
                    }
 
446
                    item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y();
 
447
                }
 
448
            } else {
 
449
                if (q->layoutDirection() == Qt::LeftToRight) {
 
450
                    item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
 
451
                } else {
 
452
                    item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
 
453
                }
 
454
                item.topLeft.ry() = q->spacing();
 
455
            }
 
456
        }
 
457
    }
 
458
    item.size = q->sizeHintForIndex(index);
 
459
}
 
460
 
 
461
void KCategorizedView::Private::topToBottomVisualRect(const QModelIndex &index, Item &item,
 
462
        const Block &block, const QPoint &blockPos) const
 
463
{
 
464
    const int firstIndexRow = block.firstIndex.row();
 
465
 
 
466
    if (hasGrid()) {
 
467
        const int relativeRow = index.row() - firstIndexRow;
 
468
        item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
 
469
        item.topLeft.ry() = relativeRow * q->gridSize().height();
 
470
    } else {
 
471
        if (q->uniformItemSizes()) {
 
472
            const int relativeRow = index.row() - firstIndexRow;
 
473
            const QSize itemSize = q->sizeHintForIndex(index);
 
474
            item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
 
475
            item.topLeft.ry() = relativeRow * itemSize.height();
 
476
        } else {
 
477
            if (index != block.firstIndex) {
 
478
                QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
 
479
                QRect prevRect = q->visualRect(prevIndex);
 
480
                prevRect = mapFromViewport(prevRect);
 
481
                item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
 
482
                item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
 
483
            } else {
 
484
                item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
 
485
                item.topLeft.ry() = q->spacing();
 
486
            }
 
487
        }
 
488
    }
 
489
    item.size = q->sizeHintForIndex(index);
 
490
    item.size.setWidth(viewportWidth());
 
491
}
 
492
 
 
493
void KCategorizedView::Private::_k_slotCollapseOrExpandClicked(QModelIndex)
 
494
{
 
495
}
 
496
 
 
497
//END: Private part
 
498
 
 
499
//BEGIN: Public part
 
500
 
 
501
KCategorizedView::KCategorizedView(QWidget *parent)
 
502
    : QListView(parent)
 
503
    , d(new Private(this))
 
504
{
 
505
}
 
506
 
 
507
KCategorizedView::~KCategorizedView()
 
508
{
 
509
    delete d;
 
510
}
 
511
 
 
512
void KCategorizedView::setModel(QAbstractItemModel *model)
 
513
{
 
514
    if (d->proxyModel == model) {
 
515
        return;
 
516
    }
 
517
 
 
518
    d->blocks.clear();
 
519
 
 
520
    if (d->proxyModel) {
 
521
        disconnect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()));
 
522
    }
 
523
 
 
524
    d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel *>(model);
 
525
 
 
526
    if (d->proxyModel) {
 
527
        connect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()));
 
528
    }
 
529
 
 
530
    QListView::setModel(model);
 
531
 
 
532
    // if the model already had information inserted, update our data structures to it
 
533
    if (model->rowCount()) {
 
534
        slotLayoutChanged();
 
535
    }
 
536
}
 
537
 
 
538
void KCategorizedView::setGridSize(const QSize &size)
 
539
{
 
540
    setGridSizeOwn(size);
 
541
}
 
542
 
 
543
void KCategorizedView::setGridSizeOwn(const QSize &size)
 
544
{
 
545
    d->regenerateAllElements();
 
546
    QListView::setGridSize(size);
 
547
}
 
548
 
 
549
QRect KCategorizedView::visualRect(const QModelIndex &index) const
 
550
{
 
551
    if (!d->isCategorized()) {
 
552
        return QListView::visualRect(index);
 
553
    }
 
554
 
 
555
    if (!index.isValid()) {
 
556
        return QRect();
 
557
    }
 
558
 
 
559
    const QString category = d->categoryForIndex(index);
 
560
 
 
561
    if (!d->blocks.contains(category)) {
 
562
        return QRect();
 
563
    }
 
564
 
 
565
    Private::Block &block = d->blocks[category];
 
566
    const int firstIndexRow = block.firstIndex.row();
 
567
 
 
568
    Q_ASSERT(block.firstIndex.isValid());
 
569
 
 
570
    if (index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count()) {
 
571
        return QRect();
 
572
    }
 
573
 
 
574
    const QPoint blockPos = d->blockPosition(category);
 
575
 
 
576
    Private::Item &ritem = block.items[index.row() - firstIndexRow];
 
577
 
 
578
    if (ritem.topLeft.isNull() || (block.quarantineStart.isValid() &&
 
579
                                   index.row() >= block.quarantineStart.row())) {
 
580
        if (flow() == LeftToRight) {
 
581
            d->leftToRightVisualRect(index, ritem, block, blockPos);
 
582
        } else {
 
583
            d->topToBottomVisualRect(index, ritem, block, blockPos);
 
584
        }
 
585
 
 
586
        //BEGIN: update the quarantine start
 
587
        const bool wasLastIndex = (index.row() == (block.firstIndex.row() + block.items.count() - 1));
 
588
        if (index.row() == block.quarantineStart.row()) {
 
589
            if (wasLastIndex) {
 
590
                block.quarantineStart = QModelIndex();
 
591
            } else {
 
592
                const QModelIndex nextIndex = d->proxyModel->index(index.row() + 1, modelColumn(), rootIndex());
 
593
                block.quarantineStart = nextIndex;
 
594
            }
 
595
        }
 
596
        //END: update the quarantine start
 
597
    }
 
598
 
 
599
    // we get now the absolute position through the relative position of the parent block. do not
 
600
    // save this on ritem, since this would override the item relative position in block terms.
 
601
    Private::Item item(ritem);
 
602
    item.topLeft.ry() += blockPos.y();
 
603
 
 
604
    const QSize sizeHint = item.size;
 
605
 
 
606
    if (d->hasGrid()) {
 
607
        const QSize sizeGrid = gridSize();
 
608
        const QSize resultingSize = sizeHint.boundedTo(sizeGrid);
 
609
        QRect res(item.topLeft.x() + ((sizeGrid.width() - resultingSize.width()) / 2),
 
610
                  item.topLeft.y(), resultingSize.width(), resultingSize.height());
 
611
        if (block.collapsed) {
 
612
            // we can still do binary search, while we "hide" items. We move those items in collapsed
 
613
            // blocks to the left and set a 0 height.
 
614
            res.setLeft(-resultingSize.width());
 
615
            res.setHeight(0);
 
616
        }
 
617
        return d->mapToViewport(res);
 
618
    }
 
619
 
 
620
    QRect res(item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height());
 
621
    if (block.collapsed) {
 
622
        // we can still do binary search, while we "hide" items. We move those items in collapsed
 
623
        // blocks to the left and set a 0 height.
 
624
        res.setLeft(-sizeHint.width());
 
625
        res.setHeight(0);
 
626
    }
 
627
    return d->mapToViewport(res);
 
628
}
 
629
 
 
630
KCategoryDrawer *KCategorizedView::categoryDrawer() const
 
631
{
 
632
    return d->categoryDrawer;
 
633
}
 
634
 
 
635
void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer)
 
636
{
 
637
    if (d->categoryDrawer) {
 
638
        disconnect(d->categoryDrawer, SIGNAL(collapseOrExpandClicked(QModelIndex)),
 
639
                   this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
 
640
    }
 
641
 
 
642
    d->categoryDrawer = categoryDrawer;
 
643
 
 
644
    connect(d->categoryDrawer, SIGNAL(collapseOrExpandClicked(QModelIndex)),
 
645
            this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
 
646
}
 
647
 
 
648
int KCategorizedView::categorySpacing() const
 
649
{
 
650
    return d->categorySpacing;
 
651
}
 
652
 
 
653
void KCategorizedView::setCategorySpacing(int categorySpacing)
 
654
{
 
655
    if (d->categorySpacing == categorySpacing) {
 
656
        return;
 
657
    }
 
658
 
 
659
    d->categorySpacing = categorySpacing;
 
660
 
 
661
    for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
 
662
        Private::Block &block = *it;
 
663
        block.outOfQuarantine = false;
 
664
    }
 
665
}
 
666
 
 
667
bool KCategorizedView::alternatingBlockColors() const
 
668
{
 
669
    return d->alternatingBlockColors;
 
670
}
 
671
 
 
672
void KCategorizedView::setAlternatingBlockColors(bool enable)
 
673
{
 
674
    d->alternatingBlockColors = enable;
 
675
}
 
676
 
 
677
bool KCategorizedView::collapsibleBlocks() const
 
678
{
 
679
    return d->collapsibleBlocks;
 
680
}
 
681
 
 
682
void KCategorizedView::setCollapsibleBlocks(bool enable)
 
683
{
 
684
    d->collapsibleBlocks = enable;
 
685
}
 
686
 
 
687
QModelIndexList KCategorizedView::block(const QString &category)
 
688
{
 
689
    QModelIndexList res;
 
690
    const Private::Block &block = d->blocks[category];
 
691
    if (block.height == -1) {
 
692
        return res;
 
693
    }
 
694
    QModelIndex current = block.firstIndex;
 
695
    const int first = current.row();
 
696
    for (int i = 1; i <= block.items.count(); ++i) {
 
697
        if (current.isValid()) {
 
698
            res << current;
 
699
        }
 
700
        current = d->proxyModel->index(first + i, modelColumn(), rootIndex());
 
701
    }
 
702
    return res;
 
703
}
 
704
 
 
705
QModelIndexList KCategorizedView::block(const QModelIndex &representative)
 
706
{
 
707
    return block(representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString());
 
708
}
 
709
 
 
710
QModelIndex KCategorizedView::indexAt(const QPoint &point) const
 
711
{
 
712
    if (!d->isCategorized()) {
 
713
        return QListView::indexAt(point);
 
714
    }
 
715
 
 
716
    const int rowCount = d->proxyModel->rowCount();
 
717
    if (!rowCount) {
 
718
        return QModelIndex();
 
719
    }
 
720
 
 
721
    // Binary search that will try to spot if there is an index under point
 
722
    int bottom = 0;
 
723
    int top = rowCount - 1;
 
724
    while (bottom <= top) {
 
725
        const int middle = (bottom + top) / 2;
 
726
        const QModelIndex index = d->proxyModel->index(middle, modelColumn(), rootIndex());
 
727
        QRect rect = visualRect(index);
 
728
        const int verticalOff = verticalOffset();
 
729
        int horizontalOff = horizontalOffset();
 
730
        if (layoutDirection() == Qt::RightToLeft) {
 
731
            horizontalOff *= -1;
 
732
        }
 
733
        rect.topLeft().ry() += verticalOff;
 
734
        rect.topLeft().rx() += horizontalOff;
 
735
        rect.bottomRight().ry() += verticalOff;
 
736
        rect.bottomRight().rx() += horizontalOff;
 
737
        if (rect.contains(point)) {
 
738
            if (index.model()->flags(index) & Qt::ItemIsEnabled) {
 
739
                return index;
 
740
            }
 
741
            return QModelIndex();
 
742
        }
 
743
        bool directionCondition;
 
744
        if (layoutDirection() == Qt::LeftToRight) {
 
745
            directionCondition = point.x() > rect.bottomRight().x();
 
746
        } else {
 
747
            directionCondition = point.x() < rect.bottomLeft().x();
 
748
        }
 
749
        if (point.y() > rect.bottomRight().y() ||
 
750
                (point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition)) {
 
751
            bottom = middle + 1;
 
752
        } else {
 
753
            top = middle - 1;
 
754
        }
 
755
    }
 
756
    return QModelIndex();
 
757
}
 
758
 
 
759
void KCategorizedView::reset()
 
760
{
 
761
    d->blocks.clear();
 
762
    QListView::reset();
 
763
}
 
764
 
 
765
void KCategorizedView::paintEvent(QPaintEvent *event)
 
766
{
 
767
    if (!d->isCategorized()) {
 
768
        QListView::paintEvent(event);
 
769
        return;
 
770
    }
 
771
 
 
772
    const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(viewport()->rect().intersected(event->rect()));
 
773
 
 
774
    QPainter p(viewport());
 
775
    p.save();
 
776
 
 
777
    Q_ASSERT(selectionModel()->model() == d->proxyModel);
 
778
 
 
779
    //BEGIN: draw categories
 
780
    QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
 
781
    while (it != d->blocks.constEnd()) {
 
782
        const Private::Block &block = *it;
 
783
        const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
784
        QStyleOptionViewItemV4 option(viewOptions());
 
785
        option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate
 
786
                           : QStyleOptionViewItemV4::None;
 
787
        option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open
 
788
                        : QStyle::State_None;
 
789
        const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
 
790
        QPoint pos = d->blockPosition(it.key());
 
791
        pos.ry() -= height;
 
792
        option.rect.setTopLeft(pos);
 
793
        option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
 
794
        option.rect.setHeight(height + d->blockHeight(it.key()));
 
795
        option.rect = d->mapToViewport(option.rect);
 
796
        if (!option.rect.intersects(viewport()->rect())) {
 
797
            ++it;
 
798
            continue;
 
799
        }
 
800
        d->categoryDrawer->drawCategory(categoryIndex, d->proxyModel->sortRole(), option, &p);
 
801
        ++it;
 
802
    }
 
803
    //END: draw categories
 
804
 
 
805
    if (intersecting.first.isValid() && intersecting.second.isValid()) {
 
806
        //BEGIN: draw items
 
807
        int i = intersecting.first.row();
 
808
        int indexToCheckIfBlockCollapsed = i;
 
809
        QModelIndex categoryIndex;
 
810
        QString category;
 
811
        Private::Block *block = 0;
 
812
        while (i <= intersecting.second.row()) {
 
813
            //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting
 
814
            if (i == indexToCheckIfBlockCollapsed) {
 
815
                categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
 
816
                category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
 
817
                block = &d->blocks[category];
 
818
                indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count();
 
819
                if (block->collapsed) {
 
820
                    i = indexToCheckIfBlockCollapsed;
 
821
                    continue;
 
822
                }
 
823
            }
 
824
            //END: first check if the block is collapsed. if so, we have to skip the item painting
 
825
 
 
826
            Q_ASSERT(block);
 
827
 
 
828
            const bool alternateItem = (i - block->firstIndex.row()) % 2;
 
829
 
 
830
            const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
 
831
            const Qt::ItemFlags flags = d->proxyModel->flags(index);
 
832
            QStyleOptionViewItemV4 option(viewOptions());
 
833
            option.rect = visualRect(index);
 
834
            option.widget = this;
 
835
            option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText
 
836
                               : QStyleOptionViewItemV2::None;
 
837
            option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate
 
838
                               : QStyleOptionViewItemV4::None;
 
839
            if (flags & Qt::ItemIsSelectable) {
 
840
                option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected
 
841
                                : QStyle::State_None;
 
842
            } else {
 
843
                option.state &= ~QStyle::State_Selected;
 
844
            }
 
845
            option.state |= (index == currentIndex()) ? QStyle::State_HasFocus
 
846
                            : QStyle::State_None;
 
847
            if (!(flags & Qt::ItemIsEnabled)) {
 
848
                option.state &= ~QStyle::State_Enabled;
 
849
            } else {
 
850
                option.state |= (index == d->hoveredIndex) ? QStyle::State_MouseOver
 
851
                                : QStyle::State_None;
 
852
            }
 
853
 
 
854
            itemDelegate(index)->paint(&p, option, index);
 
855
            ++i;
 
856
        }
 
857
        //END: draw items
 
858
    }
 
859
 
 
860
    //BEGIN: draw selection rect
 
861
    if (isSelectionRectVisible() && d->rubberBandRect.isValid()) {
 
862
        QStyleOptionRubberBand opt;
 
863
        opt.initFrom(this);
 
864
        opt.shape = QRubberBand::Rectangle;
 
865
        opt.opaque = false;
 
866
        opt.rect = d->mapToViewport(d->rubberBandRect).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
 
867
        p.save();
 
868
        style()->drawControl(QStyle::CE_RubberBand, &opt, &p);
 
869
        p.restore();
 
870
    }
 
871
    //END: draw selection rect
 
872
 
 
873
    p.restore();
 
874
}
 
875
 
 
876
void KCategorizedView::resizeEvent(QResizeEvent *event)
 
877
{
 
878
    d->regenerateAllElements();
 
879
    QListView::resizeEvent(event);
 
880
}
 
881
 
 
882
void KCategorizedView::setSelection(const QRect &rect,
 
883
                                    QItemSelectionModel::SelectionFlags flags)
 
884
{
 
885
    if (!d->isCategorized()) {
 
886
        QListView::setSelection(rect, flags);
 
887
        return;
 
888
    }
 
889
 
 
890
    if (rect.topLeft() == rect.bottomRight()) {
 
891
        const QModelIndex index = indexAt(rect.topLeft());
 
892
        selectionModel()->select(index, flags);
 
893
        return;
 
894
    }
 
895
 
 
896
    const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(rect);
 
897
 
 
898
    QItemSelection selection;
 
899
 
 
900
    //TODO: think of a faster implementation
 
901
    QModelIndex firstIndex;
 
902
    QModelIndex lastIndex;
 
903
    for (int i = intersecting.first.row(); i <= intersecting.second.row(); ++i) {
 
904
        const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
 
905
        const bool visualRectIntersects = visualRect(index).intersects(rect);
 
906
        if (firstIndex.isValid()) {
 
907
            if (visualRectIntersects) {
 
908
                lastIndex = index;
 
909
            } else {
 
910
                selection << QItemSelectionRange(firstIndex, lastIndex);
 
911
                firstIndex = QModelIndex();
 
912
            }
 
913
        } else if (visualRectIntersects) {
 
914
            firstIndex = index;
 
915
            lastIndex = index;
 
916
        }
 
917
    }
 
918
 
 
919
    if (firstIndex.isValid()) {
 
920
        selection << QItemSelectionRange(firstIndex, lastIndex);
 
921
    }
 
922
 
 
923
    selectionModel()->select(selection, flags);
 
924
}
 
925
 
 
926
void KCategorizedView::mouseMoveEvent(QMouseEvent *event)
 
927
{
 
928
    QListView::mouseMoveEvent(event);
 
929
    d->hoveredIndex = indexAt(event->pos());
 
930
    const SelectionMode itemViewSelectionMode = selectionMode();
 
931
    if (state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection
 
932
            && itemViewSelectionMode != NoSelection) {
 
933
        QRect rect(d->pressedPosition, event->pos() + QPoint(horizontalOffset(), verticalOffset()));
 
934
        rect = rect.normalized();
 
935
        update(rect.united(d->rubberBandRect));
 
936
        d->rubberBandRect = rect;
 
937
    }
 
938
    if (!d->categoryDrawer) {
 
939
        return;
 
940
    }
 
941
    QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
 
942
    while (it != d->blocks.constEnd()) {
 
943
        const Private::Block &block = *it;
 
944
        const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
945
        QStyleOptionViewItemV4 option(viewOptions());
 
946
        const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
 
947
        QPoint pos = d->blockPosition(it.key());
 
948
        pos.ry() -= height;
 
949
        option.rect.setTopLeft(pos);
 
950
        option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
 
951
        option.rect.setHeight(height + d->blockHeight(it.key()));
 
952
        option.rect = d->mapToViewport(option.rect);
 
953
        const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
 
954
        if (option.rect.contains(mousePos)) {
 
955
            if (d->hoveredBlock->height != -1 && *d->hoveredBlock != block) {
 
956
                const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
957
                const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
 
958
                d->categoryDrawer->mouseLeft(categoryIndex, option.rect);
 
959
                *d->hoveredBlock = block;
 
960
                d->hoveredCategory = it.key();
 
961
                viewport()->update(option.rect);
 
962
            } else if (d->hoveredBlock->height == -1) {
 
963
                *d->hoveredBlock = block;
 
964
                d->hoveredCategory = it.key();
 
965
            } else {
 
966
                d->categoryDrawer->mouseMoved(categoryIndex, option.rect, event);
 
967
            }
 
968
            viewport()->update(option.rect);
 
969
            return;
 
970
        }
 
971
        ++it;
 
972
    }
 
973
    if (d->hoveredBlock->height != -1) {
 
974
        const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
975
        const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
 
976
        d->categoryDrawer->mouseLeft(categoryIndex, option.rect);
 
977
        *d->hoveredBlock = Private::Block();
 
978
        d->hoveredCategory = QString();
 
979
        viewport()->update(option.rect);
 
980
    }
 
981
}
 
982
 
 
983
void KCategorizedView::mousePressEvent(QMouseEvent *event)
 
984
{
 
985
    if (event->button() == Qt::LeftButton) {
 
986
        d->pressedPosition = event->pos();
 
987
        d->pressedPosition.rx() += horizontalOffset();
 
988
        d->pressedPosition.ry() += verticalOffset();
 
989
    }
 
990
    if (!d->categoryDrawer) {
 
991
        QListView::mousePressEvent(event);
 
992
        return;
 
993
    }
 
994
    QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
 
995
    while (it != d->blocks.constEnd()) {
 
996
        const Private::Block &block = *it;
 
997
        const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
998
        const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
 
999
        const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
 
1000
        if (option.rect.contains(mousePos)) {
 
1001
            d->categoryDrawer->mouseButtonPressed(categoryIndex, option.rect, event);
 
1002
            viewport()->update(option.rect);
 
1003
            if (!event->isAccepted()) {
 
1004
                QListView::mousePressEvent(event);
 
1005
            }
 
1006
            return;
 
1007
        }
 
1008
        ++it;
 
1009
    }
 
1010
    QListView::mousePressEvent(event);
 
1011
}
 
1012
 
 
1013
void KCategorizedView::mouseReleaseEvent(QMouseEvent *event)
 
1014
{
 
1015
    d->pressedPosition = QPoint();
 
1016
    d->rubberBandRect = QRect();
 
1017
    if (!d->categoryDrawer) {
 
1018
        QListView::mouseReleaseEvent(event);
 
1019
        return;
 
1020
    }
 
1021
    QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
 
1022
    while (it != d->blocks.constEnd()) {
 
1023
        const Private::Block &block = *it;
 
1024
        const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
1025
        const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
 
1026
        const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
 
1027
        if (option.rect.contains(mousePos)) {
 
1028
            d->categoryDrawer->mouseButtonReleased(categoryIndex, option.rect, event);
 
1029
            viewport()->update(option.rect);
 
1030
            if (!event->isAccepted()) {
 
1031
                QListView::mouseReleaseEvent(event);
 
1032
            }
 
1033
            return;
 
1034
        }
 
1035
        ++it;
 
1036
    }
 
1037
    QListView::mouseReleaseEvent(event);
 
1038
}
 
1039
 
 
1040
void KCategorizedView::leaveEvent(QEvent *event)
 
1041
{
 
1042
    QListView::leaveEvent(event);
 
1043
    if (d->hoveredIndex.isValid()) {
 
1044
        viewport()->update(visualRect(d->hoveredIndex));
 
1045
        d->hoveredIndex = QModelIndex();
 
1046
    }
 
1047
    if (d->categoryDrawer && d->hoveredBlock->height != -1) {
 
1048
        const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
 
1049
        const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
 
1050
        d->categoryDrawer->mouseLeft(categoryIndex, option.rect);
 
1051
        *d->hoveredBlock = Private::Block();
 
1052
        d->hoveredCategory = QString();
 
1053
        viewport()->update(option.rect);
 
1054
    }
 
1055
}
 
1056
 
 
1057
void KCategorizedView::startDrag(Qt::DropActions supportedActions)
 
1058
{
 
1059
    QListView::startDrag(supportedActions);
 
1060
}
 
1061
 
 
1062
void KCategorizedView::dragMoveEvent(QDragMoveEvent *event)
 
1063
{
 
1064
    QListView::dragMoveEvent(event);
 
1065
    d->hoveredIndex = indexAt(event->pos());
 
1066
}
 
1067
 
 
1068
void KCategorizedView::dragEnterEvent(QDragEnterEvent *event)
 
1069
{
 
1070
    QListView::dragEnterEvent(event);
 
1071
}
 
1072
 
 
1073
void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event)
 
1074
{
 
1075
    QListView::dragLeaveEvent(event);
 
1076
}
 
1077
 
 
1078
void KCategorizedView::dropEvent(QDropEvent *event)
 
1079
{
 
1080
    QListView::dropEvent(event);
 
1081
}
 
1082
 
 
1083
//TODO: improve se we take into account collapsed blocks
 
1084
//TODO: take into account when there is no grid and no uniformItemSizes
 
1085
QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction,
 
1086
        Qt::KeyboardModifiers modifiers)
 
1087
{
 
1088
    if (!d->isCategorized()) {
 
1089
        return QListView::moveCursor(cursorAction, modifiers);
 
1090
    }
 
1091
 
 
1092
    const QModelIndex current = currentIndex();
 
1093
    const QRect currentRect = visualRect(current);
 
1094
    if (!current.isValid()) {
 
1095
        const int rowCount = d->proxyModel->rowCount(rootIndex());
 
1096
        if (!rowCount) {
 
1097
            return QModelIndex();
 
1098
        }
 
1099
        return d->proxyModel->index(0, modelColumn(), rootIndex());
 
1100
    }
 
1101
 
 
1102
    switch (cursorAction) {
 
1103
    case MoveLeft: {
 
1104
        if (!current.row()) {
 
1105
            return QModelIndex();
 
1106
        }
 
1107
        const QModelIndex previous = d->proxyModel->index(current.row() - 1, modelColumn(), rootIndex());
 
1108
        const QRect previousRect = visualRect(previous);
 
1109
        if (previousRect.top() == currentRect.top()) {
 
1110
            return previous;
 
1111
        }
 
1112
 
 
1113
        return QModelIndex();
 
1114
    }
 
1115
    case MoveRight: {
 
1116
        if (current.row() == d->proxyModel->rowCount() - 1) {
 
1117
            return QModelIndex();
 
1118
        }
 
1119
        const QModelIndex next = d->proxyModel->index(current.row() + 1, modelColumn(), rootIndex());
 
1120
        const QRect nextRect = visualRect(next);
 
1121
        if (nextRect.top() == currentRect.top()) {
 
1122
            return next;
 
1123
        }
 
1124
 
 
1125
        return QModelIndex();
 
1126
    }
 
1127
    case MoveDown: {
 
1128
        if (d->hasGrid() || uniformItemSizes()) {
 
1129
            const QModelIndex current = currentIndex();
 
1130
            const QSize itemSize = d->hasGrid() ? gridSize()
 
1131
                                   : sizeHintForIndex(current);
 
1132
            const Private::Block &block = d->blocks[d->categoryForIndex(current)];
 
1133
            const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
 
1134
            const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() +
 
1135
                                 block.items.count();
 
1136
 
 
1137
            if (canMove) {
 
1138
                return d->proxyModel->index(current.row() + maxItemsPerRow, modelColumn(), rootIndex());
 
1139
            }
 
1140
 
 
1141
            const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
 
1142
            const QModelIndex nextIndex = d->proxyModel->index(block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex());
 
1143
 
 
1144
            if (!nextIndex.isValid()) {
 
1145
                return QModelIndex();
 
1146
            }
 
1147
 
 
1148
            const Private::Block &nextBlock = d->blocks[d->categoryForIndex(nextIndex)];
 
1149
 
 
1150
            if (nextBlock.items.count() <= currentRelativePos) {
 
1151
                return QModelIndex();
 
1152
            }
 
1153
 
 
1154
            if (currentRelativePos < (block.items.count() % maxItemsPerRow)) {
 
1155
                return d->proxyModel->index(nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex());
 
1156
            }
 
1157
 
 
1158
            return QModelIndex();
 
1159
        }
 
1160
    }
 
1161
    case MoveUp: {
 
1162
        if (d->hasGrid() || uniformItemSizes()) {
 
1163
            const QModelIndex current = currentIndex();
 
1164
            const QSize itemSize = d->hasGrid() ? gridSize()
 
1165
                                   : sizeHintForIndex(current);
 
1166
            const Private::Block &block = d->blocks[d->categoryForIndex(current)];
 
1167
            const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
 
1168
            const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row();
 
1169
 
 
1170
            if (canMove) {
 
1171
                return d->proxyModel->index(current.row() - maxItemsPerRow, modelColumn(), rootIndex());
 
1172
            }
 
1173
 
 
1174
            const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
 
1175
            const QModelIndex prevIndex = d->proxyModel->index(block.firstIndex.row() - 1, modelColumn(), rootIndex());
 
1176
 
 
1177
            if (!prevIndex.isValid()) {
 
1178
                return QModelIndex();
 
1179
            }
 
1180
 
 
1181
            const Private::Block &prevBlock = d->blocks[d->categoryForIndex(prevIndex)];
 
1182
 
 
1183
            if (prevBlock.items.count() <= currentRelativePos) {
 
1184
                return QModelIndex();
 
1185
            }
 
1186
 
 
1187
            const int remainder = prevBlock.items.count() % maxItemsPerRow;
 
1188
            if (currentRelativePos < remainder) {
 
1189
                return d->proxyModel->index(prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex());
 
1190
            }
 
1191
 
 
1192
            return QModelIndex();
 
1193
        }
 
1194
    }
 
1195
    default:
 
1196
        break;
 
1197
    }
 
1198
 
 
1199
    return QModelIndex();
 
1200
}
 
1201
 
 
1202
void KCategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent,
 
1203
        int start,
 
1204
        int end)
 
1205
{
 
1206
    if (!d->isCategorized()) {
 
1207
        QListView::rowsAboutToBeRemoved(parent, start, end);
 
1208
        return;
 
1209
    }
 
1210
 
 
1211
    *d->hoveredBlock = Private::Block();
 
1212
    d->hoveredCategory = QString();
 
1213
 
 
1214
    if (end - start + 1 == d->proxyModel->rowCount()) {
 
1215
        d->blocks.clear();
 
1216
        QListView::rowsAboutToBeRemoved(parent, start, end);
 
1217
        return;
 
1218
    }
 
1219
 
 
1220
    // Removal feels a bit more complicated than insertion. Basically we can consider there are
 
1221
    // 3 different cases when going to remove items. (*) represents an item, Items between ([) and
 
1222
    // (]) are the ones which are marked for removal.
 
1223
    //
 
1224
    // - 1st case:
 
1225
    //              ... * * * * * * [ * * * ...
 
1226
    //
 
1227
    //   The items marked for removal are the last part of this category. No need to mark any item
 
1228
    //   of this category as in quarantine, because no special offset will be pushed to items at
 
1229
    //   the right because of any changes (since the removed items are those on the right most part
 
1230
    //   of the category).
 
1231
    //
 
1232
    // - 2nd case:
 
1233
    //              ... * * * * * * ] * * * ...
 
1234
    //
 
1235
    //   The items marked for removal are the first part of this category. We have to mark as in
 
1236
    //   quarantine all items in this category. Absolutely all. All items will have to be moved to
 
1237
    //   the left (or moving up, because rows got a different offset).
 
1238
    //
 
1239
    // - 3rd case:
 
1240
    //              ... * * [ * * * * ] * * ...
 
1241
    //
 
1242
    //   The items marked for removal are in between of this category. We have to mark as in
 
1243
    //   quarantine only those items that are at the right of the end of the removal interval,
 
1244
    //   (starting on "]").
 
1245
    //
 
1246
    // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are
 
1247
    // located under the top most affected category as in quarantine (the block itself, as a whole),
 
1248
    // because such a change can force it to have a different offset (note that items themselves
 
1249
    // contain relative positions to the block, so marking the block as in quarantine is enough).
 
1250
    //
 
1251
    // Also note that removal implicitly means that we have to update correctly firstIndex of each
 
1252
    // block, and in general keep updated the internal information of elements.
 
1253
 
 
1254
    QStringList listOfCategoriesMarkedForRemoval;
 
1255
 
 
1256
    QString lastCategory;
 
1257
    int alreadyRemoved = 0;
 
1258
    for (int i = start; i <= end; ++i) {
 
1259
        const QModelIndex index = d->proxyModel->index(i, modelColumn(), parent);
 
1260
 
 
1261
        Q_ASSERT(index.isValid());
 
1262
 
 
1263
        const QString category = d->categoryForIndex(index);
 
1264
 
 
1265
        if (lastCategory != category) {
 
1266
            lastCategory = category;
 
1267
            alreadyRemoved = 0;
 
1268
        }
 
1269
 
 
1270
        Private::Block &block = d->blocks[category];
 
1271
        block.items.removeAt(i - block.firstIndex.row() - alreadyRemoved);
 
1272
        ++alreadyRemoved;
 
1273
 
 
1274
        if (!block.items.count()) {
 
1275
            listOfCategoriesMarkedForRemoval << category;
 
1276
        }
 
1277
 
 
1278
        block.height = -1;
 
1279
 
 
1280
        viewport()->update();
 
1281
    }
 
1282
 
 
1283
    //BEGIN: update the items that are in quarantine in affected categories
 
1284
    {
 
1285
        const QModelIndex lastIndex = d->proxyModel->index(end, modelColumn(), parent);
 
1286
        const QString category = d->categoryForIndex(lastIndex);
 
1287
        Private::Block &block = d->blocks[category];
 
1288
        if (block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row()) {
 
1289
            block.firstIndex = d->proxyModel->index(end + 1, modelColumn(), parent);
 
1290
        }
 
1291
        block.quarantineStart = block.firstIndex;
 
1292
    }
 
1293
    //END: update the items that are in quarantine in affected categories
 
1294
 
 
1295
    Q_FOREACH (const QString &category, listOfCategoriesMarkedForRemoval) {
 
1296
        d->blocks.remove(category);
 
1297
    }
 
1298
 
 
1299
    //BEGIN: mark as in quarantine those categories that are under the affected ones
 
1300
    {
 
1301
        //BEGIN: order for marking as alternate those blocks that are alternate
 
1302
        QList<Private::Block> blockList = d->blocks.values();
 
1303
        qSort(blockList.begin(), blockList.end(), Private::Block::lessThan);
 
1304
        QList<int> firstIndexesRows;
 
1305
        foreach (const Private::Block &block, blockList) {
 
1306
            firstIndexesRows << block.firstIndex.row();
 
1307
        }
 
1308
        //END: order for marking as alternate those blocks that are alternate
 
1309
        for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
 
1310
            Private::Block &block = *it;
 
1311
            if (block.firstIndex.row() > start) {
 
1312
                block.outOfQuarantine = false;
 
1313
                block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
 
1314
            } else if (block.firstIndex.row() == start) {
 
1315
                block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
 
1316
            }
 
1317
        }
 
1318
    }
 
1319
    //END: mark as in quarantine those categories that are under the affected ones
 
1320
 
 
1321
    QListView::rowsAboutToBeRemoved(parent, start, end);
 
1322
}
 
1323
 
 
1324
void KCategorizedView::updateGeometries()
 
1325
{
 
1326
    const int oldVerticalOffset = verticalOffset();
 
1327
    const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy();
 
1328
 
 
1329
    //BEGIN bugs 213068, 287847 ------------------------------------------------------------
 
1330
    /*
 
1331
     * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not
 
1332
     * and triggers a (sometimes additionally timered) resize through ::layoutChildren()
 
1333
     * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499
 
1334
     * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy)
 
1335
     *
 
1336
     * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI
 
1337
     * So we prevent QListView from having an own opinion on the scrollbar visibility by
 
1338
     * fixing it before calling the baseclass QListView::updateGeometries()
 
1339
     *
 
1340
     * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded
 
1341
     * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself.
 
1342
     */
 
1343
    if (d->isCategorized()) { // important! - otherwise we'd pollute the setting if the view is initially not categorized
 
1344
        setVerticalScrollBarPolicy((verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo(this)) ?
 
1345
                                   Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
 
1346
        setHorizontalScrollBarPolicy((horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo(this)) ?
 
1347
                                     Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
 
1348
    }
 
1349
    //END bugs 213068, 287847 --------------------------------------------------------------
 
1350
 
 
1351
    QListView::updateGeometries();
 
1352
 
 
1353
    if (!d->isCategorized()) {
 
1354
        return;
 
1355
    }
 
1356
 
 
1357
    const int rowCount = d->proxyModel->rowCount();
 
1358
    if (!rowCount) {
 
1359
        verticalScrollBar()->setRange(0, 0);
 
1360
        // unconditional, see function end todo
 
1361
        horizontalScrollBar()->setRange(0, 0);
 
1362
        return;
 
1363
    }
 
1364
 
 
1365
    const QModelIndex lastIndex = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex());
 
1366
    Q_ASSERT(lastIndex.isValid());
 
1367
    QRect lastItemRect = visualRect(lastIndex);
 
1368
 
 
1369
    if (d->hasGrid()) {
 
1370
        lastItemRect.setSize(lastItemRect.size().expandedTo(gridSize()));
 
1371
    } else {
 
1372
        if (uniformItemSizes()) {
 
1373
            QSize itemSize = sizeHintForIndex(lastIndex);
 
1374
            itemSize.setHeight(itemSize.height() + spacing());
 
1375
            lastItemRect.setSize(itemSize);
 
1376
        } else {
 
1377
            QSize itemSize = sizeHintForIndex(lastIndex);
 
1378
            const QString category = d->categoryForIndex(lastIndex);
 
1379
            itemSize.setHeight(d->highestElementInLastRow(d->blocks[category]) + spacing());
 
1380
            lastItemRect.setSize(itemSize);
 
1381
        }
 
1382
    }
 
1383
 
 
1384
    const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height();
 
1385
 
 
1386
    if (verticalScrollMode() == ScrollPerItem) {
 
1387
        verticalScrollBar()->setSingleStep(lastItemRect.height());
 
1388
        const int rowsPerPage = qMax(viewport()->height() / lastItemRect.height(), 1);
 
1389
        verticalScrollBar()->setPageStep(rowsPerPage * lastItemRect.height());
 
1390
    }
 
1391
 
 
1392
    verticalScrollBar()->setRange(0, bottomRange);
 
1393
    verticalScrollBar()->setValue(oldVerticalOffset);
 
1394
 
 
1395
    //TODO: also consider working with the horizontal scroll bar. since at this level I am not still
 
1396
    //      supporting "top to bottom" flow, there is no real problem. If I support that someday
 
1397
    //      (think how to draw categories), we would have to take care of the horizontal scroll bar too.
 
1398
    //      In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar.
 
1399
    horizontalScrollBar()->setRange(0, 0);
 
1400
 
 
1401
    //BEGIN bugs 213068, 287847 ------------------------------------------------------------
 
1402
    // restoring values from above ...
 
1403
    setVerticalScrollBarPolicy(verticalP);
 
1404
    setHorizontalScrollBarPolicy(horizontalP);
 
1405
    // ... and correct the visibility
 
1406
    bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum();
 
1407
    if (verticalP == Qt::ScrollBarAsNeeded && (verticalScrollBar()->isVisibleTo(this) != validRange)) {
 
1408
        verticalScrollBar()->setVisible(validRange);
 
1409
    }
 
1410
    validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum();
 
1411
    if (horizontalP == Qt::ScrollBarAsNeeded && (horizontalScrollBar()->isVisibleTo(this) != validRange)) {
 
1412
        horizontalScrollBar()->setVisible(validRange);
 
1413
    }
 
1414
    //END bugs 213068, 287847 --------------------------------------------------------------
 
1415
}
 
1416
 
 
1417
void KCategorizedView::currentChanged(const QModelIndex &current,
 
1418
                                      const QModelIndex &previous)
 
1419
{
 
1420
    QListView::currentChanged(current, previous);
 
1421
}
 
1422
 
 
1423
void KCategorizedView::dataChanged(const QModelIndex &topLeft,
 
1424
                                   const QModelIndex &bottomRight,
 
1425
                                   const QVector<int> &roles)
 
1426
{
 
1427
    QListView::dataChanged(topLeft, bottomRight, roles);
 
1428
    if (!d->isCategorized()) {
 
1429
        return;
 
1430
    }
 
1431
 
 
1432
    *d->hoveredBlock = Private::Block();
 
1433
    d->hoveredCategory = QString();
 
1434
 
 
1435
    //BEGIN: since the model changed data, we need to reconsider item sizes
 
1436
    int i = topLeft.row();
 
1437
    int indexToCheck = i;
 
1438
    QModelIndex categoryIndex;
 
1439
    QString category;
 
1440
    Private::Block *block;
 
1441
    while (i <= bottomRight.row()) {
 
1442
        const QModelIndex currIndex = d->proxyModel->index(i, modelColumn(), rootIndex());
 
1443
        if (i == indexToCheck) {
 
1444
            categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
 
1445
            category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
 
1446
            block = &d->blocks[category];
 
1447
            block->quarantineStart = currIndex;
 
1448
            indexToCheck = block->firstIndex.row() + block->items.count();
 
1449
        }
 
1450
        visualRect(currIndex);
 
1451
        ++i;
 
1452
    }
 
1453
    //END: since the model changed data, we need to reconsider item sizes
 
1454
}
 
1455
 
 
1456
void KCategorizedView::rowsInserted(const QModelIndex &parent,
 
1457
                                    int start,
 
1458
                                    int end)
 
1459
{
 
1460
    QListView::rowsInserted(parent, start, end);
 
1461
    if (!d->isCategorized()) {
 
1462
        return;
 
1463
    }
 
1464
 
 
1465
    *d->hoveredBlock = Private::Block();
 
1466
    d->hoveredCategory = QString();
 
1467
    d->rowsInserted(parent, start, end);
 
1468
}
 
1469
 
 
1470
#ifndef KDE_NO_DEPRECATED
 
1471
void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent,
 
1472
        int start,
 
1473
        int end)
 
1474
{
 
1475
    Q_UNUSED(parent);
 
1476
    Q_UNUSED(start);
 
1477
    Q_UNUSED(end);
 
1478
}
 
1479
#endif
 
1480
 
 
1481
#ifndef KDE_NO_DEPRECATED
 
1482
void KCategorizedView::rowsRemoved(const QModelIndex &parent,
 
1483
                                   int start,
 
1484
                                   int end)
 
1485
{
 
1486
    Q_UNUSED(parent);
 
1487
    Q_UNUSED(start);
 
1488
    Q_UNUSED(end);
 
1489
}
 
1490
#endif
 
1491
 
 
1492
void KCategorizedView::slotLayoutChanged()
 
1493
{
 
1494
    if (!d->isCategorized()) {
 
1495
        return;
 
1496
    }
 
1497
 
 
1498
    d->blocks.clear();
 
1499
    *d->hoveredBlock = Private::Block();
 
1500
    d->hoveredCategory = QString();
 
1501
    if (d->proxyModel->rowCount()) {
 
1502
        d->rowsInserted(rootIndex(), 0, d->proxyModel->rowCount() - 1);
 
1503
    }
 
1504
}
 
1505
 
 
1506
//END: Public part
 
1507
 
 
1508
#include "moc_kcategorizedview.cpp"