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