File indexing completed on 2024-04-28 05:45:09
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com> 0003 * 0004 * Based on the Itemviews NG project from Trolltech Labs 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kitemlistview.h" 0010 0011 #include "dolphindebug.h" 0012 #include "kitemlistcontainer.h" 0013 #include "kitemlistcontroller.h" 0014 #include "kitemlistheader.h" 0015 #include "kitemlistselectionmanager.h" 0016 #include "kitemlistviewaccessible.h" 0017 #include "kstandarditemlistwidget.h" 0018 0019 #include "private/kitemlistheaderwidget.h" 0020 #include "private/kitemlistrubberband.h" 0021 #include "private/kitemlistsizehintresolver.h" 0022 #include "private/kitemlistviewlayouter.h" 0023 0024 #include <QElapsedTimer> 0025 #include <QGraphicsSceneMouseEvent> 0026 #include <QGraphicsView> 0027 #include <QPropertyAnimation> 0028 #include <QStyleOptionRubberBand> 0029 #include <QTimer> 0030 #include <QVariantAnimation> 0031 0032 namespace 0033 { 0034 // Time in ms until reaching the autoscroll margin triggers 0035 // an initial autoscrolling 0036 const int InitialAutoScrollDelay = 700; 0037 0038 // Delay in ms for triggering the next autoscroll 0039 const int RepeatingAutoScrollDelay = 1000 / 60; 0040 0041 // Copied from the Kirigami.Units.shortDuration 0042 const int RubberFadeSpeed = 150; 0043 0044 const char *RubberPropertyName = "_kitemviews_rubberBandPosition"; 0045 } 0046 0047 #ifndef QT_NO_ACCESSIBILITY 0048 QAccessibleInterface *accessibleInterfaceFactory(const QString &key, QObject *object) 0049 { 0050 Q_UNUSED(key) 0051 0052 if (KItemListContainer *container = qobject_cast<KItemListContainer *>(object)) { 0053 if (auto controller = container->controller(); controller) { 0054 if (KItemListView *view = controller->view(); view && view->accessibleParent()) { 0055 return view->accessibleParent(); 0056 } 0057 } 0058 return new KItemListContainerAccessible(container); 0059 } else if (KItemListView *view = qobject_cast<KItemListView *>(object)) { 0060 return new KItemListViewAccessible(view, view->accessibleParent()); 0061 } 0062 0063 return nullptr; 0064 } 0065 #endif 0066 0067 KItemListView::KItemListView(QGraphicsWidget *parent) 0068 : QGraphicsWidget(parent) 0069 , m_enabledSelectionToggles(false) 0070 , m_grouped(false) 0071 , m_highlightEntireRow(false) 0072 , m_alternateBackgrounds(false) 0073 , m_supportsItemExpanding(false) 0074 , m_editingRole(false) 0075 , m_activeTransactions(0) 0076 , m_endTransactionAnimationHint(Animation) 0077 , m_itemSize() 0078 , m_controller(nullptr) 0079 , m_model(nullptr) 0080 , m_visibleRoles() 0081 , m_widgetCreator(nullptr) 0082 , m_groupHeaderCreator(nullptr) 0083 , m_styleOption() 0084 , m_visibleItems() 0085 , m_visibleGroups() 0086 , m_visibleCells() 0087 , m_scrollBarExtent(0) 0088 , m_layouter(nullptr) 0089 , m_animation(nullptr) 0090 , m_oldScrollOffset(0) 0091 , m_oldMaximumScrollOffset(0) 0092 , m_oldItemOffset(0) 0093 , m_oldMaximumItemOffset(0) 0094 , m_skipAutoScrollForRubberBand(false) 0095 , m_rubberBand(nullptr) 0096 , m_tapAndHoldIndicator(nullptr) 0097 , m_mousePos() 0098 , m_autoScrollIncrement(0) 0099 , m_autoScrollTimer(nullptr) 0100 , m_header(nullptr) 0101 , m_headerWidget(nullptr) 0102 , m_indicatorAnimation(nullptr) 0103 , m_dropIndicator() 0104 , m_sizeHintResolver(nullptr) 0105 { 0106 setAcceptHoverEvents(true); 0107 setAcceptTouchEvents(true); 0108 0109 m_sizeHintResolver = new KItemListSizeHintResolver(this); 0110 0111 m_layouter = new KItemListViewLayouter(m_sizeHintResolver, this); 0112 0113 m_animation = new KItemListViewAnimation(this); 0114 connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished); 0115 0116 m_rubberBand = new KItemListRubberBand(this); 0117 connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); 0118 0119 m_tapAndHoldIndicator = new KItemListRubberBand(this); 0120 m_indicatorAnimation = new QPropertyAnimation(m_tapAndHoldIndicator, "endPosition", this); 0121 connect(m_tapAndHoldIndicator, &KItemListRubberBand::activationChanged, this, [this](bool active) { 0122 if (active) { 0123 m_indicatorAnimation->setDuration(150); 0124 m_indicatorAnimation->setStartValue(QPointF(1, 1)); 0125 m_indicatorAnimation->setEndValue(QPointF(40, 40)); 0126 m_indicatorAnimation->start(); 0127 } 0128 update(); 0129 }); 0130 connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() { 0131 if (m_tapAndHoldIndicator->isActive()) { 0132 update(); 0133 } 0134 }); 0135 0136 m_headerWidget = new KItemListHeaderWidget(this); 0137 m_headerWidget->setVisible(false); 0138 0139 m_header = new KItemListHeader(this); 0140 0141 #ifndef QT_NO_ACCESSIBILITY 0142 QAccessible::installFactory(accessibleInterfaceFactory); 0143 #endif 0144 } 0145 0146 KItemListView::~KItemListView() 0147 { 0148 // The group headers are children of the widgets created by 0149 // widgetCreator(). So it is mandatory to delete the group headers 0150 // first. 0151 delete m_groupHeaderCreator; 0152 m_groupHeaderCreator = nullptr; 0153 0154 delete m_widgetCreator; 0155 m_widgetCreator = nullptr; 0156 0157 delete m_sizeHintResolver; 0158 m_sizeHintResolver = nullptr; 0159 } 0160 0161 void KItemListView::setScrollOffset(qreal offset) 0162 { 0163 if (offset < 0) { 0164 offset = 0; 0165 } 0166 0167 const qreal previousOffset = m_layouter->scrollOffset(); 0168 if (offset == previousOffset) { 0169 return; 0170 } 0171 0172 m_layouter->setScrollOffset(offset); 0173 m_animation->setScrollOffset(offset); 0174 0175 // Don't check whether the m_layoutTimer is active: Changing the 0176 // scroll offset must always trigger a synchronous layout, otherwise 0177 // the smooth-scrolling might get jerky. 0178 doLayout(NoAnimation); 0179 onScrollOffsetChanged(offset, previousOffset); 0180 } 0181 0182 qreal KItemListView::scrollOffset() const 0183 { 0184 return m_layouter->scrollOffset(); 0185 } 0186 0187 qreal KItemListView::maximumScrollOffset() const 0188 { 0189 return m_layouter->maximumScrollOffset(); 0190 } 0191 0192 void KItemListView::setItemOffset(qreal offset) 0193 { 0194 if (m_layouter->itemOffset() == offset) { 0195 return; 0196 } 0197 0198 m_layouter->setItemOffset(offset); 0199 if (m_headerWidget->isVisible()) { 0200 m_headerWidget->setOffset(offset); 0201 } 0202 0203 // Don't check whether the m_layoutTimer is active: Changing the 0204 // item offset must always trigger a synchronous layout, otherwise 0205 // the smooth-scrolling might get jerky. 0206 doLayout(NoAnimation); 0207 } 0208 0209 qreal KItemListView::itemOffset() const 0210 { 0211 return m_layouter->itemOffset(); 0212 } 0213 0214 qreal KItemListView::maximumItemOffset() const 0215 { 0216 return m_layouter->maximumItemOffset(); 0217 } 0218 0219 int KItemListView::maximumVisibleItems() const 0220 { 0221 return m_layouter->maximumVisibleItems(); 0222 } 0223 0224 void KItemListView::setVisibleRoles(const QList<QByteArray> &roles) 0225 { 0226 const QList<QByteArray> previousRoles = m_visibleRoles; 0227 m_visibleRoles = roles; 0228 onVisibleRolesChanged(roles, previousRoles); 0229 0230 m_sizeHintResolver->clearCache(); 0231 m_layouter->markAsDirty(); 0232 0233 if (m_itemSize.isEmpty()) { 0234 m_headerWidget->setColumns(roles); 0235 updatePreferredColumnWidths(); 0236 if (!m_headerWidget->automaticColumnResizing()) { 0237 // The column-width of new roles are still 0. Apply the preferred 0238 // column-width as default with. 0239 for (const QByteArray &role : std::as_const(m_visibleRoles)) { 0240 if (m_headerWidget->columnWidth(role) == 0) { 0241 const qreal width = m_headerWidget->preferredColumnWidth(role); 0242 m_headerWidget->setColumnWidth(role, width); 0243 } 0244 } 0245 0246 applyColumnWidthsFromHeader(); 0247 } 0248 } 0249 0250 const bool alternateBackgroundsChanged = 0251 m_itemSize.isEmpty() && ((roles.count() > 1 && previousRoles.count() <= 1) || (roles.count() <= 1 && previousRoles.count() > 1)); 0252 0253 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 0254 while (it.hasNext()) { 0255 it.next(); 0256 KItemListWidget *widget = it.value(); 0257 widget->setVisibleRoles(roles); 0258 if (alternateBackgroundsChanged) { 0259 updateAlternateBackgroundForWidget(widget); 0260 } 0261 } 0262 0263 doLayout(NoAnimation); 0264 } 0265 0266 QList<QByteArray> KItemListView::visibleRoles() const 0267 { 0268 return m_visibleRoles; 0269 } 0270 0271 void KItemListView::setAutoScroll(bool enabled) 0272 { 0273 if (enabled && !m_autoScrollTimer) { 0274 m_autoScrollTimer = new QTimer(this); 0275 m_autoScrollTimer->setSingleShot(true); 0276 connect(m_autoScrollTimer, &QTimer::timeout, this, &KItemListView::triggerAutoScrolling); 0277 m_autoScrollTimer->start(InitialAutoScrollDelay); 0278 } else if (!enabled && m_autoScrollTimer) { 0279 delete m_autoScrollTimer; 0280 m_autoScrollTimer = nullptr; 0281 } 0282 } 0283 0284 bool KItemListView::autoScroll() const 0285 { 0286 return m_autoScrollTimer != nullptr; 0287 } 0288 0289 void KItemListView::setEnabledSelectionToggles(bool enabled) 0290 { 0291 if (m_enabledSelectionToggles != enabled) { 0292 m_enabledSelectionToggles = enabled; 0293 0294 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 0295 while (it.hasNext()) { 0296 it.next(); 0297 it.value()->setEnabledSelectionToggle(enabled); 0298 } 0299 } 0300 } 0301 0302 bool KItemListView::enabledSelectionToggles() const 0303 { 0304 return m_enabledSelectionToggles; 0305 } 0306 0307 KItemListController *KItemListView::controller() const 0308 { 0309 return m_controller; 0310 } 0311 0312 KItemModelBase *KItemListView::model() const 0313 { 0314 return m_model; 0315 } 0316 0317 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase *widgetCreator) 0318 { 0319 delete m_widgetCreator; 0320 m_widgetCreator = widgetCreator; 0321 } 0322 0323 KItemListWidgetCreatorBase *KItemListView::widgetCreator() const 0324 { 0325 if (!m_widgetCreator) { 0326 m_widgetCreator = defaultWidgetCreator(); 0327 } 0328 return m_widgetCreator; 0329 } 0330 0331 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase *groupHeaderCreator) 0332 { 0333 delete m_groupHeaderCreator; 0334 m_groupHeaderCreator = groupHeaderCreator; 0335 } 0336 0337 KItemListGroupHeaderCreatorBase *KItemListView::groupHeaderCreator() const 0338 { 0339 if (!m_groupHeaderCreator) { 0340 m_groupHeaderCreator = defaultGroupHeaderCreator(); 0341 } 0342 return m_groupHeaderCreator; 0343 } 0344 0345 #ifndef QT_NO_ACCESSIBILITY 0346 void KItemListView::setAccessibleParentsObject(KItemListContainer *accessibleParentsObject) 0347 { 0348 Q_ASSERT(!m_accessibleParent); 0349 m_accessibleParent = new KItemListContainerAccessible(accessibleParentsObject); 0350 } 0351 KItemListContainerAccessible *KItemListView::accessibleParent() 0352 { 0353 Q_CHECK_PTR(m_accessibleParent); // We always want the accessibility tree/hierarchy to be complete. 0354 return m_accessibleParent; 0355 } 0356 #endif 0357 0358 QSizeF KItemListView::itemSize() const 0359 { 0360 return m_itemSize; 0361 } 0362 0363 const KItemListStyleOption &KItemListView::styleOption() const 0364 { 0365 return m_styleOption; 0366 } 0367 0368 void KItemListView::setGeometry(const QRectF &rect) 0369 { 0370 QGraphicsWidget::setGeometry(rect); 0371 0372 if (!m_model) { 0373 return; 0374 } 0375 0376 const QSizeF newSize = rect.size(); 0377 if (m_itemSize.isEmpty()) { 0378 m_headerWidget->resize(rect.width(), m_headerWidget->size().height()); 0379 if (m_headerWidget->automaticColumnResizing()) { 0380 applyAutomaticColumnWidths(); 0381 } else { 0382 const qreal requiredWidth = columnWidthsSum() + 2 * m_headerWidget->sidePadding(); 0383 const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth), m_itemSize.height()); 0384 m_layouter->setItemSize(dynamicItemSize); 0385 } 0386 } 0387 0388 m_layouter->setSize(newSize); 0389 // We don't animate the moving of the items here because 0390 // it would look like the items are slow to find their position. 0391 doLayout(NoAnimation); 0392 } 0393 0394 qreal KItemListView::verticalPageStep() const 0395 { 0396 qreal headerHeight = 0; 0397 if (m_headerWidget->isVisible()) { 0398 headerHeight = m_headerWidget->size().height(); 0399 } 0400 return size().height() - headerHeight; 0401 } 0402 0403 std::optional<int> KItemListView::itemAt(const QPointF &pos) const 0404 { 0405 if (headerBoundaries().contains(pos)) { 0406 return std::nullopt; 0407 } 0408 0409 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 0410 while (it.hasNext()) { 0411 it.next(); 0412 0413 const KItemListWidget *widget = it.value(); 0414 const QPointF mappedPos = widget->mapFromItem(this, pos); 0415 if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) { 0416 return it.key(); 0417 } 0418 } 0419 0420 return std::nullopt; 0421 } 0422 0423 bool KItemListView::isAboveSelectionToggle(int index, const QPointF &pos) const 0424 { 0425 if (!m_enabledSelectionToggles) { 0426 return false; 0427 } 0428 0429 const KItemListWidget *widget = m_visibleItems.value(index); 0430 if (widget) { 0431 const QRectF selectionToggleRect = widget->selectionToggleRect(); 0432 if (!selectionToggleRect.isEmpty()) { 0433 const QPointF mappedPos = widget->mapFromItem(this, pos); 0434 return selectionToggleRect.contains(mappedPos); 0435 } 0436 } 0437 return false; 0438 } 0439 0440 bool KItemListView::isAboveExpansionToggle(int index, const QPointF &pos) const 0441 { 0442 const KItemListWidget *widget = m_visibleItems.value(index); 0443 if (widget) { 0444 const QRectF expansionToggleRect = widget->expansionToggleRect(); 0445 if (!expansionToggleRect.isEmpty()) { 0446 const QPointF mappedPos = widget->mapFromItem(this, pos); 0447 return expansionToggleRect.contains(mappedPos); 0448 } 0449 } 0450 return false; 0451 } 0452 0453 bool KItemListView::isAboveText(int index, const QPointF &pos) const 0454 { 0455 const KItemListWidget *widget = m_visibleItems.value(index); 0456 if (widget) { 0457 const QRectF &textRect = widget->textRect(); 0458 if (!textRect.isEmpty()) { 0459 const QPointF mappedPos = widget->mapFromItem(this, pos); 0460 return textRect.contains(mappedPos); 0461 } 0462 } 0463 return false; 0464 } 0465 0466 int KItemListView::firstVisibleIndex() const 0467 { 0468 return m_layouter->firstVisibleIndex(); 0469 } 0470 0471 int KItemListView::lastVisibleIndex() const 0472 { 0473 return m_layouter->lastVisibleIndex(); 0474 } 0475 0476 void KItemListView::calculateItemSizeHints(QVector<std::pair<qreal, bool>> &logicalHeightHints, qreal &logicalWidthHint) const 0477 { 0478 widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this); 0479 } 0480 0481 void KItemListView::setSupportsItemExpanding(bool supportsExpanding) 0482 { 0483 if (m_supportsItemExpanding != supportsExpanding) { 0484 m_supportsItemExpanding = supportsExpanding; 0485 updateSiblingsInformation(); 0486 onSupportsItemExpandingChanged(supportsExpanding); 0487 } 0488 } 0489 0490 bool KItemListView::supportsItemExpanding() const 0491 { 0492 return m_supportsItemExpanding; 0493 } 0494 0495 void KItemListView::setHighlightEntireRow(bool highlightEntireRow) 0496 { 0497 if (m_highlightEntireRow != highlightEntireRow) { 0498 m_highlightEntireRow = highlightEntireRow; 0499 onHighlightEntireRowChanged(highlightEntireRow); 0500 } 0501 } 0502 0503 bool KItemListView::highlightEntireRow() const 0504 { 0505 return m_highlightEntireRow; 0506 } 0507 0508 void KItemListView::setAlternateBackgrounds(bool alternate) 0509 { 0510 if (m_alternateBackgrounds != alternate) { 0511 m_alternateBackgrounds = alternate; 0512 updateAlternateBackgrounds(); 0513 } 0514 } 0515 0516 bool KItemListView::alternateBackgrounds() const 0517 { 0518 return m_alternateBackgrounds; 0519 } 0520 0521 QRectF KItemListView::itemRect(int index) const 0522 { 0523 return m_layouter->itemRect(index); 0524 } 0525 0526 QRectF KItemListView::itemContextRect(int index) const 0527 { 0528 QRectF contextRect; 0529 0530 const KItemListWidget *widget = m_visibleItems.value(index); 0531 if (widget) { 0532 contextRect = widget->iconRect() | widget->textRect(); 0533 contextRect.translate(itemRect(index).topLeft()); 0534 } 0535 0536 return contextRect; 0537 } 0538 0539 bool KItemListView::isElided(int index) const 0540 { 0541 return m_sizeHintResolver->isElided(index); 0542 } 0543 0544 void KItemListView::scrollToItem(int index, ViewItemPosition viewItemPosition) 0545 { 0546 QRectF viewGeometry = geometry(); 0547 if (m_headerWidget->isVisible()) { 0548 const qreal headerHeight = m_headerWidget->size().height(); 0549 viewGeometry.adjust(0, headerHeight, 0, 0); 0550 } 0551 QRectF currentRect = itemRect(index); 0552 0553 // Fix for Bug 311099 - View the underscore when using Ctrl + PageDown 0554 currentRect.adjust(-m_styleOption.horizontalMargin, -m_styleOption.verticalMargin, m_styleOption.horizontalMargin, m_styleOption.verticalMargin); 0555 0556 qreal newOffset = scrollOffset(); 0557 if (scrollOrientation() == Qt::Vertical && (currentRect.top() < viewGeometry.top() || currentRect.bottom() > viewGeometry.bottom())) { 0558 switch (viewItemPosition) { 0559 case Beginning: 0560 newOffset += currentRect.top() - viewGeometry.top(); 0561 break; 0562 case Middle: 0563 newOffset += 0.5 * (currentRect.top() + currentRect.bottom() - (viewGeometry.top() + viewGeometry.bottom())); 0564 break; 0565 case End: 0566 newOffset += currentRect.bottom() - viewGeometry.bottom(); 0567 break; 0568 case Nearest: 0569 if (currentRect.top() < viewGeometry.top()) { 0570 newOffset += currentRect.top() - viewGeometry.top(); 0571 } else { 0572 newOffset += currentRect.bottom() - viewGeometry.bottom(); 0573 } 0574 break; 0575 default: 0576 Q_UNREACHABLE(); 0577 } 0578 } else if (scrollOrientation() == Qt::Horizontal && (currentRect.left() < viewGeometry.left() || currentRect.right() > viewGeometry.right())) { 0579 switch (viewItemPosition) { 0580 case Beginning: 0581 if (layoutDirection() == Qt::RightToLeft) { 0582 newOffset += currentRect.right() - viewGeometry.right(); 0583 } else { 0584 newOffset += currentRect.left() - viewGeometry.left(); 0585 } 0586 break; 0587 case Middle: 0588 newOffset += 0.5 * (currentRect.left() + currentRect.right() - (viewGeometry.left() + viewGeometry.right())); 0589 break; 0590 case End: 0591 if (layoutDirection() == Qt::RightToLeft) { 0592 newOffset += currentRect.left() - viewGeometry.left(); 0593 } else { 0594 newOffset += currentRect.right() - viewGeometry.right(); 0595 } 0596 break; 0597 case Nearest: 0598 if (currentRect.left() < viewGeometry.left()) { 0599 newOffset += currentRect.left() - viewGeometry.left(); 0600 } else { 0601 newOffset += currentRect.right() - viewGeometry.right(); 0602 } 0603 break; 0604 default: 0605 Q_UNREACHABLE(); 0606 } 0607 } 0608 0609 if (newOffset != scrollOffset()) { 0610 Q_EMIT scrollTo(newOffset); 0611 return; 0612 } 0613 0614 Q_EMIT scrollingStopped(); 0615 } 0616 0617 void KItemListView::beginTransaction() 0618 { 0619 ++m_activeTransactions; 0620 if (m_activeTransactions == 1) { 0621 onTransactionBegin(); 0622 } 0623 } 0624 0625 void KItemListView::endTransaction() 0626 { 0627 --m_activeTransactions; 0628 if (m_activeTransactions < 0) { 0629 m_activeTransactions = 0; 0630 qCWarning(DolphinDebug) << "Mismatch between beginTransaction()/endTransaction()"; 0631 } 0632 0633 if (m_activeTransactions == 0) { 0634 onTransactionEnd(); 0635 doLayout(m_endTransactionAnimationHint); 0636 m_endTransactionAnimationHint = Animation; 0637 } 0638 } 0639 0640 bool KItemListView::isTransactionActive() const 0641 { 0642 return m_activeTransactions > 0; 0643 } 0644 0645 void KItemListView::setHeaderVisible(bool visible) 0646 { 0647 if (visible && !m_headerWidget->isVisible()) { 0648 QStyleOptionHeader option; 0649 const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize()); 0650 0651 m_headerWidget->setPos(0, 0); 0652 m_headerWidget->resize(size().width(), headerSize.height()); 0653 m_headerWidget->setModel(m_model); 0654 m_headerWidget->setColumns(m_visibleRoles); 0655 m_headerWidget->setZValue(1); 0656 0657 connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); 0658 connect(m_headerWidget, &KItemListHeaderWidget::sidePaddingChanged, this, &KItemListView::slotSidePaddingChanged); 0659 connect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved); 0660 connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged); 0661 connect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged); 0662 connect(m_headerWidget, &KItemListHeaderWidget::columnHovered, this, &KItemListView::columnHovered); 0663 connect(m_headerWidget, &KItemListHeaderWidget::columnUnHovered, this, &KItemListView::columnUnHovered); 0664 0665 m_layouter->setHeaderHeight(headerSize.height()); 0666 m_headerWidget->setVisible(true); 0667 } else if (!visible && m_headerWidget->isVisible()) { 0668 disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); 0669 disconnect(m_headerWidget, &KItemListHeaderWidget::sidePaddingChanged, this, &KItemListView::slotSidePaddingChanged); 0670 disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved); 0671 disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged); 0672 disconnect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged); 0673 disconnect(m_headerWidget, &KItemListHeaderWidget::columnHovered, this, &KItemListView::columnHovered); 0674 disconnect(m_headerWidget, &KItemListHeaderWidget::columnUnHovered, this, &KItemListView::columnUnHovered); 0675 0676 m_layouter->setHeaderHeight(0); 0677 m_headerWidget->setVisible(false); 0678 } 0679 } 0680 0681 bool KItemListView::isHeaderVisible() const 0682 { 0683 return m_headerWidget->isVisible(); 0684 } 0685 0686 KItemListHeader *KItemListView::header() const 0687 { 0688 return m_header; 0689 } 0690 0691 QPixmap KItemListView::createDragPixmap(const KItemSet &indexes) const 0692 { 0693 QPixmap pixmap; 0694 0695 if (indexes.count() == 1) { 0696 KItemListWidget *item = m_visibleItems.value(indexes.first()); 0697 QGraphicsView *graphicsView = scene()->views()[0]; 0698 if (item && graphicsView) { 0699 pixmap = item->createDragPixmap(nullptr, graphicsView); 0700 } 0701 } else { 0702 // TODO: Not implemented yet. Probably extend the interface 0703 // from KItemListWidget::createDragPixmap() to return a pixmap 0704 // that can be used for multiple indexes. 0705 } 0706 0707 return pixmap; 0708 } 0709 0710 void KItemListView::editRole(int index, const QByteArray &role) 0711 { 0712 KStandardItemListWidget *widget = qobject_cast<KStandardItemListWidget *>(m_visibleItems.value(index)); 0713 if (!widget || m_editingRole) { 0714 return; 0715 } 0716 0717 m_editingRole = true; 0718 widget->setEditedRole(role); 0719 0720 connect(widget, &KItemListWidget::roleEditingCanceled, this, &KItemListView::slotRoleEditingCanceled); 0721 connect(widget, &KItemListWidget::roleEditingFinished, this, &KItemListView::slotRoleEditingFinished); 0722 0723 connect(this, &KItemListView::scrollOffsetChanged, widget, &KStandardItemListWidget::finishRoleEditing); 0724 } 0725 0726 void KItemListView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0727 { 0728 QGraphicsWidget::paint(painter, option, widget); 0729 0730 for (auto animation : std::as_const(m_rubberBandAnimations)) { 0731 QRectF rubberBandRect = animation->property(RubberPropertyName).toRectF(); 0732 0733 const QPointF topLeft = rubberBandRect.topLeft(); 0734 if (scrollOrientation() == Qt::Vertical) { 0735 rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset()); 0736 } else { 0737 rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y()); 0738 } 0739 0740 QStyleOptionRubberBand opt; 0741 initStyleOption(&opt); 0742 opt.shape = QRubberBand::Rectangle; 0743 opt.opaque = false; 0744 opt.rect = rubberBandRect.toRect(); 0745 0746 painter->save(); 0747 0748 painter->setOpacity(animation->currentValue().toReal()); 0749 style()->drawControl(QStyle::CE_RubberBand, &opt, painter); 0750 0751 painter->restore(); 0752 } 0753 0754 if (m_rubberBand->isActive()) { 0755 QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized(); 0756 0757 const QPointF topLeft = rubberBandRect.topLeft(); 0758 if (scrollOrientation() == Qt::Vertical) { 0759 rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset()); 0760 } else { 0761 rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y()); 0762 } 0763 0764 QStyleOptionRubberBand opt; 0765 initStyleOption(&opt); 0766 opt.shape = QRubberBand::Rectangle; 0767 opt.opaque = false; 0768 opt.rect = rubberBandRect.toRect(); 0769 style()->drawControl(QStyle::CE_RubberBand, &opt, painter); 0770 } 0771 0772 if (m_tapAndHoldIndicator->isActive()) { 0773 const QPointF indicatorSize = m_tapAndHoldIndicator->endPosition(); 0774 const QRectF rubberBandRect = 0775 QRectF(m_tapAndHoldIndicator->startPosition() - indicatorSize, (m_tapAndHoldIndicator->startPosition()) + indicatorSize).normalized(); 0776 QStyleOptionRubberBand opt; 0777 initStyleOption(&opt); 0778 opt.shape = QRubberBand::Rectangle; 0779 opt.opaque = false; 0780 opt.rect = rubberBandRect.toRect(); 0781 style()->drawControl(QStyle::CE_RubberBand, &opt, painter); 0782 } 0783 0784 if (!m_dropIndicator.isEmpty()) { 0785 const QRectF r = m_dropIndicator.toRect(); 0786 0787 QColor color = palette().brush(QPalette::Normal, QPalette::Text).color(); 0788 painter->setPen(color); 0789 0790 // TODO: The following implementation works only for a vertical scroll-orientation 0791 // and assumes a height of the m_draggingInsertIndicator of 1. 0792 Q_ASSERT(r.height() == 1); 0793 painter->drawLine(r.left() + 1, r.top(), r.right() - 1, r.top()); 0794 0795 color.setAlpha(128); 0796 painter->setPen(color); 0797 painter->drawRect(r.left(), r.top() - 1, r.width() - 1, 2); 0798 } 0799 } 0800 0801 QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value) 0802 { 0803 if (change == QGraphicsItem::ItemSceneHasChanged && scene()) { 0804 if (!scene()->views().isEmpty()) { 0805 m_styleOption.palette = scene()->views().at(0)->palette(); 0806 } 0807 } 0808 return QGraphicsItem::itemChange(change, value); 0809 } 0810 0811 void KItemListView::setItemSize(const QSizeF &size) 0812 { 0813 const QSizeF previousSize = m_itemSize; 0814 if (size == previousSize) { 0815 return; 0816 } 0817 0818 // Skip animations when the number of rows or columns 0819 // are changed in the grid layout. Although the animation 0820 // engine can handle this usecase, it looks obtrusive. 0821 const bool animate = !changesItemGridLayout(m_layouter->size(), size, m_layouter->itemMargin()); 0822 0823 const bool alternateBackgroundsChanged = m_alternateBackgrounds && ((m_itemSize.isEmpty() && !size.isEmpty()) || (!m_itemSize.isEmpty() && size.isEmpty())); 0824 0825 m_itemSize = size; 0826 0827 if (alternateBackgroundsChanged) { 0828 // For an empty item size alternate backgrounds are drawn if more than 0829 // one role is shown. Assure that the backgrounds for visible items are 0830 // updated when changing the size in this context. 0831 updateAlternateBackgrounds(); 0832 } 0833 0834 if (size.isEmpty()) { 0835 if (m_headerWidget->automaticColumnResizing()) { 0836 updatePreferredColumnWidths(); 0837 } else { 0838 // Only apply the changed height and respect the header widths 0839 // set by the user 0840 const qreal currentWidth = m_layouter->itemSize().width(); 0841 const QSizeF newSize(currentWidth, size.height()); 0842 m_layouter->setItemSize(newSize); 0843 } 0844 } else { 0845 m_layouter->setItemSize(size); 0846 } 0847 0848 m_sizeHintResolver->clearCache(); 0849 doLayout(animate ? Animation : NoAnimation); 0850 onItemSizeChanged(size, previousSize); 0851 } 0852 0853 void KItemListView::setStyleOption(const KItemListStyleOption &option) 0854 { 0855 if (m_styleOption == option) { 0856 return; 0857 } 0858 0859 const KItemListStyleOption previousOption = m_styleOption; 0860 m_styleOption = option; 0861 0862 bool animate = true; 0863 const QSizeF margin(option.horizontalMargin, option.verticalMargin); 0864 if (margin != m_layouter->itemMargin()) { 0865 // Skip animations when the number of rows or columns 0866 // are changed in the grid layout. Although the animation 0867 // engine can handle this usecase, it looks obtrusive. 0868 animate = !changesItemGridLayout(m_layouter->size(), m_layouter->itemSize(), margin); 0869 m_layouter->setItemMargin(margin); 0870 } 0871 0872 if (m_grouped) { 0873 updateGroupHeaderHeight(); 0874 } 0875 0876 if (animate && (previousOption.maxTextLines != option.maxTextLines || previousOption.maxTextWidth != option.maxTextWidth)) { 0877 // Animating a change of the maximum text size just results in expensive 0878 // temporary eliding and clipping operations and does not look good visually. 0879 animate = false; 0880 } 0881 0882 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 0883 while (it.hasNext()) { 0884 it.next(); 0885 it.value()->setStyleOption(option); 0886 } 0887 0888 m_sizeHintResolver->clearCache(); 0889 m_layouter->markAsDirty(); 0890 doLayout(animate ? Animation : NoAnimation); 0891 0892 if (m_itemSize.isEmpty()) { 0893 updatePreferredColumnWidths(); 0894 } 0895 0896 onStyleOptionChanged(option, previousOption); 0897 } 0898 0899 void KItemListView::setScrollOrientation(Qt::Orientation orientation) 0900 { 0901 const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); 0902 if (orientation == previousOrientation) { 0903 return; 0904 } 0905 0906 m_layouter->setScrollOrientation(orientation); 0907 m_animation->setScrollOrientation(orientation); 0908 m_sizeHintResolver->clearCache(); 0909 0910 if (m_grouped) { 0911 QMutableHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_visibleGroups); 0912 while (it.hasNext()) { 0913 it.next(); 0914 it.value()->setScrollOrientation(orientation); 0915 } 0916 updateGroupHeaderHeight(); 0917 } 0918 0919 doLayout(NoAnimation); 0920 0921 onScrollOrientationChanged(orientation, previousOrientation); 0922 Q_EMIT scrollOrientationChanged(orientation, previousOrientation); 0923 } 0924 0925 Qt::Orientation KItemListView::scrollOrientation() const 0926 { 0927 return m_layouter->scrollOrientation(); 0928 } 0929 0930 KItemListWidgetCreatorBase *KItemListView::defaultWidgetCreator() const 0931 { 0932 return nullptr; 0933 } 0934 0935 KItemListGroupHeaderCreatorBase *KItemListView::defaultGroupHeaderCreator() const 0936 { 0937 return nullptr; 0938 } 0939 0940 void KItemListView::initializeItemListWidget(KItemListWidget *item) 0941 { 0942 Q_UNUSED(item) 0943 } 0944 0945 bool KItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray> &changedRoles) const 0946 { 0947 Q_UNUSED(changedRoles) 0948 return true; 0949 } 0950 0951 void KItemListView::onControllerChanged(KItemListController *current, KItemListController *previous) 0952 { 0953 Q_UNUSED(current) 0954 Q_UNUSED(previous) 0955 } 0956 0957 void KItemListView::onModelChanged(KItemModelBase *current, KItemModelBase *previous) 0958 { 0959 Q_UNUSED(current) 0960 Q_UNUSED(previous) 0961 } 0962 0963 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) 0964 { 0965 Q_UNUSED(current) 0966 Q_UNUSED(previous) 0967 } 0968 0969 void KItemListView::onItemSizeChanged(const QSizeF ¤t, const QSizeF &previous) 0970 { 0971 Q_UNUSED(current) 0972 Q_UNUSED(previous) 0973 } 0974 0975 void KItemListView::onScrollOffsetChanged(qreal current, qreal previous) 0976 { 0977 Q_UNUSED(current) 0978 Q_UNUSED(previous) 0979 } 0980 0981 void KItemListView::onVisibleRolesChanged(const QList<QByteArray> ¤t, const QList<QByteArray> &previous) 0982 { 0983 Q_UNUSED(current) 0984 Q_UNUSED(previous) 0985 } 0986 0987 void KItemListView::onStyleOptionChanged(const KItemListStyleOption ¤t, const KItemListStyleOption &previous) 0988 { 0989 Q_UNUSED(current) 0990 Q_UNUSED(previous) 0991 } 0992 0993 void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow) 0994 { 0995 Q_UNUSED(highlightEntireRow) 0996 } 0997 0998 void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) 0999 { 1000 Q_UNUSED(supportsExpanding) 1001 } 1002 1003 void KItemListView::onTransactionBegin() 1004 { 1005 } 1006 1007 void KItemListView::onTransactionEnd() 1008 { 1009 } 1010 1011 bool KItemListView::event(QEvent *event) 1012 { 1013 switch (event->type()) { 1014 case QEvent::PaletteChange: 1015 updatePalette(); 1016 break; 1017 1018 case QEvent::FontChange: 1019 updateFont(); 1020 break; 1021 1022 case QEvent::FocusIn: 1023 focusInEvent(static_cast<QFocusEvent *>(event)); 1024 event->accept(); 1025 return true; 1026 break; 1027 1028 case QEvent::FocusOut: 1029 focusOutEvent(static_cast<QFocusEvent *>(event)); 1030 event->accept(); 1031 return true; 1032 break; 1033 1034 default: 1035 // Forward all other events to the controller and handle them there 1036 if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) { 1037 event->accept(); 1038 return true; 1039 } 1040 } 1041 1042 return QGraphicsWidget::event(event); 1043 } 1044 1045 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent *event) 1046 { 1047 m_mousePos = transform().map(event->pos()); 1048 event->accept(); 1049 } 1050 1051 void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) 1052 { 1053 QGraphicsWidget::mouseMoveEvent(event); 1054 1055 m_mousePos = transform().map(event->pos()); 1056 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) { 1057 m_autoScrollTimer->start(InitialAutoScrollDelay); 1058 } 1059 } 1060 1061 void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent *event) 1062 { 1063 event->setAccepted(true); 1064 setAutoScroll(true); 1065 } 1066 1067 void KItemListView::dragMoveEvent(QGraphicsSceneDragDropEvent *event) 1068 { 1069 QGraphicsWidget::dragMoveEvent(event); 1070 1071 m_mousePos = transform().map(event->pos()); 1072 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) { 1073 m_autoScrollTimer->start(InitialAutoScrollDelay); 1074 } 1075 } 1076 1077 void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) 1078 { 1079 QGraphicsWidget::dragLeaveEvent(event); 1080 setAutoScroll(false); 1081 } 1082 1083 void KItemListView::dropEvent(QGraphicsSceneDragDropEvent *event) 1084 { 1085 QGraphicsWidget::dropEvent(event); 1086 setAutoScroll(false); 1087 } 1088 1089 QList<KItemListWidget *> KItemListView::visibleItemListWidgets() const 1090 { 1091 return m_visibleItems.values(); 1092 } 1093 1094 void KItemListView::updateFont() 1095 { 1096 if (scene() && !scene()->views().isEmpty()) { 1097 KItemListStyleOption option = styleOption(); 1098 option.font = scene()->views().first()->font(); 1099 option.fontMetrics = QFontMetrics(option.font); 1100 1101 setStyleOption(option); 1102 } 1103 } 1104 1105 void KItemListView::updatePalette() 1106 { 1107 KItemListStyleOption option = styleOption(); 1108 option.palette = palette(); 1109 setStyleOption(option); 1110 } 1111 1112 void KItemListView::slotItemsInserted(const KItemRangeList &itemRanges) 1113 { 1114 if (m_itemSize.isEmpty()) { 1115 updatePreferredColumnWidths(itemRanges); 1116 } 1117 1118 const bool hasMultipleRanges = (itemRanges.count() > 1); 1119 if (hasMultipleRanges) { 1120 beginTransaction(); 1121 } 1122 1123 m_layouter->markAsDirty(); 1124 1125 m_sizeHintResolver->itemsInserted(itemRanges); 1126 1127 int previouslyInsertedCount = 0; 1128 for (const KItemRange &range : itemRanges) { 1129 // range.index is related to the model before anything has been inserted. 1130 // As in each loop the current item-range gets inserted the index must 1131 // be increased by the already previously inserted items. 1132 const int index = range.index + previouslyInsertedCount; 1133 const int count = range.count; 1134 if (index < 0 || count <= 0) { 1135 qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; 1136 continue; 1137 } 1138 previouslyInsertedCount += count; 1139 1140 // Determine which visible items must be moved 1141 QList<int> itemsToMove; 1142 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 1143 while (it.hasNext()) { 1144 it.next(); 1145 const int visibleItemIndex = it.key(); 1146 if (visibleItemIndex >= index) { 1147 itemsToMove.append(visibleItemIndex); 1148 } 1149 } 1150 1151 // Update the indexes of all KItemListWidget instances that are located 1152 // after the inserted items. It is important to adjust the indexes in the order 1153 // from the highest index to the lowest index to prevent overlaps when setting the new index. 1154 std::sort(itemsToMove.begin(), itemsToMove.end()); 1155 for (int i = itemsToMove.count() - 1; i >= 0; --i) { 1156 KItemListWidget *widget = m_visibleItems.value(itemsToMove[i]); 1157 Q_ASSERT(widget); 1158 const int newIndex = widget->index() + count; 1159 if (hasMultipleRanges) { 1160 setWidgetIndex(widget, newIndex); 1161 } else { 1162 // Try to animate the moving of the item 1163 moveWidgetToIndex(widget, newIndex); 1164 } 1165 } 1166 1167 if (m_model->count() == count && m_activeTransactions == 0) { 1168 // Check whether a scrollbar is required to show the inserted items. In this case 1169 // the size of the layouter will be decreased before calling doLayout(): This prevents 1170 // an unnecessary temporary animation due to the geometry change of the inserted scrollbar. 1171 const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical); 1172 const bool decreaseLayouterSize = (verticalScrollOrientation && maximumScrollOffset() > size().height()) 1173 || (!verticalScrollOrientation && maximumScrollOffset() > size().width()); 1174 if (decreaseLayouterSize) { 1175 const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); 1176 1177 int scrollbarSpacing = 0; 1178 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { 1179 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing); 1180 } 1181 1182 QSizeF layouterSize = m_layouter->size(); 1183 if (verticalScrollOrientation) { 1184 layouterSize.rwidth() -= scrollBarExtent + scrollbarSpacing; 1185 } else { 1186 layouterSize.rheight() -= scrollBarExtent + scrollbarSpacing; 1187 } 1188 m_layouter->setSize(layouterSize); 1189 } 1190 } 1191 1192 if (!hasMultipleRanges) { 1193 doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count); 1194 updateSiblingsInformation(); 1195 } 1196 } 1197 1198 if (m_controller) { 1199 m_controller->selectionManager()->itemsInserted(itemRanges); 1200 } 1201 1202 if (hasMultipleRanges) { 1203 m_endTransactionAnimationHint = NoAnimation; 1204 endTransaction(); 1205 1206 updateSiblingsInformation(); 1207 } 1208 1209 if (m_grouped && (hasMultipleRanges || itemRanges.first().count < m_model->count())) { 1210 // In case if items of the same group have been inserted before an item that 1211 // currently represents the first item of the group, the group header of 1212 // this item must be removed. 1213 updateVisibleGroupHeaders(); 1214 } 1215 1216 if (useAlternateBackgrounds()) { 1217 updateAlternateBackgrounds(); 1218 } 1219 } 1220 1221 void KItemListView::slotItemsRemoved(const KItemRangeList &itemRanges) 1222 { 1223 if (m_itemSize.isEmpty()) { 1224 // Don't pass the item-range: The preferred column-widths of 1225 // all items must be adjusted when removing items. 1226 updatePreferredColumnWidths(); 1227 } 1228 1229 const bool hasMultipleRanges = (itemRanges.count() > 1); 1230 if (hasMultipleRanges) { 1231 beginTransaction(); 1232 } 1233 1234 m_layouter->markAsDirty(); 1235 1236 m_sizeHintResolver->itemsRemoved(itemRanges); 1237 1238 for (int i = itemRanges.count() - 1; i >= 0; --i) { 1239 const KItemRange &range = itemRanges[i]; 1240 const int index = range.index; 1241 const int count = range.count; 1242 if (index < 0 || count <= 0) { 1243 qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; 1244 continue; 1245 } 1246 1247 const int firstRemovedIndex = index; 1248 const int lastRemovedIndex = index + count - 1; 1249 1250 // Remember which items have to be moved because they are behind the removed range. 1251 QVector<int> itemsToMove; 1252 1253 // Remove all KItemListWidget instances that got deleted 1254 // Iterate over a const copy because the container is mutated within the loop 1255 // directly and in `recycleWidget()` (https://bugs.kde.org/show_bug.cgi?id=428374) 1256 const auto visibleItems = m_visibleItems; 1257 for (KItemListWidget *widget : visibleItems) { 1258 const int i = widget->index(); 1259 if (i < firstRemovedIndex) { 1260 continue; 1261 } else if (i > lastRemovedIndex) { 1262 itemsToMove.append(i); 1263 continue; 1264 } 1265 1266 m_animation->stop(widget); 1267 // Stopping the animation might lead to recycling the widget if 1268 // it is invisible (see slotAnimationFinished()). 1269 // Check again whether it is still visible: 1270 if (!m_visibleItems.contains(i)) { 1271 continue; 1272 } 1273 1274 if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) { 1275 // Remove the widget without animation 1276 recycleWidget(widget); 1277 } else { 1278 // Animate the removing of the items. Special case: When removing an item there 1279 // is no valid model index available anymore. For the 1280 // remove-animation the item gets removed from m_visibleItems but the widget 1281 // will stay alive until the animation has been finished and will 1282 // be recycled (deleted) in KItemListView::slotAnimationFinished(). 1283 m_visibleItems.remove(i); 1284 widget->setIndex(-1); 1285 m_animation->start(widget, KItemListViewAnimation::DeleteAnimation); 1286 } 1287 } 1288 1289 // Update the indexes of all KItemListWidget instances that are located 1290 // after the deleted items. It is important to update them in ascending 1291 // order to prevent overlaps when setting the new index. 1292 std::sort(itemsToMove.begin(), itemsToMove.end()); 1293 for (int i : std::as_const(itemsToMove)) { 1294 KItemListWidget *widget = m_visibleItems.value(i); 1295 Q_ASSERT(widget); 1296 const int newIndex = i - count; 1297 if (hasMultipleRanges) { 1298 setWidgetIndex(widget, newIndex); 1299 } else { 1300 // Try to animate the moving of the item 1301 moveWidgetToIndex(widget, newIndex); 1302 } 1303 } 1304 1305 if (!hasMultipleRanges) { 1306 // The decrease-layout-size optimization in KItemListView::slotItemsInserted() 1307 // assumes an updated geometry. If items are removed during an active transaction, 1308 // the transaction will be temporary deactivated so that doLayout() triggers a 1309 // geometry update if necessary. 1310 const int activeTransactions = m_activeTransactions; 1311 m_activeTransactions = 0; 1312 doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); 1313 m_activeTransactions = activeTransactions; 1314 updateSiblingsInformation(); 1315 } 1316 } 1317 1318 if (m_controller) { 1319 m_controller->selectionManager()->itemsRemoved(itemRanges); 1320 } 1321 1322 if (hasMultipleRanges) { 1323 m_endTransactionAnimationHint = NoAnimation; 1324 endTransaction(); 1325 updateSiblingsInformation(); 1326 } 1327 1328 if (m_grouped && (hasMultipleRanges || m_model->count() > 0)) { 1329 // In case if the first item of a group has been removed, the group header 1330 // must be applied to the next visible item. 1331 updateVisibleGroupHeaders(); 1332 } 1333 1334 if (useAlternateBackgrounds()) { 1335 updateAlternateBackgrounds(); 1336 } 1337 } 1338 1339 void KItemListView::slotItemsMoved(const KItemRange &itemRange, const QList<int> &movedToIndexes) 1340 { 1341 m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes); 1342 m_layouter->markAsDirty(); 1343 1344 if (m_controller) { 1345 m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes); 1346 } 1347 1348 const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index); 1349 const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1); 1350 1351 for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) { 1352 KItemListWidget *widget = m_visibleItems.value(index); 1353 if (widget) { 1354 updateWidgetProperties(widget, index); 1355 initializeItemListWidget(widget); 1356 } 1357 } 1358 1359 doLayout(NoAnimation); 1360 updateSiblingsInformation(); 1361 } 1362 1363 void KItemListView::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles) 1364 { 1365 const bool updateSizeHints = itemSizeHintUpdateRequired(roles); 1366 if (updateSizeHints && m_itemSize.isEmpty()) { 1367 updatePreferredColumnWidths(itemRanges); 1368 } 1369 1370 for (const KItemRange &itemRange : itemRanges) { 1371 const int index = itemRange.index; 1372 const int count = itemRange.count; 1373 1374 if (updateSizeHints) { 1375 m_sizeHintResolver->itemsChanged(index, count, roles); 1376 m_layouter->markAsDirty(); 1377 } 1378 1379 // Apply the changed roles to the visible item-widgets 1380 const int lastIndex = index + count - 1; 1381 for (int i = index; i <= lastIndex; ++i) { 1382 KItemListWidget *widget = m_visibleItems.value(i); 1383 if (widget) { 1384 widget->setData(m_model->data(i), roles); 1385 } 1386 } 1387 1388 if (m_grouped && roles.contains(m_model->sortRole())) { 1389 // The sort-role has been changed which might result 1390 // in modified group headers 1391 updateVisibleGroupHeaders(); 1392 doLayout(NoAnimation); 1393 } 1394 1395 QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged); 1396 ev.setFirstRow(itemRange.index); 1397 ev.setLastRow(itemRange.index + itemRange.count); 1398 QAccessible::updateAccessibility(&ev); 1399 } 1400 1401 doLayout(NoAnimation); 1402 } 1403 1404 void KItemListView::slotGroupsChanged() 1405 { 1406 updateVisibleGroupHeaders(); 1407 doLayout(NoAnimation); 1408 updateSiblingsInformation(); 1409 } 1410 1411 void KItemListView::slotGroupedSortingChanged(bool current) 1412 { 1413 m_grouped = current; 1414 m_layouter->markAsDirty(); 1415 1416 if (m_grouped) { 1417 updateGroupHeaderHeight(); 1418 } else { 1419 // Clear all visible headers. Note that the QHashIterator takes a copy of 1420 // m_visibleGroups. Therefore, it remains valid even if items are removed 1421 // from m_visibleGroups in recycleGroupHeaderForWidget(). 1422 QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_visibleGroups); 1423 while (it.hasNext()) { 1424 it.next(); 1425 recycleGroupHeaderForWidget(it.key()); 1426 } 1427 Q_ASSERT(m_visibleGroups.isEmpty()); 1428 } 1429 1430 if (useAlternateBackgrounds()) { 1431 // Changing the group mode requires to update the alternate backgrounds 1432 // as with the enabled group mode the altering is done on base of the first 1433 // group item. 1434 updateAlternateBackgrounds(); 1435 } 1436 updateSiblingsInformation(); 1437 doLayout(NoAnimation); 1438 } 1439 1440 void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) 1441 { 1442 Q_UNUSED(current) 1443 Q_UNUSED(previous) 1444 if (m_grouped) { 1445 updateVisibleGroupHeaders(); 1446 doLayout(NoAnimation); 1447 } 1448 } 1449 1450 void KItemListView::slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous) 1451 { 1452 Q_UNUSED(current) 1453 Q_UNUSED(previous) 1454 if (m_grouped) { 1455 updateVisibleGroupHeaders(); 1456 doLayout(NoAnimation); 1457 } 1458 } 1459 1460 void KItemListView::slotCurrentChanged(int current, int previous) 1461 { 1462 Q_UNUSED(previous) 1463 1464 // In SingleSelection mode (e.g., in the Places Panel), the current item is 1465 // always the selected item. It is not necessary to highlight the current item then. 1466 if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { 1467 KItemListWidget *previousWidget = m_visibleItems.value(previous, nullptr); 1468 if (previousWidget) { 1469 previousWidget->setCurrent(false); 1470 } 1471 1472 KItemListWidget *currentWidget = m_visibleItems.value(current, nullptr); 1473 if (currentWidget) { 1474 currentWidget->setCurrent(true); 1475 } 1476 } 1477 1478 QAccessibleEvent ev(this, QAccessible::Focus); 1479 ev.setChild(current); 1480 QAccessible::updateAccessibility(&ev); 1481 } 1482 1483 void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous) 1484 { 1485 Q_UNUSED(previous) 1486 1487 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 1488 while (it.hasNext()) { 1489 it.next(); 1490 const int index = it.key(); 1491 KItemListWidget *widget = it.value(); 1492 widget->setSelected(current.contains(index)); 1493 } 1494 } 1495 1496 void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type) 1497 { 1498 KItemListWidget *itemListWidget = qobject_cast<KItemListWidget *>(widget); 1499 Q_ASSERT(itemListWidget); 1500 1501 if (type == KItemListViewAnimation::DeleteAnimation) { 1502 // As we recycle the widget in this case it is important to assure that no 1503 // other animation has been started. This is a convention in KItemListView and 1504 // not a requirement defined by KItemListViewAnimation. 1505 Q_ASSERT(!m_animation->isStarted(itemListWidget)); 1506 1507 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained 1508 // by m_visibleWidgets and must be deleted manually after the animation has 1509 // been finished. 1510 recycleGroupHeaderForWidget(itemListWidget); 1511 widgetCreator()->recycle(itemListWidget); 1512 } else { 1513 const int index = itemListWidget->index(); 1514 const bool invisible = (index < m_layouter->firstVisibleIndex()) || (index > m_layouter->lastVisibleIndex()); 1515 if (invisible && !m_animation->isStarted(itemListWidget)) { 1516 recycleWidget(itemListWidget); 1517 } 1518 } 1519 } 1520 1521 void KItemListView::slotRubberBandPosChanged() 1522 { 1523 update(); 1524 } 1525 1526 void KItemListView::slotRubberBandActivationChanged(bool active) 1527 { 1528 if (active) { 1529 connect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); 1530 connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); 1531 m_skipAutoScrollForRubberBand = true; 1532 } else { 1533 QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized(); 1534 1535 auto animation = new QVariantAnimation(this); 1536 animation->setStartValue(1.0); 1537 animation->setEndValue(0.0); 1538 animation->setDuration(RubberFadeSpeed); 1539 animation->setProperty(RubberPropertyName, rubberBandRect); 1540 1541 QEasingCurve curve; 1542 curve.setType(QEasingCurve::BezierSpline); 1543 curve.addCubicBezierSegment(QPointF(0.4, 0.0), QPointF(1.0, 1.0), QPointF(1.0, 1.0)); 1544 animation->setEasingCurve(curve); 1545 1546 connect(animation, &QVariantAnimation::valueChanged, this, [=](const QVariant &) { 1547 update(); 1548 }); 1549 connect(animation, &QVariantAnimation::finished, this, [=]() { 1550 m_rubberBandAnimations.removeAll(animation); 1551 delete animation; 1552 }); 1553 animation->start(); 1554 m_rubberBandAnimations << animation; 1555 1556 disconnect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); 1557 disconnect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); 1558 m_skipAutoScrollForRubberBand = false; 1559 } 1560 1561 update(); 1562 } 1563 1564 void KItemListView::slotHeaderColumnWidthChanged(const QByteArray &role, qreal currentWidth, qreal previousWidth) 1565 { 1566 Q_UNUSED(role) 1567 Q_UNUSED(currentWidth) 1568 Q_UNUSED(previousWidth) 1569 1570 m_headerWidget->setAutomaticColumnResizing(false); 1571 applyColumnWidthsFromHeader(); 1572 doLayout(NoAnimation); 1573 } 1574 1575 void KItemListView::slotSidePaddingChanged(qreal width) 1576 { 1577 Q_UNUSED(width) 1578 if (m_headerWidget->automaticColumnResizing()) { 1579 applyAutomaticColumnWidths(); 1580 } 1581 applyColumnWidthsFromHeader(); 1582 doLayout(NoAnimation); 1583 } 1584 1585 void KItemListView::slotHeaderColumnMoved(const QByteArray &role, int currentIndex, int previousIndex) 1586 { 1587 Q_ASSERT(m_visibleRoles[previousIndex] == role); 1588 1589 const QList<QByteArray> previous = m_visibleRoles; 1590 1591 QList<QByteArray> current = m_visibleRoles; 1592 current.removeAt(previousIndex); 1593 current.insert(currentIndex, role); 1594 1595 setVisibleRoles(current); 1596 1597 Q_EMIT visibleRolesChanged(current, previous); 1598 } 1599 1600 void KItemListView::triggerAutoScrolling() 1601 { 1602 if (!m_autoScrollTimer) { 1603 return; 1604 } 1605 1606 int pos = 0; 1607 int visibleSize = 0; 1608 if (scrollOrientation() == Qt::Vertical) { 1609 pos = m_mousePos.y(); 1610 visibleSize = size().height(); 1611 } else { 1612 pos = m_mousePos.x(); 1613 visibleSize = size().width(); 1614 } 1615 1616 if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) { 1617 m_autoScrollIncrement = 0; 1618 } 1619 1620 m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement); 1621 if (m_autoScrollIncrement == 0) { 1622 // The mouse position is not above an autoscroll margin (the autoscroll timer 1623 // will be restarted in mouseMoveEvent()) 1624 m_autoScrollTimer->stop(); 1625 return; 1626 } 1627 1628 if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) { 1629 // If a rubberband selection is ongoing the autoscrolling may only get triggered 1630 // if the direction of the rubberband is similar to the autoscroll direction. This 1631 // prevents that starting to create a rubberband within the autoscroll margins starts 1632 // an autoscrolling. 1633 1634 const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small 1635 const qreal diff = (scrollOrientation() == Qt::Vertical) ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y() 1636 : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x(); 1637 if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) { 1638 // The rubberband direction is different from the scroll direction (e.g. the rubberband has 1639 // been moved up although the autoscroll direction might be down) 1640 m_autoScrollTimer->stop(); 1641 return; 1642 } 1643 } 1644 1645 // As soon as the autoscrolling has been triggered at least once despite having an active rubberband, 1646 // the autoscrolling may not get skipped anymore until a new rubberband is created 1647 m_skipAutoScrollForRubberBand = false; 1648 1649 const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize); 1650 const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset); 1651 setScrollOffset(newScrollOffset); 1652 1653 // Trigger the autoscroll timer which will periodically call 1654 // triggerAutoScrolling() 1655 m_autoScrollTimer->start(RepeatingAutoScrollDelay); 1656 } 1657 1658 void KItemListView::slotGeometryOfGroupHeaderParentChanged() 1659 { 1660 KItemListWidget *widget = qobject_cast<KItemListWidget *>(sender()); 1661 Q_ASSERT(widget); 1662 KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget); 1663 Q_ASSERT(groupHeader); 1664 updateGroupHeaderLayout(widget); 1665 } 1666 1667 void KItemListView::slotRoleEditingCanceled(int index, const QByteArray &role, const QVariant &value) 1668 { 1669 disconnectRoleEditingSignals(index); 1670 1671 m_editingRole = false; 1672 Q_EMIT roleEditingCanceled(index, role, value); 1673 } 1674 1675 void KItemListView::slotRoleEditingFinished(int index, const QByteArray &role, const QVariant &value) 1676 { 1677 disconnectRoleEditingSignals(index); 1678 1679 m_editingRole = false; 1680 Q_EMIT roleEditingFinished(index, role, value); 1681 } 1682 1683 void KItemListView::setController(KItemListController *controller) 1684 { 1685 if (m_controller != controller) { 1686 KItemListController *previous = m_controller; 1687 if (previous) { 1688 KItemListSelectionManager *selectionManager = previous->selectionManager(); 1689 disconnect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged); 1690 disconnect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged); 1691 } 1692 1693 m_controller = controller; 1694 1695 if (controller) { 1696 KItemListSelectionManager *selectionManager = controller->selectionManager(); 1697 connect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged); 1698 connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged); 1699 } 1700 1701 onControllerChanged(controller, previous); 1702 } 1703 } 1704 1705 void KItemListView::setModel(KItemModelBase *model) 1706 { 1707 if (m_model == model) { 1708 return; 1709 } 1710 1711 KItemModelBase *previous = m_model; 1712 1713 if (m_model) { 1714 disconnect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged); 1715 disconnect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted); 1716 disconnect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved); 1717 disconnect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved); 1718 disconnect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged); 1719 disconnect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); 1720 disconnect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); 1721 disconnect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); 1722 1723 m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count())); 1724 } 1725 1726 m_model = model; 1727 m_layouter->setModel(model); 1728 m_grouped = model->groupedSorting(); 1729 1730 if (m_model) { 1731 connect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged); 1732 connect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted); 1733 connect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved); 1734 connect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved); 1735 connect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged); 1736 connect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); 1737 connect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); 1738 connect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); 1739 1740 const int itemCount = m_model->count(); 1741 if (itemCount > 0) { 1742 slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount)); 1743 } 1744 } 1745 1746 onModelChanged(model, previous); 1747 } 1748 1749 KItemListRubberBand *KItemListView::rubberBand() const 1750 { 1751 return m_rubberBand; 1752 } 1753 1754 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount) 1755 { 1756 if (m_activeTransactions > 0) { 1757 if (hint == NoAnimation) { 1758 // As soon as at least one property change should be done without animation, 1759 // the whole transaction will be marked as not animated. 1760 m_endTransactionAnimationHint = NoAnimation; 1761 } 1762 return; 1763 } 1764 1765 if (!m_model || m_model->count() < 0) { 1766 return; 1767 } 1768 1769 int firstVisibleIndex = m_layouter->firstVisibleIndex(); 1770 if (firstVisibleIndex < 0) { 1771 emitOffsetChanges(); 1772 return; 1773 } 1774 1775 // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed 1776 // it might be possible that the maximum offset got changed too. Assure that the full visible range 1777 // is still shown if the maximum offset got decreased. 1778 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height(); 1779 const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange; 1780 if (scrollOffset() > maxOffsetToShowFullRange) { 1781 m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange)); 1782 firstVisibleIndex = m_layouter->firstVisibleIndex(); 1783 } 1784 1785 const int lastVisibleIndex = m_layouter->lastVisibleIndex(); 1786 1787 int firstSibblingIndex = -1; 1788 int lastSibblingIndex = -1; 1789 const bool supportsExpanding = supportsItemExpanding(); 1790 1791 QList<int> reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint); 1792 1793 // Assure that for each visible item a KItemListWidget is available. KItemListWidget 1794 // instances from invisible items are reused. If no reusable items are 1795 // found then new KItemListWidget instances get created. 1796 const bool animate = (hint == Animation); 1797 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) { 1798 bool applyNewPos = true; 1799 1800 const QRectF itemBounds = m_layouter->itemRect(i); 1801 const QPointF newPos = itemBounds.topLeft(); 1802 KItemListWidget *widget = m_visibleItems.value(i); 1803 if (!widget) { 1804 if (!reusableItems.isEmpty()) { 1805 // Reuse a KItemListWidget instance from an invisible item 1806 const int oldIndex = reusableItems.takeLast(); 1807 widget = m_visibleItems.value(oldIndex); 1808 setWidgetIndex(widget, i); 1809 updateWidgetProperties(widget, i); 1810 initializeItemListWidget(widget); 1811 } else { 1812 // No reusable KItemListWidget instance is available, create a new one 1813 widget = createWidget(i); 1814 } 1815 widget->resize(itemBounds.size()); 1816 1817 if (animate && changedCount < 0) { 1818 // Items have been deleted. 1819 if (i >= changedIndex) { 1820 // The item is located behind the removed range. Move the 1821 // created item to the imaginary old position outside the 1822 // view. It will get animated to the new position later. 1823 const int previousIndex = i - changedCount; 1824 const QRectF itemRect = m_layouter->itemRect(previousIndex); 1825 if (itemRect.isEmpty()) { 1826 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) ? QPointF(0, size().height()) : QPointF(size().width(), 0); 1827 widget->setPos(invisibleOldPos); 1828 } else { 1829 widget->setPos(itemRect.topLeft()); 1830 } 1831 applyNewPos = false; 1832 } 1833 } 1834 1835 if (supportsExpanding && changedCount == 0) { 1836 if (firstSibblingIndex < 0) { 1837 firstSibblingIndex = i; 1838 } 1839 lastSibblingIndex = i; 1840 } 1841 } 1842 1843 if (animate) { 1844 if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) { 1845 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); 1846 applyNewPos = false; 1847 } 1848 1849 const bool itemsRemoved = (changedCount < 0); 1850 const bool itemsInserted = (changedCount > 0); 1851 if (itemsRemoved && (i >= changedIndex)) { 1852 // The item is located after the removed items. Animate the moving of the position. 1853 applyNewPos = !moveWidget(widget, newPos); 1854 } else if (itemsInserted && i >= changedIndex) { 1855 // The item is located after the first inserted item 1856 if (i <= changedIndex + changedCount - 1) { 1857 // The item is an inserted item. Animate the appearing of the item. 1858 // For performance reasons no animation is done when changedCount is equal 1859 // to all available items. 1860 if (changedCount < m_model->count()) { 1861 m_animation->start(widget, KItemListViewAnimation::CreateAnimation); 1862 } 1863 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) { 1864 // The item was already there before, so animate the moving of the position. 1865 // No moving animation is done if the item is animated by a create animation: This 1866 // prevents a "move animation mess" when inserting several ranges in parallel. 1867 applyNewPos = !moveWidget(widget, newPos); 1868 } 1869 } 1870 } else { 1871 m_animation->stop(widget); 1872 } 1873 1874 if (applyNewPos) { 1875 widget->setPos(newPos); 1876 } 1877 1878 Q_ASSERT(widget->index() == i); 1879 widget->setVisible(true); 1880 1881 bool animateIconResizing = animate; 1882 1883 if (widget->size() != itemBounds.size()) { 1884 // Resize the widget for the item to the changed size. 1885 if (animate) { 1886 // If a dynamic item size is used then no animation is done in the direction 1887 // of the dynamic size. 1888 if (m_itemSize.width() <= 0) { 1889 // The width is dynamic, apply the new width without animation. 1890 widget->resize(itemBounds.width(), widget->size().height()); 1891 } else if (m_itemSize.height() <= 0) { 1892 // The height is dynamic, apply the new height without animation. 1893 widget->resize(widget->size().width(), itemBounds.height()); 1894 } 1895 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size()); 1896 } else { 1897 widget->resize(itemBounds.size()); 1898 } 1899 } else { 1900 animateIconResizing = false; 1901 } 1902 1903 const int newIconSize = widget->styleOption().iconSize; 1904 if (widget->iconSize() != newIconSize) { 1905 if (animateIconResizing) { 1906 m_animation->start(widget, KItemListViewAnimation::IconResizeAnimation, newIconSize); 1907 } else { 1908 widget->setIconSize(newIconSize); 1909 } 1910 } 1911 1912 // Updating the cell-information must be done as last step: The decision whether the 1913 // moving-animation should be started at all is based on the previous cell-information. 1914 const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i)); 1915 m_visibleCells.insert(i, cell); 1916 } 1917 1918 // Delete invisible KItemListWidget instances that have not been reused 1919 for (int index : std::as_const(reusableItems)) { 1920 recycleWidget(m_visibleItems.value(index)); 1921 } 1922 1923 if (supportsExpanding && firstSibblingIndex >= 0) { 1924 Q_ASSERT(lastSibblingIndex >= 0); 1925 updateSiblingsInformation(firstSibblingIndex, lastSibblingIndex); 1926 } 1927 1928 if (m_grouped) { 1929 // Update the layout of all visible group headers 1930 QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_visibleGroups); 1931 while (it.hasNext()) { 1932 it.next(); 1933 updateGroupHeaderLayout(it.key()); 1934 } 1935 } 1936 1937 emitOffsetChanges(); 1938 } 1939 1940 QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex, int lastVisibleIndex, LayoutAnimationHint hint) 1941 { 1942 // Determine all items that are completely invisible and might be 1943 // reused for items that just got (at least partly) visible. If the 1944 // animation hint is set to 'Animation' items that do e.g. an animated 1945 // moving of their position are not marked as invisible: This assures 1946 // that a scrolling inside the view can be done without breaking an animation. 1947 1948 QList<int> items; 1949 1950 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 1951 while (it.hasNext()) { 1952 it.next(); 1953 1954 KItemListWidget *widget = it.value(); 1955 const int index = widget->index(); 1956 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); 1957 1958 if (invisible) { 1959 if (m_animation->isStarted(widget)) { 1960 if (hint == NoAnimation) { 1961 // Stopping the animation will call KItemListView::slotAnimationFinished() 1962 // and the widget will be recycled if necessary there. 1963 m_animation->stop(widget); 1964 } 1965 } else { 1966 widget->setVisible(false); 1967 items.append(index); 1968 1969 if (m_grouped) { 1970 recycleGroupHeaderForWidget(widget); 1971 } 1972 } 1973 } 1974 } 1975 1976 return items; 1977 } 1978 1979 bool KItemListView::moveWidget(KItemListWidget *widget, const QPointF &newPos) 1980 { 1981 if (widget->pos() == newPos) { 1982 return false; 1983 } 1984 1985 bool startMovingAnim = false; 1986 1987 if (m_itemSize.isEmpty()) { 1988 // The items are not aligned in a grid but either as columns or rows. 1989 startMovingAnim = true; 1990 } else { 1991 // When having a grid the moving-animation should only be started, if it is done within 1992 // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation. 1993 // Otherwise instead of a moving-animation a create-animation on the new position will be used 1994 // instead. This is done to prevent overlapping (and confusing) moving-animations. 1995 const int index = widget->index(); 1996 const Cell cell = m_visibleCells.value(index); 1997 if (cell.column >= 0 && cell.row >= 0) { 1998 if (scrollOrientation() == Qt::Vertical) { 1999 startMovingAnim = (cell.row == m_layouter->itemRow(index)); 2000 } else { 2001 startMovingAnim = (cell.column == m_layouter->itemColumn(index)); 2002 } 2003 } 2004 } 2005 2006 if (startMovingAnim) { 2007 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); 2008 return true; 2009 } 2010 2011 m_animation->stop(widget); 2012 m_animation->start(widget, KItemListViewAnimation::CreateAnimation); 2013 return false; 2014 } 2015 2016 void KItemListView::emitOffsetChanges() 2017 { 2018 const qreal newScrollOffset = m_layouter->scrollOffset(); 2019 if (m_oldScrollOffset != newScrollOffset) { 2020 Q_EMIT scrollOffsetChanged(newScrollOffset, m_oldScrollOffset); 2021 m_oldScrollOffset = newScrollOffset; 2022 } 2023 2024 const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset(); 2025 if (m_oldMaximumScrollOffset != newMaximumScrollOffset) { 2026 Q_EMIT maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset); 2027 m_oldMaximumScrollOffset = newMaximumScrollOffset; 2028 } 2029 2030 const qreal newItemOffset = m_layouter->itemOffset(); 2031 if (m_oldItemOffset != newItemOffset) { 2032 Q_EMIT itemOffsetChanged(newItemOffset, m_oldItemOffset); 2033 m_oldItemOffset = newItemOffset; 2034 } 2035 2036 const qreal newMaximumItemOffset = m_layouter->maximumItemOffset(); 2037 if (m_oldMaximumItemOffset != newMaximumItemOffset) { 2038 Q_EMIT maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset); 2039 m_oldMaximumItemOffset = newMaximumItemOffset; 2040 } 2041 } 2042 2043 KItemListWidget *KItemListView::createWidget(int index) 2044 { 2045 KItemListWidget *widget = widgetCreator()->create(this); 2046 widget->setFlag(QGraphicsItem::ItemStacksBehindParent); 2047 2048 m_visibleItems.insert(index, widget); 2049 m_visibleCells.insert(index, Cell()); 2050 updateWidgetProperties(widget, index); 2051 initializeItemListWidget(widget); 2052 return widget; 2053 } 2054 2055 void KItemListView::recycleWidget(KItemListWidget *widget) 2056 { 2057 if (m_grouped) { 2058 recycleGroupHeaderForWidget(widget); 2059 } 2060 2061 const int index = widget->index(); 2062 m_visibleItems.remove(index); 2063 m_visibleCells.remove(index); 2064 2065 widgetCreator()->recycle(widget); 2066 } 2067 2068 void KItemListView::setWidgetIndex(KItemListWidget *widget, int index) 2069 { 2070 const int oldIndex = widget->index(); 2071 m_visibleItems.remove(oldIndex); 2072 m_visibleCells.remove(oldIndex); 2073 2074 m_visibleItems.insert(index, widget); 2075 m_visibleCells.insert(index, Cell()); 2076 2077 widget->setIndex(index); 2078 } 2079 2080 void KItemListView::moveWidgetToIndex(KItemListWidget *widget, int index) 2081 { 2082 const int oldIndex = widget->index(); 2083 const Cell oldCell = m_visibleCells.value(oldIndex); 2084 2085 setWidgetIndex(widget, index); 2086 2087 const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index)); 2088 const bool vertical = (scrollOrientation() == Qt::Vertical); 2089 const bool updateCell = (vertical && oldCell.row == newCell.row) || (!vertical && oldCell.column == newCell.column); 2090 if (updateCell) { 2091 m_visibleCells.insert(index, newCell); 2092 } 2093 } 2094 2095 void KItemListView::setLayouterSize(const QSizeF &size, SizeType sizeType) 2096 { 2097 switch (sizeType) { 2098 case LayouterSize: 2099 m_layouter->setSize(size); 2100 break; 2101 case ItemSize: 2102 m_layouter->setItemSize(size); 2103 break; 2104 default: 2105 break; 2106 } 2107 } 2108 2109 void KItemListView::updateWidgetProperties(KItemListWidget *widget, int index) 2110 { 2111 widget->setVisibleRoles(m_visibleRoles); 2112 updateWidgetColumnWidths(widget); 2113 widget->setStyleOption(m_styleOption); 2114 2115 const KItemListSelectionManager *selectionManager = m_controller->selectionManager(); 2116 2117 // In SingleSelection mode (e.g., in the Places Panel), the current item is 2118 // always the selected item. It is not necessary to highlight the current item then. 2119 if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { 2120 widget->setCurrent(index == selectionManager->currentItem()); 2121 } 2122 widget->setSelected(selectionManager->isSelected(index)); 2123 widget->setHovered(false); 2124 widget->setEnabledSelectionToggle(enabledSelectionToggles()); 2125 widget->setIndex(index); 2126 widget->setData(m_model->data(index)); 2127 widget->setSiblingsInformation(QBitArray()); 2128 updateAlternateBackgroundForWidget(widget); 2129 2130 if (m_grouped) { 2131 updateGroupHeaderForWidget(widget); 2132 } 2133 } 2134 2135 void KItemListView::updateGroupHeaderForWidget(KItemListWidget *widget) 2136 { 2137 Q_ASSERT(m_grouped); 2138 2139 const int index = widget->index(); 2140 if (!m_layouter->isFirstGroupItem(index)) { 2141 // The widget does not represent the first item of a group 2142 // and hence requires no header 2143 recycleGroupHeaderForWidget(widget); 2144 return; 2145 } 2146 2147 const QList<QPair<int, QVariant>> groups = model()->groups(); 2148 if (groups.isEmpty() || !groupHeaderCreator()) { 2149 return; 2150 } 2151 2152 KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget); 2153 if (!groupHeader) { 2154 groupHeader = groupHeaderCreator()->create(this); 2155 groupHeader->setParentItem(widget); 2156 m_visibleGroups.insert(widget, groupHeader); 2157 connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); 2158 } 2159 Q_ASSERT(groupHeader->parentItem() == widget); 2160 2161 const int groupIndex = groupIndexForItem(index); 2162 Q_ASSERT(groupIndex >= 0); 2163 groupHeader->setData(groups.at(groupIndex).second); 2164 groupHeader->setRole(model()->sortRole()); 2165 groupHeader->setStyleOption(m_styleOption); 2166 groupHeader->setScrollOrientation(scrollOrientation()); 2167 groupHeader->setItemIndex(index); 2168 2169 groupHeader->show(); 2170 } 2171 2172 void KItemListView::updateGroupHeaderLayout(KItemListWidget *widget) 2173 { 2174 KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget); 2175 Q_ASSERT(groupHeader); 2176 2177 const int index = widget->index(); 2178 const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index); 2179 const QRectF itemRect = m_layouter->itemRect(index); 2180 2181 // The group-header is a child of the itemlist widget. Translate the 2182 // group header position to the relative position. 2183 if (scrollOrientation() == Qt::Vertical) { 2184 // In the vertical scroll orientation the group header should always span 2185 // the whole width no matter which temporary position the parent widget 2186 // has. In this case the x-position and width will be adjusted manually. 2187 const qreal x = -widget->x() - itemOffset(); 2188 const qreal width = maximumItemOffset(); 2189 groupHeader->setPos(x, -groupHeaderRect.height()); 2190 groupHeader->resize(width, groupHeaderRect.size().height()); 2191 } else { 2192 groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y()); 2193 groupHeader->resize(groupHeaderRect.size()); 2194 } 2195 } 2196 2197 void KItemListView::recycleGroupHeaderForWidget(KItemListWidget *widget) 2198 { 2199 KItemListGroupHeader *header = m_visibleGroups.value(widget); 2200 if (header) { 2201 header->setParentItem(nullptr); 2202 groupHeaderCreator()->recycle(header); 2203 m_visibleGroups.remove(widget); 2204 disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); 2205 } 2206 } 2207 2208 void KItemListView::updateVisibleGroupHeaders() 2209 { 2210 Q_ASSERT(m_grouped); 2211 m_layouter->markAsDirty(); 2212 2213 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 2214 while (it.hasNext()) { 2215 it.next(); 2216 updateGroupHeaderForWidget(it.value()); 2217 } 2218 } 2219 2220 int KItemListView::groupIndexForItem(int index) const 2221 { 2222 Q_ASSERT(m_grouped); 2223 2224 const QList<QPair<int, QVariant>> groups = model()->groups(); 2225 if (groups.isEmpty()) { 2226 return -1; 2227 } 2228 2229 int min = 0; 2230 int max = groups.count() - 1; 2231 int mid = 0; 2232 do { 2233 mid = (min + max) / 2; 2234 if (index > groups[mid].first) { 2235 min = mid + 1; 2236 } else { 2237 max = mid - 1; 2238 } 2239 } while (groups[mid].first != index && min <= max); 2240 2241 if (min > max) { 2242 while (groups[mid].first > index && mid > 0) { 2243 --mid; 2244 } 2245 } 2246 2247 return mid; 2248 } 2249 2250 void KItemListView::updateAlternateBackgrounds() 2251 { 2252 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 2253 while (it.hasNext()) { 2254 it.next(); 2255 updateAlternateBackgroundForWidget(it.value()); 2256 } 2257 } 2258 2259 void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget *widget) 2260 { 2261 bool enabled = useAlternateBackgrounds(); 2262 if (enabled) { 2263 const int index = widget->index(); 2264 enabled = (index & 0x1) > 0; 2265 if (m_grouped) { 2266 const int groupIndex = groupIndexForItem(index); 2267 if (groupIndex >= 0) { 2268 const QList<QPair<int, QVariant>> groups = model()->groups(); 2269 const int indexOfFirstGroupItem = groups[groupIndex].first; 2270 const int relativeIndex = index - indexOfFirstGroupItem; 2271 enabled = (relativeIndex & 0x1) > 0; 2272 } 2273 } 2274 } 2275 widget->setAlternateBackground(enabled); 2276 } 2277 2278 bool KItemListView::useAlternateBackgrounds() const 2279 { 2280 return m_alternateBackgrounds && m_itemSize.isEmpty(); 2281 } 2282 2283 QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList &itemRanges) const 2284 { 2285 QElapsedTimer timer; 2286 timer.start(); 2287 2288 QHash<QByteArray, qreal> widths; 2289 2290 // Calculate the minimum width for each column that is required 2291 // to show the headline unclipped. 2292 const QFontMetricsF fontMetrics(m_headerWidget->font()); 2293 const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin); 2294 const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin); 2295 for (const QByteArray &visibleRole : std::as_const(m_visibleRoles)) { 2296 const QString headerText = m_model->roleDescription(visibleRole); 2297 const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2; 2298 widths.insert(visibleRole, headerWidth); 2299 } 2300 2301 // Calculate the preferred column widths for each item and ignore values 2302 // smaller than the width for showing the headline unclipped. 2303 const KItemListWidgetCreatorBase *creator = widgetCreator(); 2304 int calculatedItemCount = 0; 2305 bool maxTimeExceeded = false; 2306 for (const KItemRange &itemRange : itemRanges) { 2307 const int startIndex = itemRange.index; 2308 const int endIndex = startIndex + itemRange.count - 1; 2309 2310 for (int i = startIndex; i <= endIndex; ++i) { 2311 for (const QByteArray &visibleRole : std::as_const(m_visibleRoles)) { 2312 qreal maxWidth = widths.value(visibleRole, 0); 2313 const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this); 2314 maxWidth = qMax(width, maxWidth); 2315 widths.insert(visibleRole, maxWidth); 2316 } 2317 2318 if (calculatedItemCount > 100 && timer.elapsed() > 200) { 2319 // When having several thousands of items calculating the sizes can get 2320 // very expensive. We accept a possibly too small role-size in favour 2321 // of having no blocking user interface. 2322 maxTimeExceeded = true; 2323 break; 2324 } 2325 ++calculatedItemCount; 2326 } 2327 if (maxTimeExceeded) { 2328 break; 2329 } 2330 } 2331 2332 return widths; 2333 } 2334 2335 void KItemListView::applyColumnWidthsFromHeader() 2336 { 2337 // Apply the new size to the layouter 2338 const qreal requiredWidth = columnWidthsSum() + 2 * m_headerWidget->sidePadding(); 2339 const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), m_itemSize.height()); 2340 m_layouter->setItemSize(dynamicItemSize); 2341 2342 // Update the role sizes for all visible widgets 2343 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 2344 while (it.hasNext()) { 2345 it.next(); 2346 updateWidgetColumnWidths(it.value()); 2347 } 2348 } 2349 2350 void KItemListView::updateWidgetColumnWidths(KItemListWidget *widget) 2351 { 2352 for (const QByteArray &role : std::as_const(m_visibleRoles)) { 2353 widget->setColumnWidth(role, m_headerWidget->columnWidth(role)); 2354 } 2355 widget->setSidePadding(m_headerWidget->sidePadding()); 2356 } 2357 2358 void KItemListView::updatePreferredColumnWidths(const KItemRangeList &itemRanges) 2359 { 2360 Q_ASSERT(m_itemSize.isEmpty()); 2361 const int itemCount = m_model->count(); 2362 int rangesItemCount = 0; 2363 for (const KItemRange &range : itemRanges) { 2364 rangesItemCount += range.count; 2365 } 2366 2367 if (itemCount == rangesItemCount) { 2368 const QHash<QByteArray, qreal> preferredWidths = preferredColumnWidths(itemRanges); 2369 for (const QByteArray &role : std::as_const(m_visibleRoles)) { 2370 m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role)); 2371 } 2372 } else { 2373 // Only a sub range of the roles need to be determined. 2374 // The chances are good that the widths of the sub ranges 2375 // already fit into the available widths and hence no 2376 // expensive update might be required. 2377 bool changed = false; 2378 2379 const QHash<QByteArray, qreal> updatedWidths = preferredColumnWidths(itemRanges); 2380 QHashIterator<QByteArray, qreal> it(updatedWidths); 2381 while (it.hasNext()) { 2382 it.next(); 2383 const QByteArray &role = it.key(); 2384 const qreal updatedWidth = it.value(); 2385 const qreal currentWidth = m_headerWidget->preferredColumnWidth(role); 2386 if (updatedWidth > currentWidth) { 2387 m_headerWidget->setPreferredColumnWidth(role, updatedWidth); 2388 changed = true; 2389 } 2390 } 2391 2392 if (!changed) { 2393 // All the updated sizes are smaller than the current sizes and no change 2394 // of the stretched roles-widths is required 2395 return; 2396 } 2397 } 2398 2399 if (m_headerWidget->automaticColumnResizing()) { 2400 applyAutomaticColumnWidths(); 2401 } 2402 } 2403 2404 void KItemListView::updatePreferredColumnWidths() 2405 { 2406 if (m_model) { 2407 updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count())); 2408 } 2409 } 2410 2411 void KItemListView::applyAutomaticColumnWidths() 2412 { 2413 Q_ASSERT(m_itemSize.isEmpty()); 2414 Q_ASSERT(m_headerWidget->automaticColumnResizing()); 2415 if (m_visibleRoles.isEmpty()) { 2416 return; 2417 } 2418 2419 // Calculate the maximum size of an item by considering the 2420 // visible role sizes and apply them to the layouter. If the 2421 // size does not use the available view-size the size of the 2422 // first role will get stretched. 2423 2424 for (const QByteArray &role : std::as_const(m_visibleRoles)) { 2425 const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role); 2426 m_headerWidget->setColumnWidth(role, preferredWidth); 2427 } 2428 2429 const QByteArray firstRole = m_visibleRoles.first(); 2430 qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole); 2431 QSizeF dynamicItemSize = m_itemSize; 2432 2433 qreal requiredWidth = columnWidthsSum() + 2 * m_headerWidget->sidePadding(); // Adding the padding a second time so we have the same padding 2434 // symmetrically on both sides of the view. This improves UX, looks better and increases the chances of users figuring out that the padding 2435 // area can be used for deselecting and dropping files. 2436 const qreal availableWidth = size().width(); 2437 if (requiredWidth < availableWidth) { 2438 // Stretch the first column to use the whole remaining width 2439 firstColumnWidth += availableWidth - requiredWidth; 2440 m_headerWidget->setColumnWidth(firstRole, firstColumnWidth); 2441 } else if (requiredWidth > availableWidth && m_visibleRoles.count() > 1) { 2442 // Shrink the first column to be able to show as much other 2443 // columns as possible 2444 qreal shrinkedFirstColumnWidth = firstColumnWidth - requiredWidth + availableWidth; 2445 2446 // TODO: A proper calculation of the minimum width depends on the implementation 2447 // of KItemListWidget. Probably a kind of minimum size-hint should be introduced 2448 // later. 2449 const qreal minWidth = qMin(firstColumnWidth, qreal(m_styleOption.iconSize * 2 + 200)); 2450 if (shrinkedFirstColumnWidth < minWidth) { 2451 shrinkedFirstColumnWidth = minWidth; 2452 } 2453 2454 m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth); 2455 requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth; 2456 } 2457 2458 dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth); 2459 2460 m_layouter->setItemSize(dynamicItemSize); 2461 2462 // Update the role sizes for all visible widgets 2463 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 2464 while (it.hasNext()) { 2465 it.next(); 2466 updateWidgetColumnWidths(it.value()); 2467 } 2468 } 2469 2470 qreal KItemListView::columnWidthsSum() const 2471 { 2472 qreal widthsSum = 0; 2473 for (const QByteArray &role : std::as_const(m_visibleRoles)) { 2474 widthsSum += m_headerWidget->columnWidth(role); 2475 } 2476 return widthsSum; 2477 } 2478 2479 QRectF KItemListView::headerBoundaries() const 2480 { 2481 return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF(); 2482 } 2483 2484 bool KItemListView::changesItemGridLayout(const QSizeF &newGridSize, const QSizeF &newItemSize, const QSizeF &newItemMargin) const 2485 { 2486 if (newItemSize.isEmpty() || newGridSize.isEmpty()) { 2487 return false; 2488 } 2489 2490 if (m_layouter->scrollOrientation() == Qt::Vertical) { 2491 const qreal itemWidth = m_layouter->itemSize().width(); 2492 if (itemWidth > 0) { 2493 const int newColumnCount = itemsPerSize(newGridSize.width(), newItemSize.width(), newItemMargin.width()); 2494 if (m_model->count() > newColumnCount) { 2495 const int oldColumnCount = itemsPerSize(m_layouter->size().width(), itemWidth, m_layouter->itemMargin().width()); 2496 return oldColumnCount != newColumnCount; 2497 } 2498 } 2499 } else { 2500 const qreal itemHeight = m_layouter->itemSize().height(); 2501 if (itemHeight > 0) { 2502 const int newRowCount = itemsPerSize(newGridSize.height(), newItemSize.height(), newItemMargin.height()); 2503 if (m_model->count() > newRowCount) { 2504 const int oldRowCount = itemsPerSize(m_layouter->size().height(), itemHeight, m_layouter->itemMargin().height()); 2505 return oldRowCount != newRowCount; 2506 } 2507 } 2508 } 2509 2510 return false; 2511 } 2512 2513 bool KItemListView::animateChangedItemCount(int changedItemCount) const 2514 { 2515 if (m_itemSize.isEmpty()) { 2516 // We have only columns or only rows, but no grid: An animation is usually 2517 // welcome when inserting or removing items. 2518 return !supportsItemExpanding(); 2519 } 2520 2521 if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) { 2522 return false; 2523 } 2524 2525 const int maximum = (scrollOrientation() == Qt::Vertical) ? m_layouter->size().width() / m_layouter->itemSize().width() 2526 : m_layouter->size().height() / m_layouter->itemSize().height(); 2527 // Only animate if up to 2/3 of a row or column are inserted or removed 2528 return changedItemCount <= maximum * 2 / 3; 2529 } 2530 2531 bool KItemListView::scrollBarRequired(const QSizeF &size) const 2532 { 2533 const QSizeF oldSize = m_layouter->size(); 2534 2535 m_layouter->setSize(size); 2536 const qreal maxOffset = m_layouter->maximumScrollOffset(); 2537 m_layouter->setSize(oldSize); 2538 2539 return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height() : maxOffset > size.width(); 2540 } 2541 2542 int KItemListView::showDropIndicator(const QPointF &pos) 2543 { 2544 QHashIterator<int, KItemListWidget *> it(m_visibleItems); 2545 while (it.hasNext()) { 2546 it.next(); 2547 const KItemListWidget *widget = it.value(); 2548 2549 const QPointF mappedPos = widget->mapFromItem(this, pos); 2550 const QRectF rect = itemRect(widget->index()); 2551 if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) { 2552 if (m_model->supportsDropping(widget->index())) { 2553 // Keep 30% of the rectangle as the gap instead of always having a fixed gap 2554 const int gap = qMax(qreal(4.0), qreal(0.3) * rect.height()); 2555 if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) { 2556 return -1; 2557 } 2558 } 2559 2560 const bool isAboveItem = (mappedPos.y() < rect.height() / 2); 2561 const qreal y = isAboveItem ? rect.top() : rect.bottom(); 2562 2563 const QRectF draggingInsertIndicator(rect.left(), y, rect.width(), 1); 2564 if (m_dropIndicator != draggingInsertIndicator) { 2565 m_dropIndicator = draggingInsertIndicator; 2566 update(); 2567 } 2568 2569 int index = widget->index(); 2570 if (!isAboveItem) { 2571 ++index; 2572 } 2573 return index; 2574 } 2575 } 2576 2577 const QRectF firstItemRect = itemRect(firstVisibleIndex()); 2578 return (pos.y() <= firstItemRect.top()) ? 0 : -1; 2579 } 2580 2581 void KItemListView::hideDropIndicator() 2582 { 2583 if (!m_dropIndicator.isNull()) { 2584 m_dropIndicator = QRectF(); 2585 update(); 2586 } 2587 } 2588 2589 void KItemListView::updateGroupHeaderHeight() 2590 { 2591 qreal groupHeaderHeight = m_styleOption.fontMetrics.height(); 2592 qreal groupHeaderMargin = 0; 2593 2594 if (scrollOrientation() == Qt::Horizontal) { 2595 // The vertical margin above and below the header should be 2596 // equal to the horizontal margin, not the vertical margin 2597 // from m_styleOption. 2598 groupHeaderHeight += 2 * m_styleOption.horizontalMargin; 2599 groupHeaderMargin = m_styleOption.horizontalMargin; 2600 } else if (m_itemSize.isEmpty()) { 2601 groupHeaderHeight += 4 * m_styleOption.padding; 2602 groupHeaderMargin = m_styleOption.iconSize / 2; 2603 } else { 2604 groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin; 2605 groupHeaderMargin = m_styleOption.iconSize / 4; 2606 } 2607 m_layouter->setGroupHeaderHeight(groupHeaderHeight); 2608 m_layouter->setGroupHeaderMargin(groupHeaderMargin); 2609 2610 updateVisibleGroupHeaders(); 2611 } 2612 2613 void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) 2614 { 2615 if (!supportsItemExpanding() || !m_model) { 2616 return; 2617 } 2618 2619 if (firstIndex < 0 || lastIndex < 0) { 2620 firstIndex = m_layouter->firstVisibleIndex(); 2621 lastIndex = m_layouter->lastVisibleIndex(); 2622 } else { 2623 const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() && lastIndex >= m_layouter->firstVisibleIndex()); 2624 if (!isRangeVisible) { 2625 return; 2626 } 2627 } 2628 2629 int previousParents = 0; 2630 QBitArray previousSiblings; 2631 2632 // The rootIndex describes the first index where the siblings get 2633 // calculated from. For the calculation the upper most parent item 2634 // is required. For performance reasons it is checked first whether 2635 // the visible items before or after the current range already 2636 // contain a siblings information which can be used as base. 2637 int rootIndex = firstIndex; 2638 2639 KItemListWidget *widget = m_visibleItems.value(firstIndex - 1); 2640 if (!widget) { 2641 // There is no visible widget before the range, check whether there 2642 // is one after the range: 2643 widget = m_visibleItems.value(lastIndex + 1); 2644 if (widget) { 2645 // The sibling information of the widget may only be used if 2646 // all items of the range have the same number of parents. 2647 const int parents = m_model->expandedParentsCount(lastIndex + 1); 2648 for (int i = lastIndex; i >= firstIndex; --i) { 2649 if (m_model->expandedParentsCount(i) != parents) { 2650 widget = nullptr; 2651 break; 2652 } 2653 } 2654 } 2655 } 2656 2657 if (widget) { 2658 // Performance optimization: Use the sibling information of the visible 2659 // widget beside the given range. 2660 previousSiblings = widget->siblingsInformation(); 2661 if (previousSiblings.isEmpty()) { 2662 return; 2663 } 2664 previousParents = previousSiblings.count() - 1; 2665 previousSiblings.truncate(previousParents); 2666 } else { 2667 // Potentially slow path: Go back to the upper most parent of firstIndex 2668 // to be able to calculate the initial value for the siblings. 2669 while (rootIndex > 0 && m_model->expandedParentsCount(rootIndex) > 0) { 2670 --rootIndex; 2671 } 2672 } 2673 2674 Q_ASSERT(previousParents >= 0); 2675 for (int i = rootIndex; i <= lastIndex; ++i) { 2676 // Update the parent-siblings in case if the current item represents 2677 // a child or an upper parent. 2678 const int currentParents = m_model->expandedParentsCount(i); 2679 Q_ASSERT(currentParents >= 0); 2680 if (previousParents < currentParents) { 2681 previousParents = currentParents; 2682 previousSiblings.resize(currentParents); 2683 previousSiblings.setBit(currentParents - 1, hasSiblingSuccessor(i - 1)); 2684 } else if (previousParents > currentParents) { 2685 previousParents = currentParents; 2686 previousSiblings.truncate(currentParents); 2687 } 2688 2689 if (i >= firstIndex) { 2690 // The index represents a visible item. Apply the parent-siblings 2691 // and update the sibling of the current item. 2692 KItemListWidget *widget = m_visibleItems.value(i); 2693 if (!widget) { 2694 continue; 2695 } 2696 2697 QBitArray siblings = previousSiblings; 2698 siblings.resize(siblings.count() + 1); 2699 siblings.setBit(siblings.count() - 1, hasSiblingSuccessor(i)); 2700 2701 widget->setSiblingsInformation(siblings); 2702 } 2703 } 2704 } 2705 2706 bool KItemListView::hasSiblingSuccessor(int index) const 2707 { 2708 bool hasSuccessor = false; 2709 const int parentsCount = m_model->expandedParentsCount(index); 2710 int successorIndex = index + 1; 2711 2712 // Search the next sibling 2713 const int itemCount = m_model->count(); 2714 while (successorIndex < itemCount) { 2715 const int currentParentsCount = m_model->expandedParentsCount(successorIndex); 2716 if (currentParentsCount == parentsCount) { 2717 hasSuccessor = true; 2718 break; 2719 } else if (currentParentsCount < parentsCount) { 2720 break; 2721 } 2722 ++successorIndex; 2723 } 2724 2725 if (m_grouped && hasSuccessor) { 2726 // If the sibling is part of another group, don't mark it as 2727 // successor as the group header is between the sibling connections. 2728 for (int i = index + 1; i <= successorIndex; ++i) { 2729 if (m_layouter->isFirstGroupItem(i)) { 2730 hasSuccessor = false; 2731 break; 2732 } 2733 } 2734 } 2735 2736 return hasSuccessor; 2737 } 2738 2739 void KItemListView::disconnectRoleEditingSignals(int index) 2740 { 2741 KStandardItemListWidget *widget = qobject_cast<KStandardItemListWidget *>(m_visibleItems.value(index)); 2742 if (!widget) { 2743 return; 2744 } 2745 2746 disconnect(widget, &KItemListWidget::roleEditingCanceled, this, nullptr); 2747 disconnect(widget, &KItemListWidget::roleEditingFinished, this, nullptr); 2748 disconnect(this, &KItemListView::scrollOffsetChanged, widget, nullptr); 2749 } 2750 2751 int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) 2752 { 2753 int inc = 0; 2754 2755 const int minSpeed = 4; 2756 const int maxSpeed = 128; 2757 const int speedLimiter = 96; 2758 const int autoScrollBorder = 64; 2759 2760 // Limit the increment that is allowed to be added in comparison to 'oldInc'. 2761 // This assures that the autoscrolling speed grows gradually. 2762 const int incLimiter = 1; 2763 2764 if (pos < autoScrollBorder) { 2765 inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter; 2766 inc = qMax(inc, -maxSpeed); 2767 inc = qMax(inc, oldInc - incLimiter); 2768 } else if (pos > range - autoScrollBorder) { 2769 inc = minSpeed + qAbs(pos - range + autoScrollBorder) * (pos - range + autoScrollBorder) / speedLimiter; 2770 inc = qMin(inc, maxSpeed); 2771 inc = qMin(inc, oldInc + incLimiter); 2772 } 2773 2774 return inc; 2775 } 2776 2777 int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin) 2778 { 2779 const qreal availableSize = size - itemMargin; 2780 const int count = availableSize / (itemSize + itemMargin); 2781 return count; 2782 } 2783 2784 KItemListCreatorBase::~KItemListCreatorBase() 2785 { 2786 qDeleteAll(m_recycleableWidgets); 2787 qDeleteAll(m_createdWidgets); 2788 } 2789 2790 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget *widget) 2791 { 2792 m_createdWidgets.insert(widget); 2793 } 2794 2795 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget *widget) 2796 { 2797 Q_ASSERT(m_createdWidgets.contains(widget)); 2798 m_createdWidgets.remove(widget); 2799 2800 if (m_recycleableWidgets.count() < 100) { 2801 m_recycleableWidgets.append(widget); 2802 widget->setVisible(false); 2803 } else { 2804 delete widget; 2805 } 2806 } 2807 2808 QGraphicsWidget *KItemListCreatorBase::popRecycleableWidget() 2809 { 2810 if (m_recycleableWidgets.isEmpty()) { 2811 return nullptr; 2812 } 2813 2814 QGraphicsWidget *widget = m_recycleableWidgets.takeLast(); 2815 m_createdWidgets.insert(widget); 2816 return widget; 2817 } 2818 2819 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase() 2820 { 2821 } 2822 2823 void KItemListWidgetCreatorBase::recycle(KItemListWidget *widget) 2824 { 2825 widget->setParentItem(nullptr); 2826 widget->setOpacity(1.0); 2827 pushRecycleableWidget(widget); 2828 } 2829 2830 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase() 2831 { 2832 } 2833 2834 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader *header) 2835 { 2836 header->setOpacity(1.0); 2837 pushRecycleableWidget(header); 2838 } 2839 2840 #include "moc_kitemlistview.cpp"