]> cloud.milkyroute.net Git - dolphin.git/blob - src/klistview.cpp
Define variables in their context
[dolphin.git] / src / klistview.cpp
1 /**
2 * This file is part of the KDE project
3 * Copyright (C) 2007 Rafael Fernández López <ereslibre@gmail.com>
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 #include "klistview.h"
22 #include "klistview_p.h"
23
24 #include <math.h> // trunc on C99 compliant systems
25 #include <kdefakes.h> // trunc for not C99 compliant systems
26
27 #include <QApplication>
28 #include <QPainter>
29 #include <QScrollBar>
30 #include <QPaintEvent>
31
32 #include <kdebug.h>
33 #include <kstyle.h>
34
35 #include "kitemcategorizer.h"
36 #include "ksortfilterproxymodel.h"
37
38 class LessThan
39 {
40 public:
41 enum Purpose
42 {
43 GeneralPurpose = 0,
44 CategoryPurpose
45 };
46
47 inline LessThan(const KSortFilterProxyModel *proxyModel,
48 Purpose purpose)
49 : proxyModel(proxyModel)
50 , purpose(purpose)
51 {
52 }
53
54 inline bool operator()(const QModelIndex &left,
55 const QModelIndex &right) const
56 {
57 if (purpose == GeneralPurpose)
58 {
59 return proxyModel->sortOrder() == Qt::AscendingOrder ?
60 proxyModel->lessThanGeneralPurpose(left, right) :
61 !proxyModel->lessThanGeneralPurpose(left, right);
62 }
63
64 return proxyModel->sortOrder() == Qt::AscendingOrder ?
65 proxyModel->lessThanCategoryPurpose(left, right) :
66 !proxyModel->lessThanCategoryPurpose(left, right);
67 }
68
69 private:
70 const KSortFilterProxyModel *proxyModel;
71 const Purpose purpose;
72 };
73
74
75 //==============================================================================
76
77
78 KListView::Private::Private(KListView *listView)
79 : listView(listView)
80 , itemCategorizer(0)
81 , mouseButtonPressed(false)
82 , isDragging(false)
83 , dragLeftViewport(false)
84 , proxyModel(0)
85 , lastIndex(QModelIndex())
86 {
87 }
88
89 KListView::Private::~Private()
90 {
91 }
92
93 const QModelIndexList &KListView::Private::intersectionSet(const QRect &rect)
94 {
95 QModelIndex index;
96 QRect indexVisualRect;
97
98 intersectedIndexes.clear();
99
100 // Lets find out where we should start
101 int top = proxyModel->rowCount() - 1;
102 int bottom = 0;
103 int middle = (top + bottom) / 2;
104 while (bottom <= top)
105 {
106 middle = (top + bottom) / 2;
107
108 index = elementDictionary[proxyModel->index(middle, 0)];
109 indexVisualRect = visualRect(index);
110
111 if (qMax(indexVisualRect.topLeft().y(),
112 indexVisualRect.bottomRight().y()) < qMin(rect.topLeft().y(),
113 rect.bottomRight().y()))
114 {
115 bottom = middle + 1;
116 }
117 else
118 {
119 top = middle - 1;
120 }
121 }
122
123 for (int i = middle; i < proxyModel->rowCount(); i++)
124 {
125 index = elementDictionary[proxyModel->index(i, 0)];
126 indexVisualRect = visualRect(index);
127
128 if (rect.intersects(indexVisualRect))
129 intersectedIndexes.append(index);
130
131 // If we passed next item, stop searching for hits
132 if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
133 qMin(indexVisualRect.topLeft().y(),
134 indexVisualRect.bottomRight().y()))
135 break;
136 }
137
138 return intersectedIndexes;
139 }
140
141 QRect KListView::Private::visualRectInViewport(const QModelIndex &index) const
142 {
143 if (!index.isValid())
144 return QRect();
145
146 QString curCategory = elementsInfo[index].category;
147
148 QRect retRect(listView->spacing(), listView->spacing() * 2 +
149 30 /* categoryHeight */, 0, 0);
150
151 int viewportWidth = listView->viewport()->width() - listView->spacing();
152
153 // We really need all items to be of same size. Otherwise we cannot do this
154 // (ereslibre)
155 // QSize itemSize =
156 // listView->sizeHintForIndex(proxyModel->mapFromSource(index));
157 // int itemHeight = itemSize.height();
158 // int itemWidth = itemSize.width();*/
159 int itemHeight = 107;
160 int itemWidth = 130;
161 int itemWidthPlusSeparation = listView->spacing() + itemWidth;
162 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
163 if (!elementsPerRow)
164 elementsPerRow++;
165
166 int column = elementsInfo[index].relativeOffsetToCategory % elementsPerRow;
167 int row = elementsInfo[index].relativeOffsetToCategory / elementsPerRow;
168
169 retRect.setLeft(retRect.left() + column * listView->spacing() +
170 column * itemWidth);
171
172 foreach (const QString &category, categories)
173 {
174 if (category == curCategory)
175 break;
176
177 float rows = (float) ((float) categoriesIndexes[category].count() /
178 (float) elementsPerRow);
179 int rowsInt = categoriesIndexes[category].count() / elementsPerRow;
180
181 if (rows - trunc(rows)) rowsInt++;
182
183 retRect.setTop(retRect.top() +
184 (rowsInt * listView->spacing()) +
185 (rowsInt * itemHeight) +
186 30 /* categoryHeight */ +
187 listView->spacing() * 2);
188 }
189
190 retRect.setTop(retRect.top() + row * listView->spacing() +
191 row * itemHeight);
192
193 retRect.setWidth(itemWidth);
194 retRect.setHeight(itemHeight);
195
196 return retRect;
197 }
198
199 QRect KListView::Private::visualCategoryRectInViewport(const QString &category)
200 const
201 {
202 QRect retRect(listView->spacing(),
203 listView->spacing(),
204 listView->viewport()->width() - listView->spacing() * 2,
205 0);
206
207 if (!proxyModel->rowCount() || !categories.contains(category))
208 return QRect();
209
210 QModelIndex index = proxyModel->index(0, 0, QModelIndex());
211
212 int viewportWidth = listView->viewport()->width() - listView->spacing();
213
214 // We really need all items to be of same size. Otherwise we cannot do this
215 // (ereslibre)
216 // QSize itemSize = listView->sizeHintForIndex(index);
217 // int itemHeight = itemSize.height();
218 // int itemWidth = itemSize.width();
219 int itemHeight = 107;
220 int itemWidth = 130;
221 int itemWidthPlusSeparation = listView->spacing() + itemWidth;
222 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
223
224 if (!elementsPerRow)
225 elementsPerRow++;
226
227 foreach (const QString &itCategory, categories)
228 {
229 if (itCategory == category)
230 break;
231
232 float rows = (float) ((float) categoriesIndexes[itCategory].count() /
233 (float) elementsPerRow);
234 int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
235
236 if (rows - trunc(rows)) rowsInt++;
237
238 retRect.setTop(retRect.top() +
239 (rowsInt * listView->spacing()) +
240 (rowsInt * itemHeight) +
241 30 /* categoryHeight */ +
242 listView->spacing() * 2);
243 }
244
245 retRect.setHeight(30 /* categoryHeight */);
246
247 return retRect;
248 }
249
250 // We're sure elementsPosition doesn't contain index
251 const QRect &KListView::Private::cacheIndex(const QModelIndex &index)
252 {
253 QRect rect = visualRectInViewport(index);
254 elementsPosition[index] = rect;
255
256 return elementsPosition[index];
257 }
258
259 // We're sure categoriesPosition doesn't contain category
260 const QRect &KListView::Private::cacheCategory(const QString &category)
261 {
262 QRect rect = visualCategoryRectInViewport(category);
263 categoriesPosition[category] = rect;
264
265 return categoriesPosition[category];
266 }
267
268 const QRect &KListView::Private::cachedRectIndex(const QModelIndex &index)
269 {
270 if (elementsPosition.contains(index)) // If we have it cached
271 { // return it
272 return elementsPosition[index];
273 }
274 else // Otherwise, cache it
275 { // and return it
276 return cacheIndex(index);
277 }
278 }
279
280 const QRect &KListView::Private::cachedRectCategory(const QString &category)
281 {
282 if (categoriesPosition.contains(category)) // If we have it cached
283 { // return it
284 return categoriesPosition[category];
285 }
286 else // Otherwise, cache it and
287 { // return it
288 return cacheCategory(category);
289 }
290 }
291
292 QRect KListView::Private::visualRect(const QModelIndex &index)
293 {
294 QModelIndex mappedIndex = proxyModel->mapToSource(index);
295
296 QRect retRect = cachedRectIndex(mappedIndex);
297 int dx = -listView->horizontalOffset();
298 int dy = -listView->verticalOffset();
299 retRect.adjust(dx, dy, dx, dy);
300
301 return retRect;
302 }
303
304 QRect KListView::Private::categoryVisualRect(const QString &category)
305 {
306 QRect retRect = cachedRectCategory(category);
307 int dx = -listView->horizontalOffset();
308 int dy = -listView->verticalOffset();
309 retRect.adjust(dx, dy, dx, dy);
310
311 return retRect;
312 }
313
314 void KListView::Private::drawNewCategory(const QString &category,
315 const QStyleOption &option,
316 QPainter *painter)
317 {
318 QColor color = option.palette.color(QPalette::Text);
319
320 painter->save();
321 painter->setRenderHint(QPainter::Antialiasing);
322
323 QStyleOptionButton opt;
324
325 opt.rect = option.rect;
326 opt.palette = option.palette;
327 opt.direction = option.direction;
328 opt.text = category;
329
330 if ((category == hoveredCategory) && !mouseButtonPressed)
331 {
332 const QPalette::ColorGroup group =
333 option.state & QStyle::State_Enabled ?
334 QPalette::Normal : QPalette::Disabled;
335
336 QLinearGradient gradient(option.rect.topLeft(),
337 option.rect.bottomRight());
338 gradient.setColorAt(0,
339 option.palette.color(group,
340 QPalette::Highlight).light());
341 gradient.setColorAt(1, Qt::transparent);
342
343 painter->fillRect(option.rect, gradient);
344 }
345
346 /*if (const KStyle *style = dynamic_cast<const KStyle*>(QApplication::style()))
347 {
348 style->drawControl(KStyle::CE_Category, &opt, painter, this);
349 }
350 else
351 {*/
352 QFont painterFont = painter->font();
353 painterFont.setWeight(QFont::Bold);
354 QFontMetrics metrics(painterFont);
355 painter->setFont(painterFont);
356
357 QPainterPath path;
358 path.addRect(option.rect.left(),
359 option.rect.bottom() - 2,
360 option.rect.width(),
361 2);
362
363 QLinearGradient gradient(option.rect.topLeft(),
364 option.rect.bottomRight());
365 gradient.setColorAt(0, color);
366 gradient.setColorAt(1, Qt::transparent);
367
368 painter->setBrush(gradient);
369 painter->fillPath(path, gradient);
370
371 painter->setPen(color);
372
373 painter->drawText(option.rect, Qt::AlignVCenter | Qt::AlignLeft,
374 metrics.elidedText(category, Qt::ElideRight, option.rect.width()));
375 //}
376 painter->restore();
377 }
378
379
380 void KListView::Private::updateScrollbars()
381 {
382 int lastItemBottom = cachedRectIndex(lastIndex).bottom() +
383 listView->spacing() - listView->viewport()->height();
384
385 listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
386 listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
387 listView->verticalScrollBar()->setRange(0, lastItemBottom);
388 }
389
390 void KListView::Private::drawDraggedItems(QPainter *painter)
391 {
392 QStyleOptionViewItemV3 option = listView->viewOptions();
393 option.state &= ~QStyle::State_MouseOver;
394 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
395 {
396 const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
397 const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
398
399 option.rect = visualRect(index);
400 option.rect.adjust(dx, dy, dx, dy);
401
402 listView->itemDelegate(index)->paint(painter, option, index);
403 }
404 }
405
406 void KListView::Private::drawDraggedItems()
407 {
408 int dx;
409 int dy;
410 QRect rectToUpdate;
411 QRect currentRect;
412 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
413 {
414 dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
415 dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
416
417 currentRect = visualRect(index);
418 currentRect.adjust(dx, dy, dx, dy);
419
420 rectToUpdate = rectToUpdate.united(currentRect);
421 }
422
423 listView->viewport()->update(lastDraggedItemsRect);
424
425 lastDraggedItemsRect = rectToUpdate;
426
427 listView->viewport()->update(rectToUpdate);
428 }
429
430
431 //==============================================================================
432
433
434 KListView::KListView(QWidget *parent)
435 : QListView(parent)
436 , d(new Private(this))
437 {
438 }
439
440 KListView::~KListView()
441 {
442 delete d;
443 }
444
445 void KListView::setModel(QAbstractItemModel *model)
446 {
447 if (d->proxyModel)
448 {
449 QObject::disconnect(d->proxyModel,
450 SIGNAL(rowsRemoved(QModelIndex,int,int)),
451 this, SLOT(rowsRemoved(QModelIndex,int,int)));
452
453 QObject::disconnect(d->proxyModel,
454 SIGNAL(sortingRoleChanged()),
455 this, SLOT(slotSortingRoleChanged()));
456 }
457
458 QListView::setModel(model);
459
460 d->proxyModel = dynamic_cast<KSortFilterProxyModel*>(model);
461
462 if (d->proxyModel)
463 {
464 QObject::connect(d->proxyModel,
465 SIGNAL(rowsRemoved(QModelIndex,int,int)),
466 this, SLOT(rowsRemoved(QModelIndex,int,int)));
467
468 QObject::connect(d->proxyModel,
469 SIGNAL(sortingRoleChanged()),
470 this, SLOT(slotSortingRoleChanged()));
471 }
472 }
473
474 QRect KListView::visualRect(const QModelIndex &index) const
475 {
476 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
477 !d->itemCategorizer)
478 {
479 return QListView::visualRect(index);
480 }
481
482 if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
483 {
484 return d->visualRect(d->proxyModel->mapFromSource(index));
485 }
486
487 return d->visualRect(index);
488 }
489
490 KItemCategorizer *KListView::itemCategorizer() const
491 {
492 return d->itemCategorizer;
493 }
494
495 void KListView::setItemCategorizer(KItemCategorizer *itemCategorizer)
496 {
497 if (!itemCategorizer && d->proxyModel)
498 {
499 QObject::disconnect(d->proxyModel,
500 SIGNAL(rowsRemoved(QModelIndex,int,int)),
501 this, SLOT(rowsRemoved(QModelIndex,int,int)));
502
503 QObject::disconnect(d->proxyModel,
504 SIGNAL(sortingRoleChanged()),
505 this, SLOT(slotSortingRoleChanged()));
506 }
507 else if (itemCategorizer && d->proxyModel)
508 {
509 QObject::connect(d->proxyModel,
510 SIGNAL(rowsRemoved(QModelIndex,int,int)),
511 this, SLOT(rowsRemoved(QModelIndex,int,int)));
512
513 QObject::connect(d->proxyModel,
514 SIGNAL(sortingRoleChanged()),
515 this, SLOT(slotSortingRoleChanged()));
516 }
517
518 d->itemCategorizer = itemCategorizer;
519
520 if (itemCategorizer)
521 {
522 rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
523 }
524 else
525 {
526 updateGeometries();
527 }
528 }
529
530 QModelIndex KListView::indexAt(const QPoint &point) const
531 {
532 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
533 !d->itemCategorizer)
534 {
535 return QListView::indexAt(point);
536 }
537
538 QModelIndex index;
539
540 QModelIndexList item = d->intersectionSet(QRect(point, point));
541
542 if (item.count() == 1)
543 {
544 index = item[0];
545 }
546
547 d->hovered = index;
548
549 return index;
550 }
551
552 void KListView::reset()
553 {
554 QListView::reset();
555
556 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
557 !d->itemCategorizer)
558 {
559 return;
560 }
561
562 d->elementsInfo.clear();
563 d->elementsPosition.clear();
564 d->elementDictionary.clear();
565 d->categoriesIndexes.clear();
566 d->categoriesPosition.clear();
567 d->categories.clear();
568 d->intersectedIndexes.clear();
569 d->sourceModelIndexList.clear();
570 d->hovered = QModelIndex();
571 d->mouseButtonPressed = false;
572 }
573
574 void KListView::paintEvent(QPaintEvent *event)
575 {
576 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
577 !d->itemCategorizer)
578 {
579 QListView::paintEvent(event);
580 return;
581 }
582
583 QStyleOptionViewItemV3 option = viewOptions();
584 QPainter painter(viewport());
585 QRect area = event->rect();
586 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
587 currentIndex().isValid();
588 const QStyle::State state = option.state;
589 const bool enabled = (state & QStyle::State_Enabled) != 0;
590
591 painter.save();
592
593 QModelIndexList dirtyIndexes = d->intersectionSet(area);
594 foreach (const QModelIndex &index, dirtyIndexes)
595 {
596 option.state = state;
597 option.rect = d->visualRect(index);
598
599 if (selectionModel() && selectionModel()->isSelected(index))
600 {
601 option.state |= QStyle::State_Selected;
602 }
603
604 if (enabled)
605 {
606 QPalette::ColorGroup cg;
607 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
608 {
609 option.state &= ~QStyle::State_Enabled;
610 cg = QPalette::Disabled;
611 }
612 else
613 {
614 cg = QPalette::Normal;
615 }
616 option.palette.setCurrentColorGroup(cg);
617 }
618
619 if (focus && currentIndex() == index)
620 {
621 option.state |= QStyle::State_HasFocus;
622 if (this->state() == EditingState)
623 option.state |= QStyle::State_Editing;
624 }
625
626 if ((index == d->hovered) && !d->mouseButtonPressed)
627 option.state |= QStyle::State_MouseOver;
628 else
629 option.state &= ~QStyle::State_MouseOver;
630
631 itemDelegate(index)->paint(&painter, option, index);
632 }
633
634 if (d->mouseButtonPressed && !d->isDragging)
635 {
636 QPoint start, end, initialPressPosition;
637
638 initialPressPosition = d->initialPressPosition;
639
640 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
641 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
642
643 if (d->initialPressPosition.x() > d->mousePosition.x() ||
644 d->initialPressPosition.y() > d->mousePosition.y())
645 {
646 start = d->mousePosition;
647 end = initialPressPosition;
648 }
649 else
650 {
651 start = initialPressPosition;
652 end = d->mousePosition;
653 }
654
655 QStyleOptionRubberBand yetAnotherOption;
656 yetAnotherOption.initFrom(this);
657 yetAnotherOption.shape = QRubberBand::Rectangle;
658 yetAnotherOption.opaque = false;
659 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
660 painter.save();
661 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
662 painter.restore();
663 }
664
665 // Redraw categories
666 QStyleOptionViewItem otherOption;
667 foreach (const QString &category, d->categories)
668 {
669 otherOption = option;
670 otherOption.rect = d->categoryVisualRect(category);
671
672 if (otherOption.rect.intersects(area))
673 {
674 d->drawNewCategory(category, otherOption, &painter);
675 }
676 }
677
678 if (d->isDragging && !d->dragLeftViewport)
679 {
680 painter.setOpacity(0.5);
681 d->drawDraggedItems(&painter);
682 }
683
684 painter.restore();
685 }
686
687 void KListView::resizeEvent(QResizeEvent *event)
688 {
689 QListView::resizeEvent(event);
690
691 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
692 !d->itemCategorizer)
693 {
694 return;
695 }
696
697 // Clear the items positions cache
698 d->elementsPosition.clear();
699 d->categoriesPosition.clear();
700
701 d->updateScrollbars();
702 }
703
704 void KListView::setSelection(const QRect &rect,
705 QItemSelectionModel::SelectionFlags flags)
706 {
707 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
708 !d->itemCategorizer)
709 {
710 QListView::setSelection(rect, flags);
711 return;
712 }
713
714 if (!flags)
715 return;
716
717 selectionModel()->clear();
718
719 if (flags & QItemSelectionModel::Clear)
720 {
721 d->lastSelection = QItemSelection();
722 }
723
724 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
725 QItemSelection selection;
726
727 if (!dirtyIndexes.count())
728 {
729 if (d->lastSelection.count())
730 {
731 selectionModel()->select(d->lastSelection, flags);
732 }
733
734 return;
735 }
736
737 if (!d->mouseButtonPressed)
738 {
739 selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
740 }
741 else
742 {
743 QModelIndex first = dirtyIndexes[0];
744 QModelIndex last;
745 foreach (const QModelIndex &index, dirtyIndexes)
746 {
747 if (last.isValid() && last.row() + 1 != index.row())
748 {
749 QItemSelectionRange range(first, last);
750
751 selection << range;
752
753 first = index;
754 }
755
756 last = index;
757 }
758
759 if (last.isValid())
760 selection << QItemSelectionRange(first, last);
761 }
762
763 if (d->lastSelection.count() && !d->mouseButtonPressed)
764 {
765 selection.merge(d->lastSelection, flags);
766 }
767 else if (d->lastSelection.count())
768 {
769 selection.merge(d->lastSelection, QItemSelectionModel::Select);
770 }
771
772 selectionModel()->select(selection, flags);
773 }
774
775 void KListView::mouseMoveEvent(QMouseEvent *event)
776 {
777 QListView::mouseMoveEvent(event);
778
779 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
780 !d->itemCategorizer)
781 {
782 return;
783 }
784
785 d->mousePosition = event->pos();
786 d->hoveredCategory = QString();
787
788 // Redraw categories
789 foreach (const QString &category, d->categories)
790 {
791 if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
792 {
793 d->hoveredCategory = category;
794 }
795
796 viewport()->update(d->categoryVisualRect(category));
797 }
798
799 QRect rect;
800 if (d->mouseButtonPressed && !d->isDragging)
801 {
802 QPoint start, end, initialPressPosition;
803
804 initialPressPosition = d->initialPressPosition;
805
806 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
807 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
808
809 if (d->initialPressPosition.x() > d->mousePosition.x() ||
810 d->initialPressPosition.y() > d->mousePosition.y())
811 {
812 start = d->mousePosition;
813 end = initialPressPosition;
814 }
815 else
816 {
817 start = initialPressPosition;
818 end = d->mousePosition;
819 }
820
821 viewport()->update(d->lastSelectionRect);
822
823 rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
824
825 viewport()->update(rect);
826
827 d->lastSelectionRect = rect;
828 }
829 }
830
831 void KListView::mousePressEvent(QMouseEvent *event)
832 {
833 QListView::mousePressEvent(event);
834
835 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
836 !d->itemCategorizer)
837 {
838 return;
839 }
840
841 d->dragLeftViewport = false;
842
843 if (event->button() == Qt::LeftButton)
844 {
845 d->mouseButtonPressed = true;
846
847 d->initialPressPosition = event->pos();
848 d->initialPressPosition.setY(d->initialPressPosition.y() +
849 verticalOffset());
850 d->initialPressPosition.setX(d->initialPressPosition.x() +
851 horizontalOffset());
852 }
853 }
854
855 void KListView::mouseReleaseEvent(QMouseEvent *event)
856 {
857 QListView::mouseReleaseEvent(event);
858
859 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
860 !d->itemCategorizer)
861 {
862 return;
863 }
864
865 d->mouseButtonPressed = false;
866
867 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
868 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
869 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
870
871 QItemSelection selection;
872
873 if (initialPressPosition == d->initialPressPosition)
874 {
875 foreach(const QString &category, d->categories)
876 {
877 if (d->categoryVisualRect(category).contains(event->pos()))
878 {
879 QItemSelectionRange selectionRange(d->proxyModel->mapFromSource(d->categoriesIndexes[category][0]),
880 d->proxyModel->mapFromSource(d->categoriesIndexes[category][d->categoriesIndexes[category].count() - 1]));
881
882 selection << selectionRange;
883
884 selectionModel()->select(selection, QItemSelectionModel::Select);
885
886 break;
887 }
888 }
889 }
890
891 d->lastSelection = selectionModel()->selection();
892
893 if (d->hovered.isValid())
894 viewport()->update(d->visualRect(d->hovered));
895 else if (!d->hoveredCategory.isEmpty())
896 viewport()->update(d->categoryVisualRect(d->hoveredCategory));
897 }
898
899 void KListView::leaveEvent(QEvent *event)
900 {
901 QListView::leaveEvent(event);
902
903 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
904 !d->itemCategorizer)
905 {
906 return;
907 }
908
909 d->hovered = QModelIndex();
910 d->hoveredCategory = QString();
911 }
912
913 void KListView::startDrag(Qt::DropActions supportedActions)
914 {
915 QListView::startDrag(supportedActions);
916
917 d->isDragging = false;
918 d->mouseButtonPressed = false;
919 }
920
921 void KListView::dragMoveEvent(QDragMoveEvent *event)
922 {
923 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
924 !d->itemCategorizer)
925 {
926 QListView::dragMoveEvent(event);
927 return;
928 }
929
930 d->mousePosition = event->pos();
931
932 if (d->mouseButtonPressed)
933 {
934 d->isDragging = true;
935 }
936 else
937 {
938 d->isDragging = false;
939 }
940
941 d->dragLeftViewport = false;
942
943 d->drawDraggedItems();
944 }
945
946 void KListView::dragLeaveEvent(QDragLeaveEvent *event)
947 {
948 QListView::dragLeaveEvent(event);
949
950 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
951 !d->itemCategorizer)
952 {
953 return;
954 }
955
956 d->dragLeftViewport = true;
957 }
958
959 QModelIndex KListView::moveCursor(CursorAction cursorAction,
960 Qt::KeyboardModifiers modifiers)
961 {
962 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
963 !d->itemCategorizer)
964 {
965 return QListView::moveCursor(cursorAction, modifiers);
966 }
967
968 return QListView::moveCursor(cursorAction, modifiers);
969 }
970
971 void KListView::rowsInserted(const QModelIndex &parent,
972 int start,
973 int end)
974 {
975 QListView::rowsInserted(parent, start, end);
976
977 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
978 !d->itemCategorizer)
979 {
980 return;
981 }
982
983 rowsInsertedArtifficial(parent, start, end);
984 }
985
986 void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
987 int start,
988 int end)
989 {
990 Q_UNUSED(parent);
991
992 d->lastSelection = QItemSelection();
993 d->elementsInfo.clear();
994 d->elementsPosition.clear();
995 d->elementDictionary.clear();
996 d->categoriesIndexes.clear();
997 d->categoriesPosition.clear();
998 d->categories.clear();
999 d->intersectedIndexes.clear();
1000 d->sourceModelIndexList.clear();
1001 d->hovered = QModelIndex();
1002 d->mouseButtonPressed = false;
1003
1004 if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
1005 {
1006 return;
1007 }
1008
1009 // Add all elements mapped to the source model
1010 for (int k = 0; k < d->proxyModel->rowCount(); k++)
1011 {
1012 d->sourceModelIndexList <<
1013 d->proxyModel->mapToSource(d->proxyModel->index(k, 0));
1014 }
1015
1016 // Sort them with the general purpose lessThan method
1017 LessThan generalLessThan(d->proxyModel,
1018 LessThan::GeneralPurpose);
1019
1020 qStableSort(d->sourceModelIndexList.begin(), d->sourceModelIndexList.end(),
1021 generalLessThan);
1022
1023 // Explore categories
1024 QString prevCategory =
1025 d->itemCategorizer->categoryForItem(d->sourceModelIndexList[0],
1026 d->proxyModel->sortRole());
1027 QString lastCategory = prevCategory;
1028 QModelIndexList modelIndexList;
1029 struct Private::ElementInfo elementInfo;
1030 foreach (const QModelIndex &index, d->sourceModelIndexList)
1031 {
1032 lastCategory = d->itemCategorizer->categoryForItem(index,
1033 d->proxyModel->sortRole());
1034
1035 elementInfo.category = lastCategory;
1036
1037 if (prevCategory != lastCategory)
1038 {
1039 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1040 d->categories << prevCategory;
1041 modelIndexList.clear();
1042 }
1043
1044 modelIndexList << index;
1045 prevCategory = lastCategory;
1046
1047 d->elementsInfo.insert(index, elementInfo);
1048 }
1049
1050 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1051 d->categories << prevCategory;
1052
1053 // Sort items locally in their respective categories with the category
1054 // purpose lessThan
1055 LessThan categoryLessThan(d->proxyModel,
1056 LessThan::CategoryPurpose);
1057
1058 foreach (const QString &key, d->categories)
1059 {
1060 QModelIndexList &indexList = d->categoriesIndexes[key];
1061
1062 qStableSort(indexList.begin(), indexList.end(), categoryLessThan);
1063 }
1064
1065 d->lastIndex = d->categoriesIndexes[d->categories[d->categories.count() - 1]][d->categoriesIndexes[d->categories[d->categories.count() - 1]].count() - 1];
1066
1067 // Finally, fill data information of items situation. This will help when
1068 // trying to compute an item place in the viewport
1069 int i = 0; // position relative to the category beginning
1070 int j = 0; // number of elements before current
1071 foreach (const QString &key, d->categories)
1072 {
1073 foreach (const QModelIndex &index, d->categoriesIndexes[key])
1074 {
1075 struct Private::ElementInfo &elementInfo = d->elementsInfo[index];
1076
1077 elementInfo.relativeOffsetToCategory = i;
1078
1079 d->elementDictionary.insert(d->proxyModel->index(j, 0),
1080 d->proxyModel->mapFromSource(index));
1081
1082 i++;
1083 j++;
1084 }
1085
1086 i = 0;
1087 }
1088
1089 d->updateScrollbars();
1090 }
1091
1092 void KListView::rowsRemoved(const QModelIndex &parent,
1093 int start,
1094 int end)
1095 {
1096 if ((viewMode() == KListView::IconMode) && d->proxyModel &&
1097 d->itemCategorizer)
1098 {
1099 // Force the view to update all elements
1100 rowsInsertedArtifficial(parent, start, end);
1101 }
1102 }
1103
1104 void KListView::updateGeometries()
1105 {
1106 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
1107 !d->itemCategorizer)
1108 {
1109 QListView::updateGeometries();
1110 return;
1111 }
1112
1113 // Avoid QListView::updateGeometries(), since it will try to set another
1114 // range to our scroll bars, what we don't want (ereslibre)
1115 QAbstractItemView::updateGeometries();
1116 }
1117
1118 void KListView::slotSortingRoleChanged()
1119 {
1120 if ((viewMode() == KListView::IconMode) && d->proxyModel &&
1121 d->itemCategorizer)
1122 {
1123 // Force the view to update all elements
1124 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
1125 }
1126 }
1127
1128 #include "klistview.moc"