]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Implement inline-renaming for the new view-engine
[dolphin.git] / src / kitemviews / kitemlistview.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * Based on the Itemviews NG project from Trolltech Labs: *
5 * http://qt.gitorious.org/qt-labs/itemviews-ng *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
21 ***************************************************************************/
22
23 #include "kitemlistview.h"
24
25 #include "kitemlistcontroller.h"
26 #include "kitemlistheader.h"
27 #include "kitemlistselectionmanager.h"
28 #include "kitemlistwidget.h"
29
30 #include "private/kitemlistheaderwidget.h"
31 #include "private/kitemlistrubberband.h"
32 #include "private/kitemlistsizehintresolver.h"
33 #include "private/kitemlistviewlayouter.h"
34 #include "private/kitemlistviewanimation.h"
35
36 #include <KDebug>
37
38 #include <QCursor>
39 #include <QGraphicsSceneMouseEvent>
40 #include <QPainter>
41 #include <QPropertyAnimation>
42 #include <QStyle>
43 #include <QStyleOptionRubberBand>
44 #include <QTimer>
45
46 namespace {
47 // Time in ms until reaching the autoscroll margin triggers
48 // an initial autoscrolling
49 const int InitialAutoScrollDelay = 700;
50
51 // Delay in ms for triggering the next autoscroll
52 const int RepeatingAutoScrollDelay = 1000 / 60;
53 }
54
55 KItemListView::KItemListView(QGraphicsWidget* parent) :
56 QGraphicsWidget(parent),
57 m_enabledSelectionToggles(false),
58 m_grouped(false),
59 m_supportsItemExpanding(false),
60 m_editingRole(false),
61 m_activeTransactions(0),
62 m_endTransactionAnimationHint(Animation),
63 m_itemSize(),
64 m_controller(0),
65 m_model(0),
66 m_visibleRoles(),
67 m_widgetCreator(0),
68 m_groupHeaderCreator(0),
69 m_styleOption(),
70 m_visibleItems(),
71 m_visibleGroups(),
72 m_visibleCells(),
73 m_sizeHintResolver(0),
74 m_layouter(0),
75 m_animation(0),
76 m_layoutTimer(0),
77 m_oldScrollOffset(0),
78 m_oldMaximumScrollOffset(0),
79 m_oldItemOffset(0),
80 m_oldMaximumItemOffset(0),
81 m_skipAutoScrollForRubberBand(false),
82 m_rubberBand(0),
83 m_mousePos(),
84 m_autoScrollIncrement(0),
85 m_autoScrollTimer(0),
86 m_header(0),
87 m_headerWidget(0)
88 {
89 setAcceptHoverEvents(true);
90
91 m_sizeHintResolver = new KItemListSizeHintResolver(this);
92
93 m_layouter = new KItemListViewLayouter(this);
94 m_layouter->setSizeHintResolver(m_sizeHintResolver);
95
96 m_animation = new KItemListViewAnimation(this);
97 connect(m_animation, SIGNAL(finished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)),
98 this, SLOT(slotAnimationFinished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)));
99
100 m_layoutTimer = new QTimer(this);
101 m_layoutTimer->setInterval(300);
102 m_layoutTimer->setSingleShot(true);
103 connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
104
105 m_rubberBand = new KItemListRubberBand(this);
106 connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool)));
107
108 m_headerWidget = new KItemListHeaderWidget(this);
109 m_headerWidget->setVisible(false);
110
111 m_header = new KItemListHeader(this);
112 }
113
114 KItemListView::~KItemListView()
115 {
116 delete m_sizeHintResolver;
117 m_sizeHintResolver = 0;
118 }
119
120 void KItemListView::setScrollOrientation(Qt::Orientation orientation)
121 {
122 const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
123 if (orientation == previousOrientation) {
124 return;
125 }
126
127 m_layouter->setScrollOrientation(orientation);
128 m_animation->setScrollOrientation(orientation);
129 m_sizeHintResolver->clearCache();
130
131 if (m_grouped) {
132 QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
133 while (it.hasNext()) {
134 it.next();
135 it.value()->setScrollOrientation(orientation);
136 }
137 updateGroupHeaderHeight();
138
139 }
140
141 doLayout(NoAnimation);
142
143 onScrollOrientationChanged(orientation, previousOrientation);
144 emit scrollOrientationChanged(orientation, previousOrientation);
145 }
146
147 Qt::Orientation KItemListView::scrollOrientation() const
148 {
149 return m_layouter->scrollOrientation();
150 }
151
152 void KItemListView::setItemSize(const QSizeF& size)
153 {
154 const QSizeF previousSize = m_itemSize;
155 if (size == previousSize) {
156 return;
157 }
158
159 // Skip animations when the number of rows or columns
160 // are changed in the grid layout. Although the animation
161 // engine can handle this usecase, it looks obtrusive.
162 const bool animate = !changesItemGridLayout(m_layouter->size(),
163 size,
164 m_layouter->itemMargin());
165
166 const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) &&
167 (( m_itemSize.isEmpty() && !size.isEmpty()) ||
168 (!m_itemSize.isEmpty() && size.isEmpty()));
169
170 m_itemSize = size;
171
172 if (alternateBackgroundsChanged) {
173 // For an empty item size alternate backgrounds are drawn if more than
174 // one role is shown. Assure that the backgrounds for visible items are
175 // updated when changing the size in this context.
176 updateAlternateBackgrounds();
177 }
178
179 if (size.isEmpty()) {
180 if (m_headerWidget->automaticColumnResizing()) {
181 updatePreferredColumnWidths();
182 } else {
183 // Only apply the changed height and respect the header widths
184 // set by the user
185 const qreal currentWidth = m_layouter->itemSize().width();
186 const QSizeF newSize(currentWidth, size.height());
187 m_layouter->setItemSize(newSize);
188 }
189 } else {
190 m_layouter->setItemSize(size);
191 }
192
193 m_sizeHintResolver->clearCache();
194 doLayout(animate ? Animation : NoAnimation);
195 onItemSizeChanged(size, previousSize);
196 }
197
198 QSizeF KItemListView::itemSize() const
199 {
200 return m_itemSize;
201 }
202
203 void KItemListView::setScrollOffset(qreal offset)
204 {
205 if (offset < 0) {
206 offset = 0;
207 }
208
209 const qreal previousOffset = m_layouter->scrollOffset();
210 if (offset == previousOffset) {
211 return;
212 }
213
214 m_layouter->setScrollOffset(offset);
215 m_animation->setScrollOffset(offset);
216
217 // Don't check whether the m_layoutTimer is active: Changing the
218 // scroll offset must always trigger a synchronous layout, otherwise
219 // the smooth-scrolling might get jerky.
220 doLayout(NoAnimation);
221 onScrollOffsetChanged(offset, previousOffset);
222 }
223
224 qreal KItemListView::scrollOffset() const
225 {
226 return m_layouter->scrollOffset();
227 }
228
229 qreal KItemListView::maximumScrollOffset() const
230 {
231 return m_layouter->maximumScrollOffset();
232 }
233
234 void KItemListView::setItemOffset(qreal offset)
235 {
236 if (m_layouter->itemOffset() == offset) {
237 return;
238 }
239
240 m_layouter->setItemOffset(offset);
241 if (m_headerWidget->isVisible()) {
242 m_headerWidget->setOffset(offset);
243 }
244
245 // Don't check whether the m_layoutTimer is active: Changing the
246 // item offset must always trigger a synchronous layout, otherwise
247 // the smooth-scrolling might get jerky.
248 doLayout(NoAnimation);
249 }
250
251 qreal KItemListView::itemOffset() const
252 {
253 return m_layouter->itemOffset();
254 }
255
256 qreal KItemListView::maximumItemOffset() const
257 {
258 return m_layouter->maximumItemOffset();
259 }
260
261 void KItemListView::setVisibleRoles(const QList<QByteArray>& roles)
262 {
263 const QList<QByteArray> previousRoles = m_visibleRoles;
264 m_visibleRoles = roles;
265 onVisibleRolesChanged(roles, previousRoles);
266
267 m_sizeHintResolver->clearCache();
268 m_layouter->markAsDirty();
269
270 if (m_itemSize.isEmpty()) {
271 m_headerWidget->setColumns(roles);
272 updatePreferredColumnWidths();
273 if (!m_headerWidget->automaticColumnResizing()) {
274 // The column-width of new roles are still 0. Apply the preferred
275 // column-width as default with.
276 foreach (const QByteArray& role, m_visibleRoles) {
277 if (m_headerWidget->columnWidth(role) == 0) {
278 const qreal width = m_headerWidget->preferredColumnWidth(role);
279 m_headerWidget->setColumnWidth(role, width);
280 }
281 }
282
283 applyColumnWidthsFromHeader();
284 }
285 }
286
287 const bool alternateBackgroundsChanged = m_itemSize.isEmpty() &&
288 ((roles.count() > 1 && previousRoles.count() <= 1) ||
289 (roles.count() <= 1 && previousRoles.count() > 1));
290
291 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
292 while (it.hasNext()) {
293 it.next();
294 KItemListWidget* widget = it.value();
295 widget->setVisibleRoles(roles);
296 if (alternateBackgroundsChanged) {
297 updateAlternateBackgroundForWidget(widget);
298 }
299 }
300
301 doLayout(NoAnimation);
302 }
303
304 QList<QByteArray> KItemListView::visibleRoles() const
305 {
306 return m_visibleRoles;
307 }
308
309 void KItemListView::setAutoScroll(bool enabled)
310 {
311 if (enabled && !m_autoScrollTimer) {
312 m_autoScrollTimer = new QTimer(this);
313 m_autoScrollTimer->setSingleShot(true);
314 connect(m_autoScrollTimer, SIGNAL(timeout()), this, SLOT(triggerAutoScrolling()));
315 m_autoScrollTimer->start(InitialAutoScrollDelay);
316 } else if (!enabled && m_autoScrollTimer) {
317 delete m_autoScrollTimer;
318 m_autoScrollTimer = 0;
319 }
320 }
321
322 bool KItemListView::autoScroll() const
323 {
324 return m_autoScrollTimer != 0;
325 }
326
327 void KItemListView::setEnabledSelectionToggles(bool enabled)
328 {
329 if (m_enabledSelectionToggles != enabled) {
330 m_enabledSelectionToggles = enabled;
331
332 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
333 while (it.hasNext()) {
334 it.next();
335 it.value()->setEnabledSelectionToggle(enabled);
336 }
337 }
338 }
339
340 bool KItemListView::enabledSelectionToggles() const
341 {
342 return m_enabledSelectionToggles;
343 }
344
345 KItemListController* KItemListView::controller() const
346 {
347 return m_controller;
348 }
349
350 KItemModelBase* KItemListView::model() const
351 {
352 return m_model;
353 }
354
355 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator)
356 {
357 m_widgetCreator = widgetCreator;
358 }
359
360 KItemListWidgetCreatorBase* KItemListView::widgetCreator() const
361 {
362 return m_widgetCreator;
363 }
364
365 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator)
366 {
367 m_groupHeaderCreator = groupHeaderCreator;
368 }
369
370 KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const
371 {
372 return m_groupHeaderCreator;
373 }
374
375 void KItemListView::setStyleOption(const KItemListStyleOption& option)
376 {
377 const KItemListStyleOption previousOption = m_styleOption;
378 m_styleOption = option;
379
380 bool animate = true;
381 const QSizeF margin(option.horizontalMargin, option.verticalMargin);
382 if (margin != m_layouter->itemMargin()) {
383 // Skip animations when the number of rows or columns
384 // are changed in the grid layout. Although the animation
385 // engine can handle this usecase, it looks obtrusive.
386 animate = !changesItemGridLayout(m_layouter->size(),
387 m_layouter->itemSize(),
388 margin);
389 m_layouter->setItemMargin(margin);
390 }
391
392 if (m_grouped) {
393 updateGroupHeaderHeight();
394 }
395
396 if (animate && previousOption.maxTextSize != option.maxTextSize) {
397 // Animating a change of the maximum text size just results in expensive
398 // temporary eliding and clipping operations and does not look good visually.
399 animate = false;
400 }
401
402 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
403 while (it.hasNext()) {
404 it.next();
405 it.value()->setStyleOption(option);
406 }
407
408 m_sizeHintResolver->clearCache();
409 m_layouter->markAsDirty();
410 doLayout(animate ? Animation : NoAnimation);
411
412 onStyleOptionChanged(option, previousOption);
413 }
414
415 const KItemListStyleOption& KItemListView::styleOption() const
416 {
417 return m_styleOption;
418 }
419
420 void KItemListView::setGeometry(const QRectF& rect)
421 {
422 QGraphicsWidget::setGeometry(rect);
423
424 if (!m_model) {
425 return;
426 }
427
428 const QSizeF newSize = rect.size();
429 if (m_itemSize.isEmpty()) {
430 m_headerWidget->resize(rect.width(), m_headerWidget->size().height());
431 if (m_headerWidget->automaticColumnResizing()) {
432 applyAutomaticColumnWidths();
433 } else {
434 const qreal requiredWidth = columnWidthsSum();
435 const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth),
436 m_itemSize.height());
437 m_layouter->setItemSize(dynamicItemSize);
438 }
439
440 // Triggering a synchronous layout is fine from a performance point of view,
441 // as with dynamic item sizes no moving animation must be done.
442 m_layouter->setSize(newSize);
443 doLayout(NoAnimation);
444 } else {
445 const bool animate = !changesItemGridLayout(newSize,
446 m_layouter->itemSize(),
447 m_layouter->itemMargin());
448 m_layouter->setSize(newSize);
449
450 if (animate) {
451 // Trigger an asynchronous relayout with m_layoutTimer to prevent
452 // performance bottlenecks. If the timer is exceeded, an animated layout
453 // will be triggered.
454 if (!m_layoutTimer->isActive()) {
455 m_layoutTimer->start();
456 }
457 } else {
458 m_layoutTimer->stop();
459 doLayout(NoAnimation);
460 }
461 }
462 }
463
464 int KItemListView::itemAt(const QPointF& pos) const
465 {
466 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
467 while (it.hasNext()) {
468 it.next();
469
470 const KItemListWidget* widget = it.value();
471 const QPointF mappedPos = widget->mapFromItem(this, pos);
472 if (widget->contains(mappedPos)) {
473 return it.key();
474 }
475 }
476
477 return -1;
478 }
479
480 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
481 {
482 if (!m_enabledSelectionToggles) {
483 return false;
484 }
485
486 const KItemListWidget* widget = m_visibleItems.value(index);
487 if (widget) {
488 const QRectF selectionToggleRect = widget->selectionToggleRect();
489 if (!selectionToggleRect.isEmpty()) {
490 const QPointF mappedPos = widget->mapFromItem(this, pos);
491 return selectionToggleRect.contains(mappedPos);
492 }
493 }
494 return false;
495 }
496
497 bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
498 {
499 const KItemListWidget* widget = m_visibleItems.value(index);
500 if (widget) {
501 const QRectF expansionToggleRect = widget->expansionToggleRect();
502 if (!expansionToggleRect.isEmpty()) {
503 const QPointF mappedPos = widget->mapFromItem(this, pos);
504 return expansionToggleRect.contains(mappedPos);
505 }
506 }
507 return false;
508 }
509
510 int KItemListView::firstVisibleIndex() const
511 {
512 return m_layouter->firstVisibleIndex();
513 }
514
515 int KItemListView::lastVisibleIndex() const
516 {
517 return m_layouter->lastVisibleIndex();
518 }
519
520 QSizeF KItemListView::itemSizeHint(int index) const
521 {
522 return m_widgetCreator->itemSizeHint(index, this);
523 }
524
525 void KItemListView::setSupportsItemExpanding(bool supportsExpanding)
526 {
527 if (m_supportsItemExpanding != supportsExpanding) {
528 m_supportsItemExpanding = supportsExpanding;
529 updateSiblingsInformation();
530 onSupportsItemExpandingChanged(supportsExpanding);
531 }
532 }
533
534 bool KItemListView::supportsItemExpanding() const
535 {
536 return m_supportsItemExpanding;
537 }
538
539 QRectF KItemListView::itemRect(int index) const
540 {
541 return m_layouter->itemRect(index);
542 }
543
544 QRectF KItemListView::itemContextRect(int index) const
545 {
546 QRectF contextRect;
547
548 const KItemListWidget* widget = m_visibleItems.value(index);
549 if (widget) {
550 contextRect = widget->iconRect() | widget->textRect();
551 contextRect.translate(itemRect(index).topLeft());
552 }
553
554 return contextRect;
555 }
556
557 void KItemListView::scrollToItem(int index)
558 {
559 QRectF viewGeometry = geometry();
560 if (m_headerWidget->isVisible()) {
561 const qreal headerHeight = m_headerWidget->size().height();
562 viewGeometry.adjust(0, headerHeight, 0, 0);
563 }
564 const QRectF currentRect = itemRect(index);
565
566 if (!viewGeometry.contains(currentRect)) {
567 qreal newOffset = scrollOffset();
568 if (scrollOrientation() == Qt::Vertical) {
569 if (currentRect.top() < viewGeometry.top()) {
570 newOffset += currentRect.top() - viewGeometry.top();
571 } else if (currentRect.bottom() > viewGeometry.bottom()) {
572 newOffset += currentRect.bottom() - viewGeometry.bottom();
573 }
574 } else {
575 if (currentRect.left() < viewGeometry.left()) {
576 newOffset += currentRect.left() - viewGeometry.left();
577 } else if (currentRect.right() > viewGeometry.right()) {
578 newOffset += currentRect.right() - viewGeometry.right();
579 }
580 }
581
582 if (newOffset != scrollOffset()) {
583 emit scrollTo(newOffset);
584 }
585 }
586 }
587
588 void KItemListView::beginTransaction()
589 {
590 ++m_activeTransactions;
591 if (m_activeTransactions == 1) {
592 onTransactionBegin();
593 }
594 }
595
596 void KItemListView::endTransaction()
597 {
598 --m_activeTransactions;
599 if (m_activeTransactions < 0) {
600 m_activeTransactions = 0;
601 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
602 }
603
604 if (m_activeTransactions == 0) {
605 onTransactionEnd();
606 doLayout(m_endTransactionAnimationHint);
607 m_endTransactionAnimationHint = Animation;
608 }
609 }
610
611 bool KItemListView::isTransactionActive() const
612 {
613 return m_activeTransactions > 0;
614 }
615
616 void KItemListView::setHeaderVisible(bool visible)
617 {
618 if (visible && !m_headerWidget->isVisible()) {
619 QStyleOptionHeader option;
620 const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection,
621 &option, QSize());
622
623 m_headerWidget->setPos(0, 0);
624 m_headerWidget->resize(size().width(), headerSize.height());
625 m_headerWidget->setModel(m_model);
626 m_headerWidget->setColumns(m_visibleRoles);
627 m_headerWidget->setZValue(1);
628
629 connect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)),
630 this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal)));
631 connect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)),
632 this, SLOT(slotHeaderColumnMoved(QByteArray,int,int)));
633 connect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
634 this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
635 connect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
636 this, SIGNAL(sortRoleChanged(QByteArray,QByteArray)));
637
638 m_layouter->setHeaderHeight(headerSize.height());
639 m_headerWidget->setVisible(true);
640 } else if (!visible && m_headerWidget->isVisible()) {
641 disconnect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)),
642 this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal)));
643 disconnect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)),
644 this, SLOT(slotHeaderColumnMoved(QByteArray,int,int)));
645 disconnect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
646 this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
647 disconnect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
648 this, SIGNAL(sortRoleChanged(QByteArray,QByteArray)));
649
650 m_layouter->setHeaderHeight(0);
651 m_headerWidget->setVisible(false);
652 }
653 }
654
655 bool KItemListView::isHeaderVisible() const
656 {
657 return m_headerWidget->isVisible();
658 }
659
660 KItemListHeader* KItemListView::header() const
661 {
662 return m_header;
663 }
664
665 QPixmap KItemListView::createDragPixmap(const QSet<int>& indexes) const
666 {
667 Q_UNUSED(indexes);
668 return QPixmap();
669 }
670
671 void KItemListView::editRole(int index, const QByteArray& role)
672 {
673 KItemListWidget* widget = m_visibleItems.value(index);
674 if (!widget) {
675 return;
676 }
677
678 Q_ASSERT(!m_editingRole);
679 m_editingRole = true;
680 widget->setEditedRole(role);
681
682 connect(widget, SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)),
683 this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant)));
684 connect(widget, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)),
685 this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant)));
686 }
687
688 void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
689 {
690 QGraphicsWidget::paint(painter, option, widget);
691
692 if (m_rubberBand->isActive()) {
693 QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
694 m_rubberBand->endPosition()).normalized();
695
696 const QPointF topLeft = rubberBandRect.topLeft();
697 if (scrollOrientation() == Qt::Vertical) {
698 rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset());
699 } else {
700 rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y());
701 }
702
703 QStyleOptionRubberBand opt;
704 opt.initFrom(widget);
705 opt.shape = QRubberBand::Rectangle;
706 opt.opaque = false;
707 opt.rect = rubberBandRect.toRect();
708 style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
709 }
710 }
711
712 void KItemListView::initializeItemListWidget(KItemListWidget* item)
713 {
714 Q_UNUSED(item);
715 }
716
717 bool KItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
718 {
719 Q_UNUSED(changedRoles);
720 return true;
721 }
722
723 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
724 {
725 Q_UNUSED(current);
726 Q_UNUSED(previous);
727 }
728
729 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
730 {
731 Q_UNUSED(current);
732 Q_UNUSED(previous);
733 }
734
735 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
736 {
737 Q_UNUSED(current);
738 Q_UNUSED(previous);
739 }
740
741 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
742 {
743 Q_UNUSED(current);
744 Q_UNUSED(previous);
745 }
746
747 void KItemListView::onScrollOffsetChanged(qreal current, qreal previous)
748 {
749 Q_UNUSED(current);
750 Q_UNUSED(previous);
751 }
752
753 void KItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
754 {
755 Q_UNUSED(current);
756 Q_UNUSED(previous);
757 }
758
759 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
760 {
761 Q_UNUSED(current);
762 Q_UNUSED(previous);
763 }
764
765 void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
766 {
767 Q_UNUSED(supportsExpanding);
768 }
769
770 void KItemListView::onTransactionBegin()
771 {
772 }
773
774 void KItemListView::onTransactionEnd()
775 {
776 }
777
778 bool KItemListView::event(QEvent* event)
779 {
780 // Forward all events to the controller and handle them there
781 if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) {
782 event->accept();
783 return true;
784 }
785 return QGraphicsWidget::event(event);
786 }
787
788 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
789 {
790 m_mousePos = transform().map(event->pos());
791 event->accept();
792 }
793
794 void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
795 {
796 QGraphicsWidget::mouseMoveEvent(event);
797
798 m_mousePos = transform().map(event->pos());
799 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) {
800 m_autoScrollTimer->start(InitialAutoScrollDelay);
801 }
802 }
803
804 void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
805 {
806 event->setAccepted(true);
807 setAutoScroll(true);
808 }
809
810 void KItemListView::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
811 {
812 QGraphicsWidget::dragMoveEvent(event);
813
814 m_mousePos = transform().map(event->pos());
815 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) {
816 m_autoScrollTimer->start(InitialAutoScrollDelay);
817 }
818 }
819
820 void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
821 {
822 QGraphicsWidget::dragLeaveEvent(event);
823 setAutoScroll(false);
824 }
825
826 void KItemListView::dropEvent(QGraphicsSceneDragDropEvent* event)
827 {
828 QGraphicsWidget::dropEvent(event);
829 setAutoScroll(false);
830 }
831
832 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
833 {
834 return m_visibleItems.values();
835 }
836
837 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
838 {
839 if (m_itemSize.isEmpty()) {
840 updatePreferredColumnWidths(itemRanges);
841 }
842
843 const bool hasMultipleRanges = (itemRanges.count() > 1);
844 if (hasMultipleRanges) {
845 beginTransaction();
846 }
847
848 m_layouter->markAsDirty();
849
850 int previouslyInsertedCount = 0;
851 foreach (const KItemRange& range, itemRanges) {
852 // range.index is related to the model before anything has been inserted.
853 // As in each loop the current item-range gets inserted the index must
854 // be increased by the already previously inserted items.
855 const int index = range.index + previouslyInsertedCount;
856 const int count = range.count;
857 if (index < 0 || count <= 0) {
858 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
859 continue;
860 }
861 previouslyInsertedCount += count;
862
863 m_sizeHintResolver->itemsInserted(index, count);
864
865 // Determine which visible items must be moved
866 QList<int> itemsToMove;
867 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
868 while (it.hasNext()) {
869 it.next();
870 const int visibleItemIndex = it.key();
871 if (visibleItemIndex >= index) {
872 itemsToMove.append(visibleItemIndex);
873 }
874 }
875
876 // Update the indexes of all KItemListWidget instances that are located
877 // after the inserted items. It is important to adjust the indexes in the order
878 // from the highest index to the lowest index to prevent overlaps when setting the new index.
879 qSort(itemsToMove);
880 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
881 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
882 Q_ASSERT(widget);
883 const int newIndex = widget->index() + count;
884 if (hasMultipleRanges) {
885 setWidgetIndex(widget, newIndex);
886 } else {
887 // Try to animate the moving of the item
888 moveWidgetToIndex(widget, newIndex);
889 }
890 }
891
892 if (m_model->count() == count && m_activeTransactions == 0) {
893 // Check whether a scrollbar is required to show the inserted items. In this case
894 // the size of the layouter will be decreased before calling doLayout(): This prevents
895 // an unnecessary temporary animation due to the geometry change of the inserted scrollbar.
896 const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical);
897 const bool decreaseLayouterSize = ( verticalScrollOrientation && maximumScrollOffset() > size().height()) ||
898 (!verticalScrollOrientation && maximumScrollOffset() > size().width());
899 if (decreaseLayouterSize) {
900 const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
901 QSizeF layouterSize = m_layouter->size();
902 if (verticalScrollOrientation) {
903 layouterSize.rwidth() -= scrollBarExtent;
904 } else {
905 layouterSize.rheight() -= scrollBarExtent;
906 }
907 m_layouter->setSize(layouterSize);
908 }
909 }
910
911 if (!hasMultipleRanges) {
912 doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count);
913 updateSiblingsInformation();
914 }
915 }
916
917 if (m_controller) {
918 m_controller->selectionManager()->itemsInserted(itemRanges);
919 }
920
921 if (hasMultipleRanges) {
922 #ifndef QT_NO_DEBUG
923 // Important: Don't read any m_layouter-property inside the for-loop in case if
924 // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is
925 // updated in each loop-cycle and has only a consistent state after the loop.
926 Q_ASSERT(m_layouter->isDirty());
927 #endif
928 m_endTransactionAnimationHint = NoAnimation;
929 endTransaction();
930 updateSiblingsInformation();
931 }
932
933 if (useAlternateBackgrounds()) {
934 updateAlternateBackgrounds();
935 }
936 }
937
938 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
939 {
940 if (m_itemSize.isEmpty()) {
941 // Don't pass the item-range: The preferred column-widths of
942 // all items must be adjusted when removing items.
943 updatePreferredColumnWidths();
944 }
945
946 const bool hasMultipleRanges = (itemRanges.count() > 1);
947 if (hasMultipleRanges) {
948 beginTransaction();
949 }
950
951 m_layouter->markAsDirty();
952
953 int removedItemsCount = 0;
954 for (int i = 0; i < itemRanges.count(); ++i) {
955 removedItemsCount += itemRanges[i].count;
956 }
957
958 for (int i = itemRanges.count() - 1; i >= 0; --i) {
959 const KItemRange& range = itemRanges[i];
960 const int index = range.index;
961 const int count = range.count;
962 if (index < 0 || count <= 0) {
963 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
964 continue;
965 }
966
967 m_sizeHintResolver->itemsRemoved(index, count);
968
969 const int firstRemovedIndex = index;
970 const int lastRemovedIndex = index + count - 1;
971 const int lastIndex = m_model->count() - 1 + removedItemsCount;
972 removedItemsCount -= count;
973
974 // Remove all KItemListWidget instances that got deleted
975 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
976 KItemListWidget* widget = m_visibleItems.value(i);
977 if (!widget) {
978 continue;
979 }
980
981 m_animation->stop(widget);
982 // Stopping the animation might lead to recycling the widget if
983 // it is invisible (see slotAnimationFinished()).
984 // Check again whether it is still visible:
985 if (!m_visibleItems.contains(i)) {
986 continue;
987 }
988
989 if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) {
990 // Remove the widget without animation
991 recycleWidget(widget);
992 } else {
993 // Animate the removing of the items. Special case: When removing an item there
994 // is no valid model index available anymore. For the
995 // remove-animation the item gets removed from m_visibleItems but the widget
996 // will stay alive until the animation has been finished and will
997 // be recycled (deleted) in KItemListView::slotAnimationFinished().
998 m_visibleItems.remove(i);
999 widget->setIndex(-1);
1000 m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
1001 }
1002 }
1003
1004 // Update the indexes of all KItemListWidget instances that are located
1005 // after the deleted items
1006 for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
1007 KItemListWidget* widget = m_visibleItems.value(i);
1008 if (widget) {
1009 const int newIndex = i - count;
1010 if (hasMultipleRanges) {
1011 setWidgetIndex(widget, newIndex);
1012 } else {
1013 // Try to animate the moving of the item
1014 moveWidgetToIndex(widget, newIndex);
1015 }
1016 }
1017 }
1018
1019 if (!hasMultipleRanges) {
1020 // The decrease-layout-size optimization in KItemListView::slotItemsInserted()
1021 // assumes an updated geometry. If items are removed during an active transaction,
1022 // the transaction will be temporary deactivated so that doLayout() triggers a
1023 // geometry update if necessary.
1024 const int activeTransactions = m_activeTransactions;
1025 m_activeTransactions = 0;
1026 doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count);
1027 m_activeTransactions = activeTransactions;
1028 updateSiblingsInformation();
1029 }
1030 }
1031
1032 if (m_controller) {
1033 m_controller->selectionManager()->itemsRemoved(itemRanges);
1034 }
1035
1036 if (hasMultipleRanges) {
1037 #ifndef QT_NO_DEBUG
1038 // Important: Don't read any m_layouter-property inside the for-loop in case if
1039 // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is
1040 // updated in each loop-cycle and has only a consistent state after the loop.
1041 Q_ASSERT(m_layouter->isDirty());
1042 #endif
1043 m_endTransactionAnimationHint = NoAnimation;
1044 endTransaction();
1045 updateSiblingsInformation();
1046 }
1047
1048 if (useAlternateBackgrounds()) {
1049 updateAlternateBackgrounds();
1050 }
1051 }
1052
1053 void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes)
1054 {
1055 m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count);
1056 m_layouter->markAsDirty();
1057
1058 if (m_controller) {
1059 m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes);
1060 }
1061
1062 const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index);
1063 const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1);
1064
1065 for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) {
1066 KItemListWidget* widget = m_visibleItems.value(index);
1067 if (widget) {
1068 updateWidgetProperties(widget, index);
1069 initializeItemListWidget(widget);
1070 }
1071 }
1072
1073 doLayout(NoAnimation);
1074 updateSiblingsInformation();
1075 }
1076
1077 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
1078 const QSet<QByteArray>& roles)
1079 {
1080 const bool updateSizeHints = itemSizeHintUpdateRequired(roles);
1081 if (updateSizeHints && m_itemSize.isEmpty()) {
1082 updatePreferredColumnWidths(itemRanges);
1083 }
1084
1085 foreach (const KItemRange& itemRange, itemRanges) {
1086 const int index = itemRange.index;
1087 const int count = itemRange.count;
1088
1089 if (updateSizeHints) {
1090 m_sizeHintResolver->itemsChanged(index, count, roles);
1091 m_layouter->markAsDirty();
1092
1093 if (!m_layoutTimer->isActive()) {
1094 m_layoutTimer->start();
1095 }
1096 }
1097
1098 // Apply the changed roles to the visible item-widgets
1099 const int lastIndex = index + count - 1;
1100 for (int i = index; i <= lastIndex; ++i) {
1101 KItemListWidget* widget = m_visibleItems.value(i);
1102 if (widget) {
1103 widget->setData(m_model->data(i), roles);
1104 }
1105 }
1106
1107 if (m_grouped && roles.contains(m_model->sortRole())) {
1108 // The sort-role has been changed which might result
1109 // in modified group headers
1110 updateVisibleGroupHeaders();
1111 doLayout(NoAnimation);
1112 }
1113 }
1114 }
1115
1116 void KItemListView::slotGroupedSortingChanged(bool current)
1117 {
1118 m_grouped = current;
1119 m_layouter->markAsDirty();
1120
1121 if (m_grouped) {
1122 updateGroupHeaderHeight();
1123 } else {
1124 // Clear all visible headers
1125 QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
1126 while (it.hasNext()) {
1127 it.next();
1128 recycleGroupHeaderForWidget(it.key());
1129 }
1130 Q_ASSERT(m_visibleGroups.isEmpty());
1131 }
1132
1133 if (useAlternateBackgrounds()) {
1134 // Changing the group mode requires to update the alternate backgrounds
1135 // as with the enabled group mode the altering is done on base of the first
1136 // group item.
1137 updateAlternateBackgrounds();
1138 }
1139 updateSiblingsInformation();
1140 doLayout(NoAnimation);
1141 }
1142
1143 void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
1144 {
1145 Q_UNUSED(current);
1146 Q_UNUSED(previous);
1147 if (m_grouped) {
1148 updateVisibleGroupHeaders();
1149 doLayout(NoAnimation);
1150 }
1151 }
1152
1153 void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
1154 {
1155 Q_UNUSED(current);
1156 Q_UNUSED(previous);
1157 if (m_grouped) {
1158 updateVisibleGroupHeaders();
1159 doLayout(NoAnimation);
1160 }
1161 }
1162
1163 void KItemListView::slotCurrentChanged(int current, int previous)
1164 {
1165 Q_UNUSED(previous);
1166
1167 KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
1168 if (previousWidget) {
1169 previousWidget->setCurrent(false);
1170 }
1171
1172 KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
1173 if (currentWidget) {
1174 currentWidget->setCurrent(true);
1175 }
1176 }
1177
1178 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
1179 {
1180 Q_UNUSED(previous);
1181
1182 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1183 while (it.hasNext()) {
1184 it.next();
1185 const int index = it.key();
1186 KItemListWidget* widget = it.value();
1187 widget->setSelected(current.contains(index));
1188 }
1189 }
1190
1191 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
1192 KItemListViewAnimation::AnimationType type)
1193 {
1194 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
1195 Q_ASSERT(itemListWidget);
1196
1197 switch (type) {
1198 case KItemListViewAnimation::DeleteAnimation: {
1199 // As we recycle the widget in this case it is important to assure that no
1200 // other animation has been started. This is a convention in KItemListView and
1201 // not a requirement defined by KItemListViewAnimation.
1202 Q_ASSERT(!m_animation->isStarted(itemListWidget));
1203
1204 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
1205 // by m_visibleWidgets and must be deleted manually after the animation has
1206 // been finished.
1207 recycleGroupHeaderForWidget(itemListWidget);
1208 m_widgetCreator->recycle(itemListWidget);
1209 break;
1210 }
1211
1212 case KItemListViewAnimation::CreateAnimation:
1213 case KItemListViewAnimation::MovingAnimation:
1214 case KItemListViewAnimation::ResizeAnimation: {
1215 const int index = itemListWidget->index();
1216 const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
1217 (index > m_layouter->lastVisibleIndex());
1218 if (invisible && !m_animation->isStarted(itemListWidget)) {
1219 recycleWidget(itemListWidget);
1220 }
1221 break;
1222 }
1223
1224 default: break;
1225 }
1226 }
1227
1228 void KItemListView::slotLayoutTimerFinished()
1229 {
1230 m_layouter->setSize(geometry().size());
1231 doLayout(Animation);
1232 }
1233
1234 void KItemListView::slotRubberBandPosChanged()
1235 {
1236 update();
1237 }
1238
1239 void KItemListView::slotRubberBandActivationChanged(bool active)
1240 {
1241 if (active) {
1242 connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1243 connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1244 m_skipAutoScrollForRubberBand = true;
1245 } else {
1246 disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1247 disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1248 m_skipAutoScrollForRubberBand = false;
1249 }
1250
1251 update();
1252 }
1253
1254 void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role,
1255 qreal currentWidth,
1256 qreal previousWidth)
1257 {
1258 Q_UNUSED(role);
1259 Q_UNUSED(currentWidth);
1260 Q_UNUSED(previousWidth);
1261
1262 m_headerWidget->setAutomaticColumnResizing(false);
1263 applyColumnWidthsFromHeader();
1264 doLayout(NoAnimation);
1265 }
1266
1267 void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
1268 int currentIndex,
1269 int previousIndex)
1270 {
1271 Q_ASSERT(m_visibleRoles[previousIndex] == role);
1272
1273 const QList<QByteArray> previous = m_visibleRoles;
1274
1275 QList<QByteArray> current = m_visibleRoles;
1276 current.removeAt(previousIndex);
1277 current.insert(currentIndex, role);
1278
1279 setVisibleRoles(current);
1280
1281 emit visibleRolesChanged(current, previous);
1282 }
1283
1284 void KItemListView::triggerAutoScrolling()
1285 {
1286 if (!m_autoScrollTimer) {
1287 return;
1288 }
1289
1290 int pos = 0;
1291 int visibleSize = 0;
1292 if (scrollOrientation() == Qt::Vertical) {
1293 pos = m_mousePos.y();
1294 visibleSize = size().height();
1295 } else {
1296 pos = m_mousePos.x();
1297 visibleSize = size().width();
1298 }
1299
1300 if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) {
1301 m_autoScrollIncrement = 0;
1302 }
1303
1304 m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement);
1305 if (m_autoScrollIncrement == 0) {
1306 // The mouse position is not above an autoscroll margin (the autoscroll timer
1307 // will be restarted in mouseMoveEvent())
1308 m_autoScrollTimer->stop();
1309 return;
1310 }
1311
1312 if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) {
1313 // If a rubberband selection is ongoing the autoscrolling may only get triggered
1314 // if the direction of the rubberband is similar to the autoscroll direction. This
1315 // prevents that starting to create a rubberband within the autoscroll margins starts
1316 // an autoscrolling.
1317
1318 const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small
1319 const qreal diff = (scrollOrientation() == Qt::Vertical)
1320 ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y()
1321 : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x();
1322 if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) {
1323 // The rubberband direction is different from the scroll direction (e.g. the rubberband has
1324 // been moved up although the autoscroll direction might be down)
1325 m_autoScrollTimer->stop();
1326 return;
1327 }
1328 }
1329
1330 // As soon as the autoscrolling has been triggered at least once despite having an active rubberband,
1331 // the autoscrolling may not get skipped anymore until a new rubberband is created
1332 m_skipAutoScrollForRubberBand = false;
1333
1334 const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize);
1335 const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset);
1336 setScrollOffset(newScrollOffset);
1337
1338 // Trigger the autoscroll timer which will periodically call
1339 // triggerAutoScrolling()
1340 m_autoScrollTimer->start(RepeatingAutoScrollDelay);
1341 }
1342
1343 void KItemListView::slotGeometryOfGroupHeaderParentChanged()
1344 {
1345 KItemListWidget* widget = qobject_cast<KItemListWidget*>(sender());
1346 Q_ASSERT(widget);
1347 KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
1348 Q_ASSERT(groupHeader);
1349 updateGroupHeaderLayout(widget);
1350 }
1351
1352 void KItemListView::slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value)
1353 {
1354 emit roleEditingCanceled(index, role, value);
1355 m_editingRole = false;
1356 }
1357
1358 void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value)
1359 {
1360 emit roleEditingFinished(index, role, value);
1361 m_editingRole = false;
1362 }
1363
1364 void KItemListView::setController(KItemListController* controller)
1365 {
1366 if (m_controller != controller) {
1367 KItemListController* previous = m_controller;
1368 if (previous) {
1369 KItemListSelectionManager* selectionManager = previous->selectionManager();
1370 disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
1371 disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
1372 }
1373
1374 m_controller = controller;
1375
1376 if (controller) {
1377 KItemListSelectionManager* selectionManager = controller->selectionManager();
1378 connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
1379 connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
1380 }
1381
1382 onControllerChanged(controller, previous);
1383 }
1384 }
1385
1386 void KItemListView::setModel(KItemModelBase* model)
1387 {
1388 if (m_model == model) {
1389 return;
1390 }
1391
1392 KItemModelBase* previous = m_model;
1393
1394 if (m_model) {
1395 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1396 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1397 disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
1398 this, SLOT(slotItemsInserted(KItemRangeList)));
1399 disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
1400 this, SLOT(slotItemsRemoved(KItemRangeList)));
1401 disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
1402 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
1403 disconnect(m_model, SIGNAL(groupedSortingChanged(bool)),
1404 this, SLOT(slotGroupedSortingChanged(bool)));
1405 disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
1406 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
1407 disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
1408 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
1409 }
1410
1411 m_model = model;
1412 m_layouter->setModel(model);
1413 m_grouped = model->groupedSorting();
1414
1415 if (m_model) {
1416 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1417 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1418 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
1419 this, SLOT(slotItemsInserted(KItemRangeList)));
1420 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
1421 this, SLOT(slotItemsRemoved(KItemRangeList)));
1422 connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
1423 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
1424 connect(m_model, SIGNAL(groupedSortingChanged(bool)),
1425 this, SLOT(slotGroupedSortingChanged(bool)));
1426 connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
1427 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
1428 connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
1429 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
1430 }
1431
1432 onModelChanged(model, previous);
1433 }
1434
1435 KItemListRubberBand* KItemListView::rubberBand() const
1436 {
1437 return m_rubberBand;
1438 }
1439
1440 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
1441 {
1442 if (m_layoutTimer->isActive()) {
1443 m_layoutTimer->stop();
1444 }
1445
1446 if (m_activeTransactions > 0) {
1447 if (hint == NoAnimation) {
1448 // As soon as at least one property change should be done without animation,
1449 // the whole transaction will be marked as not animated.
1450 m_endTransactionAnimationHint = NoAnimation;
1451 }
1452 return;
1453 }
1454
1455 if (!m_model || m_model->count() < 0) {
1456 return;
1457 }
1458
1459 int firstVisibleIndex = m_layouter->firstVisibleIndex();
1460 if (firstVisibleIndex < 0) {
1461 emitOffsetChanges();
1462 return;
1463 }
1464
1465 // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed
1466 // it might be possible that the maximum offset got changed too. Assure that the full visible range
1467 // is still shown if the maximum offset got decreased.
1468 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
1469 const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange;
1470 if (scrollOffset() > maxOffsetToShowFullRange) {
1471 m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange));
1472 firstVisibleIndex = m_layouter->firstVisibleIndex();
1473 }
1474
1475 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
1476
1477 int firstSibblingIndex = -1;
1478 int lastSibblingIndex = -1;
1479 const bool supportsExpanding = supportsItemExpanding();
1480
1481 QList<int> reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint);
1482
1483 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
1484 // instances from invisible items are reused. If no reusable items are
1485 // found then new KItemListWidget instances get created.
1486 const bool animate = (hint == Animation);
1487 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
1488 bool applyNewPos = true;
1489 bool wasHidden = false;
1490
1491 const QRectF itemBounds = m_layouter->itemRect(i);
1492 const QPointF newPos = itemBounds.topLeft();
1493 KItemListWidget* widget = m_visibleItems.value(i);
1494 if (!widget) {
1495 wasHidden = true;
1496 if (!reusableItems.isEmpty()) {
1497 // Reuse a KItemListWidget instance from an invisible item
1498 const int oldIndex = reusableItems.takeLast();
1499 widget = m_visibleItems.value(oldIndex);
1500 setWidgetIndex(widget, i);
1501 updateWidgetProperties(widget, i);
1502 initializeItemListWidget(widget);
1503 } else {
1504 // No reusable KItemListWidget instance is available, create a new one
1505 widget = createWidget(i);
1506 }
1507 widget->resize(itemBounds.size());
1508
1509 if (animate && changedCount < 0) {
1510 // Items have been deleted, move the created item to the
1511 // imaginary old position. They will get animated to the new position
1512 // later.
1513 const QRectF itemRect = m_layouter->itemRect(i - changedCount);
1514 if (itemRect.isEmpty()) {
1515 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
1516 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
1517 widget->setPos(invisibleOldPos);
1518 } else {
1519 widget->setPos(itemRect.topLeft());
1520 }
1521 applyNewPos = false;
1522 }
1523
1524 if (supportsExpanding && changedCount == 0) {
1525 if (firstSibblingIndex < 0) {
1526 firstSibblingIndex = i;
1527 }
1528 lastSibblingIndex = i;
1529 }
1530 }
1531
1532 if (animate) {
1533 const bool itemsRemoved = (changedCount < 0);
1534 const bool itemsInserted = (changedCount > 0);
1535 if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
1536 // The item is located after the removed items. Animate the moving of the position.
1537 applyNewPos = !moveWidget(widget, newPos);
1538 } else if (itemsInserted && i >= changedIndex) {
1539 // The item is located after the first inserted item
1540 if (i <= changedIndex + changedCount - 1) {
1541 // The item is an inserted item. Animate the appearing of the item.
1542 // For performance reasons no animation is done when changedCount is equal
1543 // to all available items.
1544 if (changedCount < m_model->count()) {
1545 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
1546 }
1547 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
1548 // The item was already there before, so animate the moving of the position.
1549 // No moving animation is done if the item is animated by a create animation: This
1550 // prevents a "move animation mess" when inserting several ranges in parallel.
1551 applyNewPos = !moveWidget(widget, newPos);
1552 }
1553 } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
1554 // The size of the view might have been changed. Animate the moving of the position.
1555 applyNewPos = !moveWidget(widget, newPos);
1556 }
1557 } else {
1558 m_animation->stop(widget);
1559 }
1560
1561 if (applyNewPos) {
1562 widget->setPos(newPos);
1563 }
1564
1565 Q_ASSERT(widget->index() == i);
1566 widget->setVisible(true);
1567
1568 if (widget->size() != itemBounds.size()) {
1569 // Resize the widget for the item to the changed size.
1570 if (animate) {
1571 // If a dynamic item size is used then no animation is done in the direction
1572 // of the dynamic size.
1573 if (m_itemSize.width() <= 0) {
1574 // The width is dynamic, apply the new width without animation.
1575 widget->resize(itemBounds.width(), widget->size().height());
1576 } else if (m_itemSize.height() <= 0) {
1577 // The height is dynamic, apply the new height without animation.
1578 widget->resize(widget->size().width(), itemBounds.height());
1579 }
1580 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
1581 } else {
1582 widget->resize(itemBounds.size());
1583 }
1584 }
1585
1586 // Updating the cell-information must be done as last step: The decision whether the
1587 // moving-animation should be started at all is based on the previous cell-information.
1588 const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i));
1589 m_visibleCells.insert(i, cell);
1590 }
1591
1592 // Delete invisible KItemListWidget instances that have not been reused
1593 foreach (int index, reusableItems) {
1594 recycleWidget(m_visibleItems.value(index));
1595 }
1596
1597 if (supportsExpanding && firstSibblingIndex >= 0) {
1598 Q_ASSERT(lastSibblingIndex >= 0);
1599 updateSiblingsInformation(firstSibblingIndex, lastSibblingIndex);
1600 }
1601
1602 if (m_grouped) {
1603 // Update the layout of all visible group headers
1604 QHashIterator<KItemListWidget*, KItemListGroupHeader*> it(m_visibleGroups);
1605 while (it.hasNext()) {
1606 it.next();
1607 updateGroupHeaderLayout(it.key());
1608 }
1609 }
1610
1611 emitOffsetChanges();
1612 }
1613
1614 QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex,
1615 int lastVisibleIndex,
1616 LayoutAnimationHint hint)
1617 {
1618 // Determine all items that are completely invisible and might be
1619 // reused for items that just got (at least partly) visible. If the
1620 // animation hint is set to 'Animation' items that do e.g. an animated
1621 // moving of their position are not marked as invisible: This assures
1622 // that a scrolling inside the view can be done without breaking an animation.
1623
1624 QList<int> items;
1625
1626 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1627 while (it.hasNext()) {
1628 it.next();
1629
1630 KItemListWidget* widget = it.value();
1631 const int index = widget->index();
1632 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
1633
1634 if (invisible) {
1635 if (m_animation->isStarted(widget)) {
1636 if (hint == NoAnimation) {
1637 // Stopping the animation will call KItemListView::slotAnimationFinished()
1638 // and the widget will be recycled if necessary there.
1639 m_animation->stop(widget);
1640 }
1641 } else {
1642 widget->setVisible(false);
1643 items.append(index);
1644
1645 if (m_grouped) {
1646 recycleGroupHeaderForWidget(widget);
1647 }
1648 }
1649 }
1650 }
1651
1652 return items;
1653 }
1654
1655 bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos)
1656 {
1657 if (widget->pos() == newPos) {
1658 return false;
1659 }
1660
1661 bool startMovingAnim = false;
1662
1663 // When having a grid the moving-animation should only be started, if it is done within
1664 // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation.
1665 // Otherwise instead of a moving-animation a create-animation on the new position will be used
1666 // instead. This is done to prevent overlapping (and confusing) moving-animations.
1667 const int index = widget->index();
1668 const Cell cell = m_visibleCells.value(index);
1669 if (cell.column >= 0 && cell.row >= 0) {
1670 if (scrollOrientation() == Qt::Vertical) {
1671 startMovingAnim = (cell.row == m_layouter->itemRow(index));
1672 } else {
1673 startMovingAnim = (cell.column == m_layouter->itemColumn(index));
1674 }
1675 }
1676
1677 if (startMovingAnim) {
1678 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
1679 return true;
1680 }
1681
1682 m_animation->stop(widget);
1683 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
1684 return false;
1685 }
1686
1687 void KItemListView::emitOffsetChanges()
1688 {
1689 const qreal newScrollOffset = m_layouter->scrollOffset();
1690 if (m_oldScrollOffset != newScrollOffset) {
1691 emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
1692 m_oldScrollOffset = newScrollOffset;
1693 }
1694
1695 const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset();
1696 if (m_oldMaximumScrollOffset != newMaximumScrollOffset) {
1697 emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
1698 m_oldMaximumScrollOffset = newMaximumScrollOffset;
1699 }
1700
1701 const qreal newItemOffset = m_layouter->itemOffset();
1702 if (m_oldItemOffset != newItemOffset) {
1703 emit itemOffsetChanged(newItemOffset, m_oldItemOffset);
1704 m_oldItemOffset = newItemOffset;
1705 }
1706
1707 const qreal newMaximumItemOffset = m_layouter->maximumItemOffset();
1708 if (m_oldMaximumItemOffset != newMaximumItemOffset) {
1709 emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
1710 m_oldMaximumItemOffset = newMaximumItemOffset;
1711 }
1712 }
1713
1714 KItemListWidget* KItemListView::createWidget(int index)
1715 {
1716 KItemListWidget* widget = m_widgetCreator->create(this);
1717 widget->setFlag(QGraphicsItem::ItemStacksBehindParent);
1718
1719 m_visibleItems.insert(index, widget);
1720 m_visibleCells.insert(index, Cell());
1721 updateWidgetProperties(widget, index);
1722 initializeItemListWidget(widget);
1723 return widget;
1724 }
1725
1726 void KItemListView::recycleWidget(KItemListWidget* widget)
1727 {
1728 if (m_grouped) {
1729 recycleGroupHeaderForWidget(widget);
1730 }
1731
1732 const int index = widget->index();
1733 m_visibleItems.remove(index);
1734 m_visibleCells.remove(index);
1735
1736 m_widgetCreator->recycle(widget);
1737 }
1738
1739 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
1740 {
1741 const int oldIndex = widget->index();
1742 m_visibleItems.remove(oldIndex);
1743 m_visibleCells.remove(oldIndex);
1744
1745 m_visibleItems.insert(index, widget);
1746 m_visibleCells.insert(index, Cell());
1747
1748 widget->setIndex(index);
1749 }
1750
1751 void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index)
1752 {
1753 const int oldIndex = widget->index();
1754 const Cell oldCell = m_visibleCells.value(oldIndex);
1755
1756 setWidgetIndex(widget, index);
1757
1758 const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index));
1759 const bool vertical = (scrollOrientation() == Qt::Vertical);
1760 const bool updateCell = (vertical && oldCell.row == newCell.row) ||
1761 (!vertical && oldCell.column == newCell.column);
1762 if (updateCell) {
1763 m_visibleCells.insert(index, newCell);
1764 }
1765 }
1766
1767 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1768 {
1769 switch (sizeType) {
1770 case LayouterSize: m_layouter->setSize(size); break;
1771 case ItemSize: m_layouter->setItemSize(size); break;
1772 default: break;
1773 }
1774 }
1775
1776 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1777 {
1778 widget->setVisibleRoles(m_visibleRoles);
1779 updateWidgetColumnWidths(widget);
1780 widget->setStyleOption(m_styleOption);
1781
1782 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1783 widget->setCurrent(index == selectionManager->currentItem());
1784 widget->setSelected(selectionManager->isSelected(index));
1785 widget->setHovered(false);
1786 widget->setEnabledSelectionToggle(enabledSelectionToggles());
1787 widget->setIndex(index);
1788 widget->setData(m_model->data(index));
1789 widget->setSiblingsInformation(QBitArray());
1790 updateAlternateBackgroundForWidget(widget);
1791
1792 if (m_grouped) {
1793 updateGroupHeaderForWidget(widget);
1794 }
1795 }
1796
1797 void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget)
1798 {
1799 Q_ASSERT(m_grouped);
1800
1801 const int index = widget->index();
1802 if (!m_layouter->isFirstGroupItem(index)) {
1803 // The widget does not represent the first item of a group
1804 // and hence requires no header
1805 recycleGroupHeaderForWidget(widget);
1806 return;
1807 }
1808
1809 const QList<QPair<int, QVariant> > groups = model()->groups();
1810 if (groups.isEmpty()) {
1811 return;
1812 }
1813
1814 KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
1815 if (!groupHeader) {
1816 groupHeader = m_groupHeaderCreator->create(this);
1817 groupHeader->setParentItem(widget);
1818 m_visibleGroups.insert(widget, groupHeader);
1819 connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged()));
1820 }
1821 Q_ASSERT(groupHeader->parentItem() == widget);
1822
1823 const int groupIndex = groupIndexForItem(index);
1824 Q_ASSERT(groupIndex >= 0);
1825 groupHeader->setData(groups.at(groupIndex).second);
1826 groupHeader->setRole(model()->sortRole());
1827 groupHeader->setStyleOption(m_styleOption);
1828 groupHeader->setScrollOrientation(scrollOrientation());
1829 groupHeader->setItemIndex(index);
1830
1831 groupHeader->show();
1832 }
1833
1834 void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget)
1835 {
1836 KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
1837 Q_ASSERT(groupHeader);
1838
1839 const int index = widget->index();
1840 const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index);
1841 const QRectF itemRect = m_layouter->itemRect(index);
1842
1843 // The group-header is a child of the itemlist widget. Translate the
1844 // group header position to the relative position.
1845 if (scrollOrientation() == Qt::Vertical) {
1846 // In the vertical scroll orientation the group header should always span
1847 // the whole width no matter which temporary position the parent widget
1848 // has. In this case the x-position and width will be adjusted manually.
1849 groupHeader->setPos(-widget->x(), -groupHeaderRect.height());
1850 groupHeader->resize(size().width(), groupHeaderRect.size().height());
1851 } else {
1852 groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y());
1853 groupHeader->resize(groupHeaderRect.size());
1854 }
1855 }
1856
1857 void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget)
1858 {
1859 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1860 if (header) {
1861 header->setParentItem(0);
1862 m_groupHeaderCreator->recycle(header);
1863 m_visibleGroups.remove(widget);
1864 disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged()));
1865 }
1866 }
1867
1868 void KItemListView::updateVisibleGroupHeaders()
1869 {
1870 Q_ASSERT(m_grouped);
1871 m_layouter->markAsDirty();
1872
1873 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1874 while (it.hasNext()) {
1875 it.next();
1876 updateGroupHeaderForWidget(it.value());
1877 }
1878 }
1879
1880 int KItemListView::groupIndexForItem(int index) const
1881 {
1882 Q_ASSERT(m_grouped);
1883
1884 const QList<QPair<int, QVariant> > groups = model()->groups();
1885 if (groups.isEmpty()) {
1886 return -1;
1887 }
1888
1889 int min = 0;
1890 int max = groups.count() - 1;
1891 int mid = 0;
1892 do {
1893 mid = (min + max) / 2;
1894 if (index > groups[mid].first) {
1895 min = mid + 1;
1896 } else {
1897 max = mid - 1;
1898 }
1899 } while (groups[mid].first != index && min <= max);
1900
1901 if (min > max) {
1902 while (groups[mid].first > index && mid > 0) {
1903 --mid;
1904 }
1905 }
1906
1907 return mid;
1908 }
1909
1910 void KItemListView::updateAlternateBackgrounds()
1911 {
1912 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1913 while (it.hasNext()) {
1914 it.next();
1915 updateAlternateBackgroundForWidget(it.value());
1916 }
1917 }
1918
1919 void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget)
1920 {
1921 bool enabled = useAlternateBackgrounds();
1922 if (enabled) {
1923 const int index = widget->index();
1924 enabled = (index & 0x1) > 0;
1925 if (m_grouped) {
1926 const int groupIndex = groupIndexForItem(index);
1927 if (groupIndex >= 0) {
1928 const QList<QPair<int, QVariant> > groups = model()->groups();
1929 const int indexOfFirstGroupItem = groups[groupIndex].first;
1930 const int relativeIndex = index - indexOfFirstGroupItem;
1931 enabled = (relativeIndex & 0x1) > 0;
1932 }
1933 }
1934 }
1935 widget->setAlternateBackground(enabled);
1936 }
1937
1938 bool KItemListView::useAlternateBackgrounds() const
1939 {
1940 return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
1941 }
1942
1943 QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
1944 {
1945 QElapsedTimer timer;
1946 timer.start();
1947
1948 QHash<QByteArray, qreal> widths;
1949
1950 // Calculate the minimum width for each column that is required
1951 // to show the headline unclipped.
1952 const QFontMetricsF fontMetrics(m_headerWidget->font());
1953 const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin);
1954 const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
1955 foreach (const QByteArray& visibleRole, visibleRoles()) {
1956 const QString headerText = m_model->roleDescription(visibleRole);
1957 const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
1958 widths.insert(visibleRole, headerWidth);
1959 }
1960
1961 // Calculate the preferred column withs for each item and ignore values
1962 // smaller than the width for showing the headline unclipped.
1963 int calculatedItemCount = 0;
1964 bool maxTimeExceeded = false;
1965 foreach (const KItemRange& itemRange, itemRanges) {
1966 const int startIndex = itemRange.index;
1967 const int endIndex = startIndex + itemRange.count - 1;
1968
1969 for (int i = startIndex; i <= endIndex; ++i) {
1970 foreach (const QByteArray& visibleRole, visibleRoles()) {
1971 qreal maxWidth = widths.value(visibleRole, 0);
1972 const qreal width = m_widgetCreator->preferredRoleColumnWidth(visibleRole, i, this);
1973 maxWidth = qMax(width, maxWidth);
1974 widths.insert(visibleRole, maxWidth);
1975 }
1976
1977 if (calculatedItemCount > 100 && timer.elapsed() > 200) {
1978 // When having several thousands of items calculating the sizes can get
1979 // very expensive. We accept a possibly too small role-size in favour
1980 // of having no blocking user interface.
1981 maxTimeExceeded = true;
1982 break;
1983 }
1984 ++calculatedItemCount;
1985 }
1986 if (maxTimeExceeded) {
1987 break;
1988 }
1989 }
1990
1991 return widths;
1992 }
1993
1994 void KItemListView::applyColumnWidthsFromHeader()
1995 {
1996 // Apply the new size to the layouter
1997 const qreal requiredWidth = columnWidthsSum();
1998 const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
1999 m_itemSize.height());
2000 m_layouter->setItemSize(dynamicItemSize);
2001
2002 // Update the role sizes for all visible widgets
2003 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
2004 while (it.hasNext()) {
2005 it.next();
2006 updateWidgetColumnWidths(it.value());
2007 }
2008 }
2009
2010 void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget)
2011 {
2012 foreach (const QByteArray& role, m_visibleRoles) {
2013 widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
2014 }
2015 }
2016
2017 void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges)
2018 {
2019 Q_ASSERT(m_itemSize.isEmpty());
2020 const int itemCount = m_model->count();
2021 int rangesItemCount = 0;
2022 foreach (const KItemRange& range, itemRanges) {
2023 rangesItemCount += range.count;
2024 }
2025
2026 if (itemCount == rangesItemCount) {
2027 const QHash<QByteArray, qreal> preferredWidths = preferredColumnWidths(itemRanges);
2028 foreach (const QByteArray& role, m_visibleRoles) {
2029 m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role));
2030 }
2031 } else {
2032 // Only a sub range of the roles need to be determined.
2033 // The chances are good that the widths of the sub ranges
2034 // already fit into the available widths and hence no
2035 // expensive update might be required.
2036 bool changed = false;
2037
2038 const QHash<QByteArray, qreal> updatedWidths = preferredColumnWidths(itemRanges);
2039 QHashIterator<QByteArray, qreal> it(updatedWidths);
2040 while (it.hasNext()) {
2041 it.next();
2042 const QByteArray& role = it.key();
2043 const qreal updatedWidth = it.value();
2044 const qreal currentWidth = m_headerWidget->preferredColumnWidth(role);
2045 if (updatedWidth > currentWidth) {
2046 m_headerWidget->setPreferredColumnWidth(role, updatedWidth);
2047 changed = true;
2048 }
2049 }
2050
2051 if (!changed) {
2052 // All the updated sizes are smaller than the current sizes and no change
2053 // of the stretched roles-widths is required
2054 return;
2055 }
2056 }
2057
2058 if (m_headerWidget->automaticColumnResizing()) {
2059 applyAutomaticColumnWidths();
2060 }
2061 }
2062
2063 void KItemListView::updatePreferredColumnWidths()
2064 {
2065 if (m_model) {
2066 updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count()));
2067 }
2068 }
2069
2070 void KItemListView::applyAutomaticColumnWidths()
2071 {
2072 Q_ASSERT(m_itemSize.isEmpty());
2073 Q_ASSERT(m_headerWidget->automaticColumnResizing());
2074
2075 // Calculate the maximum size of an item by considering the
2076 // visible role sizes and apply them to the layouter. If the
2077 // size does not use the available view-size the size of the
2078 // first role will get stretched.
2079
2080 foreach (const QByteArray& role, m_visibleRoles) {
2081 const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role);
2082 m_headerWidget->setColumnWidth(role, preferredWidth);
2083 }
2084
2085 const QByteArray firstRole = m_visibleRoles.first();
2086 qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
2087 QSizeF dynamicItemSize = m_itemSize;
2088
2089 qreal requiredWidth = columnWidthsSum();
2090 const qreal availableWidth = size().width();
2091 if (requiredWidth < availableWidth) {
2092 // Stretch the first column to use the whole remaining width
2093 firstColumnWidth += availableWidth - requiredWidth;
2094 m_headerWidget->setColumnWidth(firstRole, firstColumnWidth);
2095 } else if (requiredWidth > availableWidth) {
2096 // Shrink the first column to be able to show as much other
2097 // columns as possible
2098 qreal shrinkedFirstColumnWidth = firstColumnWidth - requiredWidth + availableWidth;
2099
2100 // TODO: A proper calculation of the minimum width depends on the implementation
2101 // of KItemListWidget. Probably a kind of minimum size-hint should be introduced
2102 // later.
2103 const qreal minWidth = qMin(firstColumnWidth, qreal(m_styleOption.iconSize * 2 + 200));
2104 if (shrinkedFirstColumnWidth < minWidth) {
2105 shrinkedFirstColumnWidth = minWidth;
2106 }
2107
2108 m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth);
2109 requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth;
2110 }
2111
2112 dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth);
2113
2114 m_layouter->setItemSize(dynamicItemSize);
2115
2116 // Update the role sizes for all visible widgets
2117 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
2118 while (it.hasNext()) {
2119 it.next();
2120 updateWidgetColumnWidths(it.value());
2121 }
2122 }
2123
2124 qreal KItemListView::columnWidthsSum() const
2125 {
2126 qreal widthsSum = 0;
2127 foreach (const QByteArray& role, m_visibleRoles) {
2128 widthsSum += m_headerWidget->columnWidth(role);
2129 }
2130 return widthsSum;
2131 }
2132
2133 QRectF KItemListView::headerBoundaries() const
2134 {
2135 return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF();
2136 }
2137
2138 bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize,
2139 const QSizeF& newItemSize,
2140 const QSizeF& newItemMargin) const
2141 {
2142 if (newItemSize.isEmpty() || newGridSize.isEmpty()) {
2143 return false;
2144 }
2145
2146 if (m_layouter->scrollOrientation() == Qt::Vertical) {
2147 const qreal itemWidth = m_layouter->itemSize().width();
2148 if (itemWidth > 0) {
2149 const int newColumnCount = itemsPerSize(newGridSize.width(),
2150 newItemSize.width(),
2151 newItemMargin.width());
2152 if (m_model->count() > newColumnCount) {
2153 const int oldColumnCount = itemsPerSize(m_layouter->size().width(),
2154 itemWidth,
2155 m_layouter->itemMargin().width());
2156 return oldColumnCount != newColumnCount;
2157 }
2158 }
2159 } else {
2160 const qreal itemHeight = m_layouter->itemSize().height();
2161 if (itemHeight > 0) {
2162 const int newRowCount = itemsPerSize(newGridSize.height(),
2163 newItemSize.height(),
2164 newItemMargin.height());
2165 if (m_model->count() > newRowCount) {
2166 const int oldRowCount = itemsPerSize(m_layouter->size().height(),
2167 itemHeight,
2168 m_layouter->itemMargin().height());
2169 return oldRowCount != newRowCount;
2170 }
2171 }
2172 }
2173
2174 return false;
2175 }
2176
2177 bool KItemListView::animateChangedItemCount(int changedItemCount) const
2178 {
2179 if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) {
2180 return false;
2181 }
2182
2183 const int maximum = (scrollOrientation() == Qt::Vertical)
2184 ? m_layouter->size().width() / m_layouter->itemSize().width()
2185 : m_layouter->size().height() / m_layouter->itemSize().height();
2186 // Only animate if up to 2/3 of a row or column are inserted or removed
2187 return changedItemCount <= maximum * 2 / 3;
2188 }
2189
2190
2191 bool KItemListView::scrollBarRequired(const QSizeF& size) const
2192 {
2193 const QSizeF oldSize = m_layouter->size();
2194
2195 m_layouter->setSize(size);
2196 const qreal maxOffset = m_layouter->maximumScrollOffset();
2197 m_layouter->setSize(oldSize);
2198
2199 return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height()
2200 : maxOffset > size.width();
2201 }
2202
2203 void KItemListView::updateGroupHeaderHeight()
2204 {
2205 qreal groupHeaderHeight = m_styleOption.fontMetrics.height();
2206 qreal groupHeaderMargin = 0;
2207
2208 if (scrollOrientation() == Qt::Horizontal) {
2209 // The vertical margin above and below the header should be
2210 // equal to the horizontal margin, not the vertical margin
2211 // from m_styleOption.
2212 groupHeaderHeight += 2 * m_styleOption.horizontalMargin;
2213 groupHeaderMargin = m_styleOption.horizontalMargin;
2214 } else if (m_itemSize.isEmpty()){
2215 groupHeaderHeight += 4 * m_styleOption.padding;
2216 groupHeaderMargin = m_styleOption.iconSize / 2;
2217 } else {
2218 groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin;
2219 groupHeaderMargin = m_styleOption.iconSize / 4;
2220 }
2221 m_layouter->setGroupHeaderHeight(groupHeaderHeight);
2222 m_layouter->setGroupHeaderMargin(groupHeaderMargin);
2223
2224 updateVisibleGroupHeaders();
2225 }
2226
2227 void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
2228 {
2229 if (!supportsItemExpanding() || !m_model) {
2230 return;
2231 }
2232
2233 if (firstIndex < 0 || lastIndex < 0) {
2234 firstIndex = m_layouter->firstVisibleIndex();
2235 lastIndex = m_layouter->lastVisibleIndex();
2236 } else {
2237 const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() &&
2238 lastIndex >= m_layouter->firstVisibleIndex());
2239 if (!isRangeVisible) {
2240 return;
2241 }
2242 }
2243
2244 int previousParents = 0;
2245 QBitArray previousSiblings;
2246
2247 // The rootIndex describes the first index where the siblings get
2248 // calculated from. For the calculation the upper most parent item
2249 // is required. For performance reasons it is checked first whether
2250 // the visible items before or after the current range already
2251 // contain a siblings information which can be used as base.
2252 int rootIndex = firstIndex;
2253
2254 KItemListWidget* widget = m_visibleItems.value(firstIndex - 1);
2255 if (!widget) {
2256 // There is no visible widget before the range, check whether there
2257 // is one after the range:
2258 widget = m_visibleItems.value(lastIndex + 1);
2259 if (widget) {
2260 // The sibling information of the widget may only be used if
2261 // all items of the range have the same number of parents.
2262 const int parents = m_model->expandedParentsCount(lastIndex + 1);
2263 for (int i = lastIndex; i >= firstIndex; --i) {
2264 if (m_model->expandedParentsCount(i) != parents) {
2265 widget = 0;
2266 break;
2267 }
2268 }
2269 }
2270 }
2271
2272 if (widget) {
2273 // Performance optimization: Use the sibling information of the visible
2274 // widget beside the given range.
2275 previousSiblings = widget->siblingsInformation();
2276 if (previousSiblings.isEmpty()) {
2277 return;
2278 }
2279 previousParents = previousSiblings.count() - 1;
2280 previousSiblings.truncate(previousParents);
2281 } else {
2282 // Potentially slow path: Go back to the upper most parent of firstIndex
2283 // to be able to calculate the initial value for the siblings.
2284 while (rootIndex > 0 && m_model->expandedParentsCount(rootIndex) > 0) {
2285 --rootIndex;
2286 }
2287 }
2288
2289 Q_ASSERT(previousParents >= 0);
2290 for (int i = rootIndex; i <= lastIndex; ++i) {
2291 // Update the parent-siblings in case if the current item represents
2292 // a child or an upper parent.
2293 const int currentParents = m_model->expandedParentsCount(i);
2294 Q_ASSERT(currentParents >= 0);
2295 if (previousParents < currentParents) {
2296 previousParents = currentParents;
2297 previousSiblings.resize(currentParents);
2298 previousSiblings.setBit(currentParents - 1, hasSiblingSuccessor(i - 1));
2299 } else if (previousParents > currentParents) {
2300 previousParents = currentParents;
2301 previousSiblings.truncate(currentParents);
2302 }
2303
2304 if (i >= firstIndex) {
2305 // The index represents a visible item. Apply the parent-siblings
2306 // and update the sibling of the current item.
2307 KItemListWidget* widget = m_visibleItems.value(i);
2308 if (!widget) {
2309 continue;
2310 }
2311
2312 QBitArray siblings = previousSiblings;
2313 siblings.resize(siblings.count() + 1);
2314 siblings.setBit(siblings.count() - 1, hasSiblingSuccessor(i));
2315
2316 widget->setSiblingsInformation(siblings);
2317 }
2318 }
2319 }
2320
2321 bool KItemListView::hasSiblingSuccessor(int index) const
2322 {
2323 bool hasSuccessor = false;
2324 const int parentsCount = m_model->expandedParentsCount(index);
2325 int successorIndex = index + 1;
2326
2327 // Search the next sibling
2328 const int itemCount = m_model->count();
2329 while (successorIndex < itemCount) {
2330 const int currentParentsCount = m_model->expandedParentsCount(successorIndex);
2331 if (currentParentsCount == parentsCount) {
2332 hasSuccessor = true;
2333 break;
2334 } else if (currentParentsCount < parentsCount) {
2335 break;
2336 }
2337 ++successorIndex;
2338 }
2339
2340 if (m_grouped && hasSuccessor) {
2341 // If the sibling is part of another group, don't mark it as
2342 // successor as the group header is between the sibling connections.
2343 for (int i = index + 1; i <= successorIndex; ++i) {
2344 if (m_layouter->isFirstGroupItem(i)) {
2345 hasSuccessor = false;
2346 break;
2347 }
2348 }
2349 }
2350
2351 return hasSuccessor;
2352 }
2353
2354 int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc)
2355 {
2356 int inc = 0;
2357
2358 const int minSpeed = 4;
2359 const int maxSpeed = 128;
2360 const int speedLimiter = 96;
2361 const int autoScrollBorder = 64;
2362
2363 // Limit the increment that is allowed to be added in comparison to 'oldInc'.
2364 // This assures that the autoscrolling speed grows gradually.
2365 const int incLimiter = 1;
2366
2367 if (pos < autoScrollBorder) {
2368 inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter;
2369 inc = qMax(inc, -maxSpeed);
2370 inc = qMax(inc, oldInc - incLimiter);
2371 } else if (pos > range - autoScrollBorder) {
2372 inc = minSpeed + qAbs(pos - range + autoScrollBorder) * (pos - range + autoScrollBorder) / speedLimiter;
2373 inc = qMin(inc, maxSpeed);
2374 inc = qMin(inc, oldInc + incLimiter);
2375 }
2376
2377 return inc;
2378 }
2379
2380 int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin)
2381 {
2382 const qreal availableSize = size - itemMargin;
2383 const int count = availableSize / (itemSize + itemMargin);
2384 return count;
2385 }
2386
2387
2388
2389 KItemListCreatorBase::~KItemListCreatorBase()
2390 {
2391 qDeleteAll(m_recycleableWidgets);
2392 qDeleteAll(m_createdWidgets);
2393 }
2394
2395 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
2396 {
2397 m_createdWidgets.insert(widget);
2398 }
2399
2400 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
2401 {
2402 Q_ASSERT(m_createdWidgets.contains(widget));
2403 m_createdWidgets.remove(widget);
2404
2405 if (m_recycleableWidgets.count() < 100) {
2406 m_recycleableWidgets.append(widget);
2407 widget->setVisible(false);
2408 } else {
2409 delete widget;
2410 }
2411 }
2412
2413 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
2414 {
2415 if (m_recycleableWidgets.isEmpty()) {
2416 return 0;
2417 }
2418
2419 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
2420 m_createdWidgets.insert(widget);
2421 return widget;
2422 }
2423
2424 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
2425 {
2426 }
2427
2428 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
2429 {
2430 widget->setParentItem(0);
2431 widget->setOpacity(1.0);
2432 pushRecycleableWidget(widget);
2433 }
2434
2435 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
2436 {
2437 }
2438
2439 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
2440 {
2441 header->setOpacity(1.0);
2442 pushRecycleableWidget(header);
2443 }
2444
2445 #include "kitemlistview.moc"