#include "kitemlistview.h"
+#ifndef QT_NO_ACCESSIBILITY
+#include "accessibility/kitemlistcontaineraccessible.h"
+#include "accessibility/kitemlistdelegateaccessible.h"
+#include "accessibility/kitemlistviewaccessible.h"
+#endif
#include "dolphindebug.h"
#include "kitemlistcontainer.h"
#include "kitemlistcontroller.h"
#include "kitemlistheader.h"
#include "kitemlistselectionmanager.h"
-#include "kitemlistviewaccessible.h"
#include "kstandarditemlistwidget.h"
#include "private/kitemlistheaderwidget.h"
#include "private/kitemlistsizehintresolver.h"
#include "private/kitemlistviewlayouter.h"
+#include <optional>
+
#include <QElapsedTimer>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
, m_header(nullptr)
, m_headerWidget(nullptr)
, m_indicatorAnimation(nullptr)
+ , m_statusBarOffset(0)
, m_dropIndicator()
, m_sizeHintResolver(nullptr)
{
qreal KItemListView::maximumScrollOffset() const
{
- return m_layouter->maximumScrollOffset();
+ return m_layouter->maximumScrollOffset() + m_statusBarOffset;
}
void KItemListView::setItemOffset(qreal offset)
if (m_headerWidget->automaticColumnResizing()) {
applyAutomaticColumnWidths();
} else {
- const qreal requiredWidth = columnWidthsSum() + 2 * m_headerWidget->sidePadding();
+ const qreal requiredWidth = m_headerWidget->leftPadding() + columnWidthsSum() + m_headerWidget->rightPadding();
const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth), m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
}
doLayout(NoAnimation);
}
+qreal KItemListView::scrollSingleStep() const
+{
+ const QFontMetrics metrics(font());
+ return metrics.height();
+}
+
qreal KItemListView::verticalPageStep() const
{
qreal headerHeight = 0;
std::optional<int> KItemListView::itemAt(const QPointF &pos) const
{
+ if (headerBoundaries().contains(pos)) {
+ return std::nullopt;
+ }
+
QHashIterator<int, KItemListWidget *> it(m_visibleItems);
while (it.hasNext()) {
it.next();
const KItemListWidget *widget = it.value();
const QPointF mappedPos = widget->mapFromItem(this, pos);
- if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
+ if (widget->contains(mappedPos)) {
return it.key();
}
}
const KItemListWidget *widget = m_visibleItems.value(index);
if (widget) {
- contextRect = widget->iconRect() | widget->textRect();
+ contextRect = widget->selectionRectCore();
contextRect.translate(itemRect(index).topLeft());
}
const qreal headerHeight = m_headerWidget->size().height();
viewGeometry.adjust(0, headerHeight, 0, 0);
}
+ if (m_statusBarOffset != 0) {
+ viewGeometry.adjust(0, 0, 0, -m_statusBarOffset);
+ }
QRectF currentRect = itemRect(index);
+ if (layoutDirection() == Qt::RightToLeft && scrollOrientation() == Qt::Horizontal) {
+ currentRect.moveLeft(m_layouter->size().width() - currentRect.right());
+ }
+
// Fix for Bug 311099 - View the underscore when using Ctrl + PageDown
currentRect.adjust(-m_styleOption.horizontalMargin, -m_styleOption.verticalMargin, m_styleOption.horizontalMargin, m_styleOption.verticalMargin);
- qreal newOffset = scrollOffset();
- if (scrollOrientation() == Qt::Vertical && (currentRect.top() < viewGeometry.top() || currentRect.bottom() > viewGeometry.bottom())) {
- switch (viewItemPosition) {
- case Beginning:
- newOffset += currentRect.top() - viewGeometry.top();
- break;
- case Middle:
- newOffset += 0.5 * (currentRect.top() + currentRect.bottom() - (viewGeometry.top() + viewGeometry.bottom()));
- break;
- case End:
- newOffset += currentRect.bottom() - viewGeometry.bottom();
- break;
- case Nearest:
- if (currentRect.top() < viewGeometry.top()) {
- newOffset += currentRect.top() - viewGeometry.top();
- } else {
- newOffset += currentRect.bottom() - viewGeometry.bottom();
- }
- break;
- default:
- Q_UNREACHABLE();
- }
- } else if (scrollOrientation() == Qt::Horizontal && (currentRect.left() < viewGeometry.left() || currentRect.right() > viewGeometry.right())) {
- switch (viewItemPosition) {
- case Beginning:
- if (layoutDirection() == Qt::RightToLeft) {
- newOffset += currentRect.right() - viewGeometry.right();
- } else {
- newOffset += currentRect.left() - viewGeometry.left();
- }
- break;
- case Middle:
- newOffset += 0.5 * (currentRect.left() + currentRect.right() - (viewGeometry.left() + viewGeometry.right()));
- break;
- case End:
- if (layoutDirection() == Qt::RightToLeft) {
- newOffset += currentRect.left() - viewGeometry.left();
- } else {
- newOffset += currentRect.right() - viewGeometry.right();
+ qreal offset = 0;
+ switch (scrollOrientation()) {
+ case Qt::Vertical:
+ if (currentRect.top() < viewGeometry.top() || currentRect.bottom() > viewGeometry.bottom()) {
+ switch (viewItemPosition) {
+ case Beginning:
+ offset = currentRect.top() - viewGeometry.top();
+ break;
+ case Middle:
+ offset = 0.5 * (currentRect.top() + currentRect.bottom() - (viewGeometry.top() + viewGeometry.bottom()));
+ break;
+ case End:
+ offset = currentRect.bottom() - viewGeometry.bottom();
+ break;
+ case Nearest:
+ if (currentRect.top() < viewGeometry.top()) {
+ offset = currentRect.top() - viewGeometry.top();
+ }
+ if (currentRect.bottom() > viewGeometry.bottom() + offset) {
+ offset += currentRect.bottom() - viewGeometry.bottom() - offset;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
}
- break;
- case Nearest:
- if (currentRect.left() < viewGeometry.left()) {
- newOffset += currentRect.left() - viewGeometry.left();
- } else {
- newOffset += currentRect.right() - viewGeometry.right();
+ }
+ break;
+ case Qt::Horizontal:
+ if (currentRect.left() < viewGeometry.left() || currentRect.right() > viewGeometry.right()) {
+ switch (viewItemPosition) {
+ case Beginning:
+ if (layoutDirection() == Qt::RightToLeft) {
+ offset = currentRect.right() - viewGeometry.right();
+ } else {
+ offset = currentRect.left() - viewGeometry.left();
+ }
+ break;
+ case Middle:
+ offset = 0.5 * (currentRect.left() + currentRect.right() - (viewGeometry.left() + viewGeometry.right()));
+ break;
+ case End:
+ if (layoutDirection() == Qt::RightToLeft) {
+ offset = currentRect.left() - viewGeometry.left();
+ } else {
+ offset = currentRect.right() - viewGeometry.right();
+ }
+ break;
+ case Nearest:
+ if (layoutDirection() == Qt::RightToLeft) {
+ if (currentRect.left() < viewGeometry.left()) {
+ offset = currentRect.left() - viewGeometry.left();
+ }
+ if (currentRect.right() > viewGeometry.right() + offset) {
+ offset += currentRect.right() - viewGeometry.right() - offset;
+ }
+ } else {
+ if (currentRect.right() > viewGeometry.right()) {
+ offset = currentRect.right() - viewGeometry.right();
+ }
+ if (currentRect.left() < viewGeometry.left() + offset) {
+ offset += currentRect.left() - viewGeometry.left() - offset;
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
}
- break;
- default:
- Q_UNREACHABLE();
}
+ break;
+ default:
+ Q_UNREACHABLE();
}
- if (newOffset != scrollOffset()) {
- Q_EMIT scrollTo(newOffset);
+ if (!qFuzzyIsNull(offset)) {
+ Q_EMIT scrollTo(scrollOffset() + offset);
return;
}
void KItemListView::editRole(int index, const QByteArray &role)
{
KStandardItemListWidget *widget = qobject_cast<KStandardItemListWidget *>(m_visibleItems.value(index));
- if (!widget || m_editingRole) {
+ if (!widget) {
+ return;
+ }
+ if (m_editingRole || m_animation->isStarted(widget)) {
+ Q_EMIT widget->roleEditingCanceled(index, role, QVariant());
return;
}
m_editingRole = true;
+ m_controller->selectionManager()->setCurrentItem(index);
widget->setEditedRole(role);
connect(widget, &KItemListWidget::roleEditingCanceled, this, &KItemListView::slotRoleEditingCanceled);
}
}
+void KItemListView::setStatusBarOffset(int offset)
+{
+ if (m_statusBarOffset != offset) {
+ m_statusBarOffset = offset;
+ if (m_layouter) {
+ m_layouter->setStatusBarOffset(offset);
+ }
+ }
+}
+
QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemSceneHasChanged && scene()) {
const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index);
const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1);
+ /// Represents an item that was moved while being edited.
+ struct MovedEditedItem {
+ int movedToIndex;
+ QByteArray editedRole;
+ };
+ std::optional<MovedEditedItem> movedEditedItem;
for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) {
KItemListWidget *widget = m_visibleItems.value(index);
if (widget) {
+ if (m_editingRole && !widget->editedRole().isEmpty()) {
+ movedEditedItem = {movedToIndexes[index - itemRange.index], widget->editedRole()};
+ disconnectRoleEditingSignals(index);
+ m_editingRole = false;
+ }
updateWidgetProperties(widget, index);
initializeItemListWidget(widget);
}
doLayout(NoAnimation);
updateSiblingsInformation();
+
+ if (movedEditedItem) {
+ editRole(movedEditedItem->movedToIndex, movedEditedItem->editedRole);
+ }
}
void KItemListView::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles)
updateVisibleGroupHeaders();
doLayout(NoAnimation);
}
-
- QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged);
- ev.setFirstRow(itemRange.index);
- ev.setLastRow(itemRange.index + itemRange.count);
- QAccessible::updateAccessibility(&ev);
}
-
doLayout(NoAnimation);
}
void KItemListView::slotCurrentChanged(int current, int previous)
{
- Q_UNUSED(previous)
-
// In SingleSelection mode (e.g., in the Places Panel), the current item is
// always the selected item. It is not necessary to highlight the current item then.
if (m_controller->selectionBehavior() != KItemListController::SingleSelection) {
currentWidget->setCurrent(true);
}
}
-
- QAccessibleEvent ev(this, QAccessible::Focus);
- ev.setChild(current);
- QAccessible::updateAccessibility(&ev);
+#ifndef QT_NO_ACCESSIBILITY
+ if (current != previous && QAccessible::isActive()) {
+ static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(this))->announceCurrentItem();
+ }
+#endif
}
void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous)
{
- Q_UNUSED(previous)
-
QHashIterator<int, KItemListWidget *> it(m_visibleItems);
while (it.hasNext()) {
it.next();
const int index = it.key();
KItemListWidget *widget = it.value();
- widget->setSelected(current.contains(index));
+ const bool isSelected(current.contains(index));
+ widget->setSelected(isSelected);
+
+#ifndef QT_NO_ACCESSIBILITY
+ if (!QAccessible::isActive()) {
+ continue;
+ }
+ // Let the screen reader announce "selected" or "not selected" for the active item.
+ const bool wasSelected(previous.contains(index));
+ if (isSelected != wasSelected) {
+ QAccessibleEvent accessibleSelectionChangedEvent(this, QAccessible::SelectionAdd);
+ accessibleSelectionChangedEvent.setChild(index);
+ QAccessible::updateAccessibility(&accessibleSelectionChangedEvent);
+ }
}
+#else
+ }
+ Q_UNUSED(previous)
+#endif
}
void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type)
curve.addCubicBezierSegment(QPointF(0.4, 0.0), QPointF(1.0, 1.0), QPointF(1.0, 1.0));
animation->setEasingCurve(curve);
- connect(animation, &QVariantAnimation::valueChanged, this, [=](const QVariant &) {
+ connect(animation, &QVariantAnimation::valueChanged, this, [=, this](const QVariant &) {
update();
});
- connect(animation, &QVariantAnimation::finished, this, [=]() {
+ connect(animation, &QVariantAnimation::finished, this, [=, this]() {
m_rubberBandAnimations.removeAll(animation);
delete animation;
});
if (animate) {
if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
+ if (m_editingRole) {
+ Q_EMIT widget->roleEditingCanceled(widget->index(), QByteArray(), QVariant());
+ }
m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
applyNewPos = false;
}
void KItemListView::applyColumnWidthsFromHeader()
{
// Apply the new size to the layouter
- const qreal requiredWidth = columnWidthsSum() + 2 * m_headerWidget->sidePadding();
+ const qreal requiredWidth = m_headerWidget->leftPadding() + columnWidthsSum() + m_headerWidget->rightPadding();
const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
for (const QByteArray &role : std::as_const(m_visibleRoles)) {
widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
}
- widget->setSidePadding(m_headerWidget->sidePadding());
+ widget->setSidePadding(m_headerWidget->leftPadding(), m_headerWidget->rightPadding());
}
void KItemListView::updatePreferredColumnWidths(const KItemRangeList &itemRanges)
qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
QSizeF dynamicItemSize = m_itemSize;
- qreal requiredWidth = columnWidthsSum() + 2 * m_headerWidget->sidePadding(); // 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.
+ qreal requiredWidth = m_headerWidget->leftPadding() + columnWidthsSum() + m_headerWidget->rightPadding();
+ // By default we want 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