m_sizeHintResolver(nullptr),
m_layouter(nullptr),
m_animation(nullptr),
- m_layoutTimer(nullptr),
m_oldScrollOffset(0),
m_oldMaximumScrollOffset(0),
m_oldItemOffset(0),
connect(m_animation, &KItemListViewAnimation::finished,
this, &KItemListView::slotAnimationFinished);
- m_layoutTimer = new QTimer(this);
- m_layoutTimer->setInterval(300);
- m_layoutTimer->setSingleShot(true);
- connect(m_layoutTimer, &QTimer::timeout, this, &KItemListView::slotLayoutTimerFinished);
-
m_rubberBand = new KItemListRubberBand(this);
connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged);
m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
}
-
- // Triggering a synchronous layout is fine from a performance point of view,
- // as with dynamic item sizes no moving animation must be done.
- m_layouter->setSize(newSize);
- doLayout(NoAnimation);
- } else {
- const bool animate = !changesItemGridLayout(newSize,
- m_layouter->itemSize(),
- m_layouter->itemMargin());
- m_layouter->setSize(newSize);
-
- if (animate) {
- // Trigger an asynchronous relayout with m_layoutTimer to prevent
- // performance bottlenecks. If the timer is exceeded, an animated layout
- // will be triggered.
- if (!m_layoutTimer->isActive()) {
- m_layoutTimer->start();
- }
- } else {
- m_layoutTimer->stop();
- doLayout(NoAnimation);
- }
}
+
+ m_layouter->setSize(newSize);
+ // We don't animate the moving of the items here because
+ // it would look like the items are slow to find their position.
+ doLayout(NoAnimation);
}
qreal KItemListView::verticalPageStep() const
return size().height() - headerHeight;
}
-int KItemListView::itemAt(const QPointF& pos) const
+std::optional<int> KItemListView::itemAt(const QPointF& pos) const
{
QHashIterator<int, KItemListWidget*> it(m_visibleItems);
while (it.hasNext()) {
const KItemListWidget* widget = it.value();
const QPointF mappedPos = widget->mapFromItem(this, pos);
- if (widget->contains(mappedPos)) {
+ if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
return it.key();
}
}
- return -1;
+ return std::nullopt;
}
bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
return m_layouter->lastVisibleIndex();
}
-void KItemListView::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint) const
+void KItemListView::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint) const
{
widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this);
}
return m_supportsItemExpanding;
}
+void KItemListView::setHighlightEntireRow(bool highlightEntireRow)
+{
+ if (m_highlightEntireRow != highlightEntireRow) {
+ m_highlightEntireRow = highlightEntireRow;
+ onHighlightEntireRowChanged(highlightEntireRow);
+ }
+}
+
+bool KItemListView::highlightEntireRow() const
+{
+ return m_highlightEntireRow;
+}
+
+void KItemListView::setAlternateBackgrounds(bool alternate)
+{
+ if (m_alternateBackgrounds != alternate) {
+ m_alternateBackgrounds = alternate;
+ updateAlternateBackgrounds();
+ }
+}
+
+bool KItemListView::alternateBackgrounds() const
+{
+ return m_alternateBackgrounds;
+}
+
QRectF KItemListView::itemRect(int index) const
{
return m_layouter->itemRect(index);
return contextRect;
}
+bool KItemListView::isElided(int index) const
+{
+ return m_sizeHintResolver->isElided(index);
+}
+
void KItemListView::scrollToItem(int index)
{
QRectF viewGeometry = geometry();
if (newOffset != scrollOffset()) {
Q_EMIT scrollTo(newOffset);
+ return;
}
}
+
+ Q_EMIT scrollingStopped();
}
void KItemListView::beginTransaction()
connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
this, &KItemListView::slotHeaderColumnWidthChanged);
+ connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+ this, &KItemListView::slotLeadingPaddingChanged);
connect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
this, &KItemListView::slotHeaderColumnMoved);
connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
} else if (!visible && m_headerWidget->isVisible()) {
disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
this, &KItemListView::slotHeaderColumnWidthChanged);
+ disconnect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+ this, &KItemListView::slotLeadingPaddingChanged);
disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
this, &KItemListView::slotHeaderColumnMoved);
disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
size,
m_layouter->itemMargin());
- const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) &&
+ const bool alternateBackgroundsChanged = m_alternateBackgrounds &&
(( m_itemSize.isEmpty() && !size.isEmpty()) ||
(!m_itemSize.isEmpty() && size.isEmpty()));
Q_UNUSED(previous)
}
+void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow)
+{
+ Q_UNUSED(highlightEntireRow)
+}
+
void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
{
Q_UNUSED(supportsExpanding)
if (updateSizeHints) {
m_sizeHintResolver->itemsChanged(index, count, roles);
m_layouter->markAsDirty();
-
- if (!m_layoutTimer->isActive()) {
- m_layoutTimer->start();
- }
}
// Apply the changed roles to the visible item-widgets
ev.setLastRow(itemRange.index + itemRange.count);
QAccessible::updateAccessibility(&ev);
}
+
+ doLayout(NoAnimation);
}
void KItemListView::slotGroupsChanged()
KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
Q_ASSERT(itemListWidget);
- switch (type) {
- case KItemListViewAnimation::DeleteAnimation: {
+ if (type == KItemListViewAnimation::DeleteAnimation) {
// As we recycle the widget in this case it is important to assure that no
// other animation has been started. This is a convention in KItemListView and
// not a requirement defined by KItemListViewAnimation.
// been finished.
recycleGroupHeaderForWidget(itemListWidget);
widgetCreator()->recycle(itemListWidget);
- break;
- }
-
- case KItemListViewAnimation::CreateAnimation:
- case KItemListViewAnimation::MovingAnimation:
- case KItemListViewAnimation::ResizeAnimation: {
+ } else {
const int index = itemListWidget->index();
const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
(index > m_layouter->lastVisibleIndex());
if (invisible && !m_animation->isStarted(itemListWidget)) {
recycleWidget(itemListWidget);
}
- break;
- }
-
- default: break;
}
}
-void KItemListView::slotLayoutTimerFinished()
-{
- m_layouter->setSize(geometry().size());
- doLayout(Animation);
-}
-
void KItemListView::slotRubberBandPosChanged()
{
update();
doLayout(NoAnimation);
}
+void KItemListView::slotLeadingPaddingChanged(qreal width)
+{
+ Q_UNUSED(width)
+ if (m_headerWidget->automaticColumnResizing()) {
+ applyAutomaticColumnWidths();
+ }
+ applyColumnWidthsFromHeader();
+ doLayout(NoAnimation);
+}
+
void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
int currentIndex,
int previousIndex)
{
disconnectRoleEditingSignals(index);
- Q_EMIT roleEditingCanceled(index, role, value);
m_editingRole = false;
+ Q_EMIT roleEditingCanceled(index, role, value);
}
void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value)
{
disconnectRoleEditingSignals(index);
- Q_EMIT roleEditingFinished(index, role, value);
m_editingRole = false;
+ Q_EMIT roleEditingFinished(index, role, value);
}
void KItemListView::setController(KItemListController* controller)
void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
{
- if (m_layoutTimer->isActive()) {
- m_layoutTimer->stop();
- }
-
if (m_activeTransactions > 0) {
if (hint == NoAnimation) {
// As soon as at least one property change should be done without animation,
const bool animate = (hint == Animation);
for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
bool applyNewPos = true;
- bool wasHidden = false;
const QRectF itemBounds = m_layouter->itemRect(i);
const QPointF newPos = itemBounds.topLeft();
KItemListWidget* widget = m_visibleItems.value(i);
if (!widget) {
- wasHidden = true;
if (!reusableItems.isEmpty()) {
// Reuse a KItemListWidget instance from an invisible item
const int oldIndex = reusableItems.takeLast();
// prevents a "move animation mess" when inserting several ranges in parallel.
applyNewPos = !moveWidget(widget, newPos);
}
- } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
- // The size of the view might have been changed. Animate the moving of the position.
- applyNewPos = !moveWidget(widget, newPos);
}
} else {
m_animation->stop(widget);
Q_ASSERT(widget->index() == i);
widget->setVisible(true);
+ bool animateIconResizing = animate;
+
if (widget->size() != itemBounds.size()) {
// Resize the widget for the item to the changed size.
if (animate) {
} else {
widget->resize(itemBounds.size());
}
+ } else {
+ animateIconResizing = false;
+ }
+
+ const int newIconSize = widget->styleOption().iconSize;
+ if (widget->iconSize() != newIconSize) {
+ if (animateIconResizing) {
+ m_animation->start(widget, KItemListViewAnimation::IconResizeAnimation, newIconSize);
+ } else {
+ widget->setIconSize(newIconSize);
+ }
}
// Updating the cell-information must be done as last step: The decision whether the
bool KItemListView::useAlternateBackgrounds() const
{
- return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
+ return m_alternateBackgrounds && m_itemSize.isEmpty();
}
QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
const QString headerText = m_model->roleDescription(visibleRole);
- const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
+ const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2;
widths.insert(visibleRole, headerWidth);
}
- // Calculate the preferred column withs for each item and ignore values
+ // Calculate the preferred column widths for each item and ignore values
// smaller than the width for showing the headline unclipped.
const KItemListWidgetCreatorBase* creator = widgetCreator();
int calculatedItemCount = 0;
void KItemListView::applyColumnWidthsFromHeader()
{
// Apply the new size to the layouter
- const qreal requiredWidth = columnWidthsSum();
+ const qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
for (const QByteArray& role : qAsConst(m_visibleRoles)) {
widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
}
+ widget->setLeadingPadding(m_headerWidget->leadingPadding());
}
void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges)
qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
QSizeF dynamicItemSize = m_itemSize;
- qreal requiredWidth = columnWidthsSum();
+ qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding()
+ + m_headerWidget->leadingPadding(); // Adding the padding a second time so we have the same padding symmetrically on both sides of the view.
+ // This improves UX, looks better and increases the chances of users figuring out that the padding area can be used for deselecting and dropping files.
const qreal availableWidth = size().width();
if (requiredWidth < availableWidth) {
// Stretch the first column to use the whole remaining width
groupHeaderHeight += 2 * m_styleOption.horizontalMargin;
groupHeaderMargin = m_styleOption.horizontalMargin;
} else if (m_itemSize.isEmpty()){
- groupHeaderHeight += 2 * m_styleOption.padding;
+ groupHeaderHeight += 4 * m_styleOption.padding;
groupHeaderMargin = m_styleOption.iconSize / 2;
} else {
groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin;