const bool horizontalScrolling = m_view->scrollOrientation() == Qt::Horizontal;
+ if (m_view->layoutDirection() == Qt::RightToLeft) {
+ // swap left and right arrow keys
+ switch (key) {
+ case Qt::Key_Left:
+ key = Qt::Key_Right;
+ break;
+ case Qt::Key_Right:
+ key = Qt::Key_Left;
+ break;
+ default:
+ break;
+ }
+ }
+
// Handle the expanding/collapsing of items
// expand / collapse all selected directories
if (m_view->supportsItemExpanding() && m_model->isExpandable(index) && (key == Qt::Key_Right || key == Qt::Key_Left)) {
}
}
- if (m_view->layoutDirection() == Qt::RightToLeft) {
- if (horizontalScrolling) {
- // swap up and down arrow keys
- switch (key) {
- case Qt::Key_Up:
- key = Qt::Key_Down;
- break;
- case Qt::Key_Down:
- key = Qt::Key_Up;
- break;
- default:
- break;
- }
- } else if (!m_view->supportsItemExpanding()) {
- // swap left and right arrow keys
- switch (key) {
- case Qt::Key_Left:
- key = Qt::Key_Right;
- break;
- case Qt::Key_Right:
- key = Qt::Key_Left;
- break;
- default:
- break;
- }
- }
- }
-
const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
if (selectSingleItem) {
return m_headerWidget->preferredColumnWidth(role);
}
-void KItemListHeader::setSidePadding(qreal width)
+void KItemListHeader::setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth)
{
- if (m_headerWidget->sidePadding() != width) {
- m_headerWidget->setSidePadding(width);
+ if (m_headerWidget->leftPadding() != leftPaddingWidth || m_headerWidget->rightPadding() != rightPaddingWidth) {
+ m_headerWidget->setSidePadding(leftPaddingWidth, rightPaddingWidth);
if (m_headerWidget->automaticColumnResizing()) {
m_view->applyAutomaticColumnWidths();
}
}
}
-qreal KItemListHeader::sidePadding() const
+qreal KItemListHeader::leftPadding() const
{
- return m_headerWidget->sidePadding();
+ return m_headerWidget->leftPadding();
+}
+
+qreal KItemListHeader::rightPadding() const
+{
+ return m_headerWidget->rightPadding();
}
KItemListHeader::KItemListHeader(KItemListView *listView)
qreal preferredColumnWidth(const QByteArray &role) const;
/**
- * Sets the width of the column *before* the first column.
+ * Sets the widths of the columns *before* the first column and *after* the last column.
* This is intended to facilitate an empty region for deselection in the main viewport.
*/
- void setSidePadding(qreal width);
- qreal sidePadding() const;
+ void setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth);
+ qreal leftPadding() const;
+ qreal rightPadding() const;
Q_SIGNALS:
- void sidePaddingChanged(qreal width);
+ void sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth);
/**
* Is emitted if the width of a column has been adjusted by the user with the mouse
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);
}
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
, m_data()
, m_visibleRoles()
, m_columnWidths()
- , m_sidePadding(0)
+ , m_leftPadding(0)
+ , m_rightPadding(0)
, m_styleOption()
, m_siblingsInfo()
, m_hoverOpacity(0)
return m_columnWidths.value(role);
}
-qreal KItemListWidget::sidePadding() const
+void KItemListWidget::setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth)
{
- return m_sidePadding;
+ bool changed = false;
+ if (m_leftPadding != leftPaddingWidth) {
+ m_leftPadding = leftPaddingWidth;
+ changed = true;
+ }
+
+ if (m_rightPadding != rightPaddingWidth) {
+ m_rightPadding = rightPaddingWidth;
+ changed = true;
+ }
+
+ if (!changed) {
+ return;
+ }
+
+ sidePaddingChanged(leftPaddingWidth, rightPaddingWidth);
+ update();
}
-void KItemListWidget::setSidePadding(qreal width)
+qreal KItemListWidget::leftPadding() const
{
- if (m_sidePadding != width) {
- m_sidePadding = width;
- sidePaddingChanged(width);
- update();
- }
+ return m_leftPadding;
+}
+
+qreal KItemListWidget::rightPadding() const
+{
+ return m_rightPadding;
}
void KItemListWidget::setStyleOption(const KItemListStyleOption &option)
Q_UNUSED(previous)
}
-void KItemListWidget::sidePaddingChanged(qreal width)
+void KItemListWidget::sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth)
{
- Q_UNUSED(width)
+ Q_UNUSED(leftPaddingWidth)
+ Q_UNUSED(rightPaddingWidth)
}
void KItemListWidget::styleOptionChanged(const KItemListStyleOption ¤t, const KItemListStyleOption &previous)
void setColumnWidth(const QByteArray &role, qreal width);
qreal columnWidth(const QByteArray &role) const;
- void setSidePadding(qreal width);
- qreal sidePadding() const;
+ void setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth);
+ qreal leftPadding() const;
+ qreal rightPadding() const;
void setStyleOption(const KItemListStyleOption &option);
const KItemListStyleOption &styleOption() const;
virtual void dataChanged(const QHash<QByteArray, QVariant> ¤t, const QSet<QByteArray> &roles = QSet<QByteArray>());
virtual void visibleRolesChanged(const QList<QByteArray> ¤t, const QList<QByteArray> &previous);
virtual void columnWidthChanged(const QByteArray &role, qreal current, qreal previous);
- virtual void sidePaddingChanged(qreal width);
+ virtual void sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth);
virtual void styleOptionChanged(const KItemListStyleOption ¤t, const KItemListStyleOption &previous);
virtual void currentChanged(bool current);
virtual void selectedChanged(bool selected);
QHash<QByteArray, QVariant> m_data;
QList<QByteArray> m_visibleRoles;
QHash<QByteArray, qreal> m_columnWidths;
- qreal m_sidePadding;
+ qreal m_leftPadding;
+ qreal m_rightPadding;
KItemListStyleOption m_styleOption;
QBitArray m_siblingsInfo;
painter->drawStaticText(textInfo->pos, textInfo->staticText);
bool clipAdditionalInfoBounds = false;
- if (m_supportsItemExpanding) {
- // Prevent a possible overlapping of the additional-information texts
- // with the icon. This can happen if the user has minimized the width
- // of the name-column to a very small value.
- const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding;
- if (textInfo->pos.x() + columnWidth("text") > minX) {
- clipAdditionalInfoBounds = true;
- painter->save();
- painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip);
+ if (m_supportsItemExpanding && m_sortedVisibleRoles.count() > 1) {
+ // Prevent a possible overlapping of the additional-information-texts with the icon.
+ // This will happen if the user has resized the width of the name-column to be very narrow while having folders expanded.
+ // We only want to draw additional info text outside the area of the icon or expansion area, so we set a clip rect that does not contain the icon area.
+ // This needs to work both for left-to-right as well as right-to-left layout directions.
+ const TextInfo *potentiallyOverlappingRoleText = m_textInfo.value(m_sortedVisibleRoles[1]); // Only the first column after the name column can overlap.
+ if (layoutDirection() == Qt::LeftToRight) { // In left-to-right languages the left end of text would overlap. This is mirrored for right-to-left.
+ const qreal minX = m_iconRect.right() + 2 * itemListStyleOption.padding;
+ if (potentiallyOverlappingRoleText->pos.x() < minX) {
+ clipAdditionalInfoBounds = true;
+ painter->save();
+ painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip);
+ }
+ } else {
+ const qreal maxX = m_iconRect.left() - 2 * itemListStyleOption.padding;
+ if (potentiallyOverlappingRoleText->pos.x() + m_customizedFontMetrics.horizontalAdvance(potentiallyOverlappingRoleText->staticText.text()) > maxX) {
+ clipAdditionalInfoBounds = true;
+ painter->save();
+ painter->setClipRect(0, 0, maxX, size().height(), Qt::IntersectClip);
+ }
}
}
QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding);
QRectF result = adjustedIconRect | m_textRect;
if (m_highlightEntireRow) {
- result.setRight(m_columnWidthSum + sidePadding());
+ if (layoutDirection() == Qt::LeftToRight) {
+ result.setRight(leftPadding() + m_columnWidthSum);
+ } else {
+ result.setLeft(size().width() - m_columnWidthSum - rightPadding());
+ }
}
return result;
}
m_dirtyLayout = true;
}
-void KStandardItemListWidget::sidePaddingChanged(qreal padding)
+void KStandardItemListWidget::sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth)
{
- Q_UNUSED(padding)
+ Q_UNUSED(leftPaddingWidth)
+ Q_UNUSED(rightPaddingWidth)
m_dirtyLayout = true;
}
const qreal inc = (widgetHeight - widgetIconSize) / 2;
const qreal x = expandedParentsCount * widgetHeight + inc;
const qreal y = inc;
- const qreal xPadding = m_highlightEntireRow ? sidePadding() : 0;
- m_expansionArea = QRectF(xPadding + x, y, widgetIconSize, widgetIconSize);
+ if (layoutDirection() == Qt::LeftToRight) {
+ const qreal leftPaddingWidth = m_highlightEntireRow ? leftPadding() : 0;
+ m_expansionArea = QRectF(leftPaddingWidth + x, y, widgetIconSize, widgetIconSize);
+ return;
+ }
+ const qreal rightPaddingWidth = m_highlightEntireRow ? rightPadding() : 0;
+ m_expansionArea = QRectF(size().width() - rightPaddingWidth - x - widgetIconSize, y, widgetIconSize, widgetIconSize);
return;
}
}
} else {
// Center horizontally and vertically within the icon-area
const TextInfo *textInfo = m_textInfo.value("text");
- if (QApplication::isRightToLeft() && m_layout == CompactLayout) {
- m_pixmapPos.setX(size().width() - padding - (scaledIconSize + m_scaledPixmapSize.width()) / 2.0);
+ if (QApplication::isRightToLeft()) {
+ m_pixmapPos.setX(m_textRect.right() + 2.0 * padding);
} else {
m_pixmapPos.setX(textInfo->pos.x() - 2.0 * padding - (scaledIconSize + m_scaledPixmapSize.width()) / 2.0);
}
// +------+
// | Icon | Name role Additional role 1 Additional role 2
// +------+
+ // Mirror the above for right-to-left languages.
+ const bool isLeftToRight = QApplication::layoutDirection() == Qt::LeftToRight;
m_textRect = QRectF();
const KItemListStyleOption &option = styleOption();
const qreal columnWidthInc = columnPadding(option);
qreal firstColumnInc = iconSize();
if (m_supportsItemExpanding) {
- firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
+ firstColumnInc += isLeftToRight ? (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2
+ : ((size().width() - m_expansionArea.left()) + (size().width() - m_expansionArea.right()) + widgetHeight) / 2;
} else {
- firstColumnInc += option.padding + sidePadding();
+ firstColumnInc += option.padding + (isLeftToRight ? leftPadding() : rightPadding());
}
qreal x = firstColumnInc;
const bool isTextRole = (role == "text");
if (isTextRole) {
text = escapeString(text);
- availableTextWidth -= firstColumnInc - sidePadding();
+ availableTextWidth -= firstColumnInc - (isLeftToRight ? leftPadding() : rightPadding());
}
if (requiredWidth > availableTextWidth) {
TextInfo *textInfo = m_textInfo.value(role);
textInfo->staticText.setText(text);
- textInfo->pos = QPointF(x + columnWidthInc / 2, y);
+ textInfo->pos = QPointF(isLeftToRight ? (x + columnWidthInc / 2) : (size().width() - (x + columnWidthInc / 2) - requiredWidth), y);
x += roleWidth;
if (isTextRole) {
- const qreal textWidth = option.extendedSelectionRegion ? size().width() - textInfo->pos.x() : requiredWidth + 2 * option.padding;
- m_textRect = QRectF(textInfo->pos.x() - option.padding, 0, textWidth, size().height());
+ m_textRect = QRectF(textInfo->pos.x() - option.padding, 0, requiredWidth + 2 * option.padding, size().height());
// The column after the name should always be aligned on the same x-position independent
// from the expansion-level shown in the name column
- x -= firstColumnInc - sidePadding();
- } else if (isRoleRightAligned(role)) {
+ x -= firstColumnInc - (isLeftToRight ? leftPadding() : rightPadding());
+ } else if (isRoleRightAligned(role) && isLeftToRight) {
textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc;
}
}
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
- siblingRect.translate(-siblingRect.width(), 0);
+ siblingRect.translate(layoutDirection() == Qt::LeftToRight ? -siblingRect.width() : siblingRect.width(), 0);
}
}
void dataChanged(const QHash<QByteArray, QVariant> ¤t, const QSet<QByteArray> &roles = QSet<QByteArray>()) override;
void visibleRolesChanged(const QList<QByteArray> ¤t, const QList<QByteArray> &previous) override;
void columnWidthChanged(const QByteArray &role, qreal current, qreal previous) override;
- void sidePaddingChanged(qreal width) override;
+ void sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth) override;
void styleOptionChanged(const KItemListStyleOption ¤t, const KItemListStyleOption &previous) override;
void hoveredChanged(bool hovered) override;
void selectedChanged(bool selected) override;
void updateDetailsLayoutTextCache();
void drawPixmap(QPainter *painter, const QPixmap &pixmap);
+ /** Draw the lines and arrows that visualize the expanded state and level of this row. */
void drawSiblingsInformation(QPainter *painter);
QRectF roleEditingRect(const QByteArray &role) const;
/*
* SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2022, 2024 Felix Ernst <felixernst@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QPainter>
#include <QStyleOptionHeader>
+namespace
+{
+/**
+ * @returns a list which has a reversed order of elements compared to @a list.
+ */
+QList<QByteArray> reversed(const QList<QByteArray> list)
+{
+ QList<QByteArray> reversedList;
+ for (auto i = list.rbegin(); i != list.rend(); i++) {
+ reversedList.emplaceBack(*i);
+ }
+ return reversedList;
+};
+
+/**
+ * @returns the index of the column for the name/text of items. This depends on the layoutDirection() and column count of @a itemListHeaderWidget.
+ */
+int nameColumnIndex(const KItemListHeaderWidget *itemListHeaderWidget)
+{
+ if (itemListHeaderWidget->layoutDirection() == Qt::LeftToRight) {
+ return 0;
+ }
+ return itemListHeaderWidget->columns().count() - 1;
+};
+}
+
KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget *parent)
: QGraphicsWidget(parent)
, m_automaticColumnResizing(true)
, m_model(nullptr)
, m_offset(0)
- , m_sidePadding(0)
+ , m_leftPadding(0)
+ , m_rightPadding(0)
, m_columns()
, m_columnWidths()
, m_preferredColumnWidths()
, m_hoveredIndex(-1)
, m_pressedRoleIndex(-1)
- , m_roleOperation(NoRoleOperation)
, m_pressedMousePos()
, m_movingRole()
{
}
}
- m_columns = roles;
+ m_columns = layoutDirection() == Qt::LeftToRight ? roles : reversed(roles);
update();
}
QList<QByteArray> KItemListHeaderWidget::columns() const
{
- return m_columns;
+ return layoutDirection() == Qt::LeftToRight ? m_columns : reversed(m_columns);
}
void KItemListHeaderWidget::setColumnWidth(const QByteArray &role, qreal width)
return m_offset;
}
-void KItemListHeaderWidget::setSidePadding(qreal width)
+void KItemListHeaderWidget::setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth)
{
- if (m_sidePadding != width) {
- m_sidePadding = width;
- Q_EMIT sidePaddingChanged(width);
- update();
+ bool changed = false;
+ if (m_leftPadding != leftPaddingWidth) {
+ m_leftPadding = leftPaddingWidth;
+ changed = true;
}
+
+ if (m_rightPadding != rightPaddingWidth) {
+ m_rightPadding = rightPaddingWidth;
+ changed = true;
+ }
+
+ if (!changed) {
+ return;
+ }
+
+ Q_EMIT sidePaddingChanged(leftPaddingWidth, rightPaddingWidth);
+ update();
+}
+
+qreal KItemListHeaderWidget::leftPadding() const
+{
+ return m_leftPadding;
}
-qreal KItemListHeaderWidget::sidePadding() const
+qreal KItemListHeaderWidget::rightPadding() const
{
- return m_sidePadding;
+ return m_rightPadding;
}
qreal KItemListHeaderWidget::minimumColumnWidth() const
painter->setFont(font());
painter->setPen(palette().text().color());
- qreal x = -m_offset + m_sidePadding;
+ qreal x = -m_offset + m_leftPadding + unusedSpace();
int orderIndex = 0;
for (const QByteArray &role : std::as_const(m_columns)) {
const qreal roleWidth = m_columnWidths.value(role);
}
if (!m_movingRole.pixmap.isNull()) {
- Q_ASSERT(m_roleOperation == MoveRoleOperation);
painter->drawPixmap(m_movingRole.x, 0, m_movingRole.pixmap);
}
}
{
if (event->button() & Qt::LeftButton) {
m_pressedMousePos = event->pos();
- if (isAbovePaddingGrip(m_pressedMousePos, PaddingGrip::Leading)) {
- m_roleOperation = ResizePaddingColumnOperation;
- } else {
+ m_pressedGrip = isAboveResizeGrip(m_pressedMousePos);
+ if (!m_pressedGrip) {
updatePressedRoleIndex(event->pos());
- m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ? ResizeRoleOperation : NoRoleOperation;
}
event->accept();
} else {
{
QGraphicsWidget::mouseReleaseEvent(event);
- if (m_pressedRoleIndex == -1) {
- return;
- }
-
- switch (m_roleOperation) {
- case NoRoleOperation: {
+ if (m_pressedGrip) {
+ // Emitting a column width change removes automatic column resizing, so we do not emit if only the padding is being changed.
+ // Eception: In mouseMoveEvent() we also resize the last column if the right padding is at zero but the user still quickly resizes beyond the screen
+ // boarder. Such a resize "of the right padding" is let through when automatic column resizing was disabled by that resize.
+ if (m_pressedGrip->roleToTheLeft != "leftPadding" && (m_pressedGrip->roleToTheRight != "rightPadding" || !m_automaticColumnResizing)) {
+ const qreal currentWidth = m_columnWidths.value(m_pressedGrip->roleToTheLeft);
+ Q_EMIT columnWidthChangeFinished(m_pressedGrip->roleToTheLeft, currentWidth);
+ }
+ } else if (m_pressedRoleIndex != -1 && m_movingRole.index == -1) {
// Only a click has been done and no moving or resizing has been started
const QByteArray sortRole = m_model->sortRole();
const int sortRoleIndex = m_columns.indexOf(sortRole);
Q_EMIT sortOrderChanged(Qt::AscendingOrder, Qt::DescendingOrder);
}
}
- break;
}
- case ResizeRoleOperation: {
- const QByteArray pressedRole = m_columns[m_pressedRoleIndex];
- const qreal currentWidth = m_columnWidths.value(pressedRole);
- Q_EMIT columnWidthChangeFinished(pressedRole, currentWidth);
- break;
- }
-
- case MoveRoleOperation:
- m_movingRole.pixmap = QPixmap();
- m_movingRole.x = 0;
- m_movingRole.xDec = 0;
- m_movingRole.index = -1;
- break;
-
- default:
- break;
- }
+ m_movingRole.pixmap = QPixmap();
+ m_movingRole.x = 0;
+ m_movingRole.xDec = 0;
+ m_movingRole.index = -1;
+ m_pressedGrip = std::nullopt;
m_pressedRoleIndex = -1;
- m_roleOperation = NoRoleOperation;
update();
QApplication::restoreOverrideCursor();
{
QGraphicsWidget::mouseMoveEvent(event);
- switch (m_roleOperation) {
- case NoRoleOperation:
- if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
- // A role gets dragged by the user. Create a pixmap of the role that will get
- // synchronized on each further mouse-move-event with the mouse-position.
- m_roleOperation = MoveRoleOperation;
- const int roleIndex = roleIndexAt(m_pressedMousePos);
- m_movingRole.index = roleIndex;
- if (roleIndex == 0) {
- // TODO: It should be configurable whether moving the first role is allowed.
- // In the context of Dolphin this is not required, however this should be
- // changed if KItemViews are used in a more generic way.
- QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor));
- } else {
- m_movingRole.pixmap = createRolePixmap(roleIndex);
-
- qreal roleX = -m_offset + m_sidePadding;
- for (int i = 0; i < roleIndex; ++i) {
- const QByteArray role = m_columns[i];
- roleX += m_columnWidths.value(role);
- }
+ if (m_pressedGrip) {
+ if (m_pressedGrip->roleToTheLeft == "leftPadding") {
+ qreal currentWidth = m_leftPadding;
+ currentWidth += event->pos().x() - event->lastPos().x();
+ m_leftPadding = qMax(0.0, currentWidth);
- m_movingRole.xDec = event->pos().x() - roleX;
- m_movingRole.x = roleX;
- update();
- }
+ update();
+ Q_EMIT sidePaddingChanged(m_leftPadding, m_rightPadding);
+ return;
}
- break;
- case ResizeRoleOperation: {
- const QByteArray pressedRole = m_columns[m_pressedRoleIndex];
+ if (m_pressedGrip->roleToTheRight == "rightPadding") {
+ qreal currentWidth = m_rightPadding;
+ currentWidth -= event->pos().x() - event->lastPos().x();
+ m_rightPadding = qMax(0.0, currentWidth);
+
+ update();
+ Q_EMIT sidePaddingChanged(m_leftPadding, m_rightPadding);
+ if (m_rightPadding > 0.0) {
+ return;
+ }
+ // Continue so resizing of the last column beyond the view width is possible.
+ if (currentWidth > -10) {
+ return; // Automatic column resizing is valuable, so we don't want to give it up just for a few pixels of extra width for the rightmost column.
+ }
+ m_automaticColumnResizing = false;
+ }
- qreal previousWidth = m_columnWidths.value(pressedRole);
+ qreal previousWidth = m_columnWidths.value(m_pressedGrip->roleToTheLeft);
qreal currentWidth = previousWidth;
currentWidth += event->pos().x() - event->lastPos().x();
currentWidth = qMax(minimumColumnWidth(), currentWidth);
- m_columnWidths.insert(pressedRole, currentWidth);
- update();
-
- Q_EMIT columnWidthChanged(pressedRole, currentWidth, previousWidth);
- break;
- }
-
- case ResizePaddingColumnOperation: {
- qreal currentWidth = m_sidePadding;
- currentWidth += event->pos().x() - event->lastPos().x();
- currentWidth = qMax(0.0, currentWidth);
-
- m_sidePadding = currentWidth;
-
+ m_columnWidths.insert(m_pressedGrip->roleToTheLeft, currentWidth);
update();
- Q_EMIT sidePaddingChanged(currentWidth);
-
- break;
+ Q_EMIT columnWidthChanged(m_pressedGrip->roleToTheLeft, currentWidth, previousWidth);
+ return;
}
- case MoveRoleOperation: {
+ if (m_movingRole.index != -1) {
// TODO: It should be configurable whether moving the first role is allowed.
// In the context of Dolphin this is not required, however this should be
// changed if KItemViews are used in a more generic way.
- if (m_movingRole.index > 0) {
+ if (m_movingRole.index != nameColumnIndex(this)) {
m_movingRole.x = event->pos().x() - m_movingRole.xDec;
update();
const QByteArray role = m_columns[m_movingRole.index];
const int previousIndex = m_movingRole.index;
m_movingRole.index = targetIndex;
- Q_EMIT columnMoved(role, targetIndex, previousIndex);
+ if (layoutDirection() == Qt::LeftToRight) {
+ Q_EMIT columnMoved(role, targetIndex, previousIndex);
+ } else {
+ Q_EMIT columnMoved(role, m_columns.count() - 1 - targetIndex, m_columns.count() - 1 - previousIndex);
+ }
m_movingRole.xDec = event->pos().x() - roleXPosition(role);
}
}
- break;
+ return;
}
- default:
- break;
+ if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
+ // A role gets dragged by the user. Create a pixmap of the role that will get
+ // synchronized on each further mouse-move-event with the mouse-position.
+ const int roleIndex = roleIndexAt(m_pressedMousePos);
+ m_movingRole.index = roleIndex;
+ if (roleIndex == nameColumnIndex(this)) {
+ // TODO: It should be configurable whether moving the first role is allowed.
+ // In the context of Dolphin this is not required, however this should be
+ // changed if KItemViews are used in a more generic way.
+ QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor));
+ return;
+ }
+
+ m_movingRole.pixmap = createRolePixmap(roleIndex);
+
+ qreal roleX = -m_offset + m_leftPadding + unusedSpace();
+ for (int i = 0; i < roleIndex; ++i) {
+ const QByteArray role = m_columns[i];
+ roleX += m_columnWidths.value(role);
+ }
+
+ m_movingRole.xDec = event->pos().x() - roleX;
+ m_movingRole.x = roleX;
+ update();
}
}
{
QGraphicsItem::mouseDoubleClickEvent(event);
- const int roleIndex = roleIndexAt(event->pos());
- if (roleIndex >= 0 && isAboveRoleGrip(event->pos(), roleIndex)) {
- const QByteArray role = m_columns.at(roleIndex);
+ const std::optional<Grip> doubleClickedGrip = isAboveResizeGrip(event->pos());
+ if (!doubleClickedGrip || doubleClickedGrip->roleToTheLeft.isEmpty()) {
+ return;
+ }
- qreal previousWidth = columnWidth(role);
- setColumnWidth(role, preferredColumnWidth(role));
- qreal currentWidth = columnWidth(role);
+ qreal previousWidth = columnWidth(doubleClickedGrip->roleToTheLeft);
+ setColumnWidth(doubleClickedGrip->roleToTheLeft, preferredColumnWidth(doubleClickedGrip->roleToTheLeft));
+ qreal currentWidth = columnWidth(doubleClickedGrip->roleToTheLeft);
- Q_EMIT columnWidthChanged(role, currentWidth, previousWidth);
- Q_EMIT columnWidthChangeFinished(role, currentWidth);
- }
+ Q_EMIT columnWidthChanged(doubleClickedGrip->roleToTheLeft, currentWidth, previousWidth);
+ Q_EMIT columnWidthChangeFinished(doubleClickedGrip->roleToTheLeft, currentWidth);
}
void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
const QPointF &pos = event->pos();
updateHoveredIndex(pos);
- if ((m_hoveredIndex >= 0 && isAboveRoleGrip(pos, m_hoveredIndex)) || isAbovePaddingGrip(pos, PaddingGrip::Leading)
- || isAbovePaddingGrip(pos, PaddingGrip::Trailing)) {
+ if (isAboveResizeGrip(pos)) {
setCursor(Qt::SplitHCursor);
} else {
unsetCursor();
void KItemListHeaderWidget::paintRole(QPainter *painter, const QByteArray &role, const QRectF &rect, int orderIndex, QWidget *widget) const
{
- const auto direction = widget ? widget->layoutDirection() : qApp->layoutDirection();
-
// The following code is based on the code from QHeaderView::paintSection().
// SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
QStyleOptionHeader option;
- option.direction = direction;
- option.textAlignment = direction == Qt::LeftToRight ? Qt::AlignLeft : Qt::AlignRight;
-
option.section = orderIndex;
option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
if (isEnabled()) {
if (m_columns.count() == 1) {
option.position = QStyleOptionHeader::Middle;
paintPadding(0, QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
- paintPadding(1, QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
+ paintPadding(1, QRectF(rect.right(), 0.0, size().width() - rect.right(), rect.height()), QStyleOptionHeader::End);
} else if (orderIndex == 0) {
// Paint the header for the first column; check if there is some empty space to the left which needs to be filled.
if (rect.left() > 0) {
// Paint the header for the last column; check if there is some empty space to the right which needs to be filled.
if (rect.right() < size().width()) {
option.position = QStyleOptionHeader::Middle;
- paintPadding(m_columns.count(), QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
+ paintPadding(m_columns.count(), QRectF(rect.right(), 0.0, size().width() - rect.right(), rect.height()), QStyleOptionHeader::End);
} else {
option.position = QStyleOptionHeader::End;
}
void KItemListHeaderWidget::updateHoveredIndex(const QPointF &pos)
{
- const int hoverIndex = roleIndexAt(pos);
+ const int hoverIndex = isAboveResizeGrip(pos) ? -1 : roleIndexAt(pos);
if (m_hoveredIndex != hoverIndex) {
if (m_hoveredIndex != -1) {
int KItemListHeaderWidget::roleIndexAt(const QPointF &pos) const
{
- int index = -1;
+ qreal x = -m_offset + m_leftPadding + unusedSpace();
+ if (pos.x() < x) {
+ return -1;
+ }
- qreal x = -m_offset + m_sidePadding;
+ int index = -1;
for (const QByteArray &role : std::as_const(m_columns)) {
++index;
x += m_columnWidths.value(role);
if (pos.x() <= x) {
- break;
+ return index;
}
}
- return index;
+ return -1;
}
-bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF &pos, int roleIndex) const
+std::optional<const KItemListHeaderWidget::Grip> KItemListHeaderWidget::isAboveResizeGrip(const QPointF &position) const
{
- qreal x = -m_offset + m_sidePadding;
- for (int i = 0; i <= roleIndex; ++i) {
- const QByteArray role = m_columns[i];
- x += m_columnWidths.value(role);
- }
-
- const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
- return pos.x() >= (x - grip) && pos.x() <= x;
-}
+ qreal x = -m_offset + m_leftPadding + unusedSpace();
+ const int gripWidthTolerance = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
-bool KItemListHeaderWidget::isAbovePaddingGrip(const QPointF &pos, PaddingGrip paddingGrip) const
-{
- const qreal lx = -m_offset + m_sidePadding;
- const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+ if (x - gripWidthTolerance < position.x() && position.x() < x + gripWidthTolerance) {
+ return std::optional{Grip{"leftPadding", m_columns[0]}};
+ }
- switch (paddingGrip) {
- case Leading:
- return pos.x() >= (lx - grip) && pos.x() <= lx;
- case Trailing: {
- qreal rx = lx;
- for (const QByteArray &role : std::as_const(m_columns)) {
- rx += m_columnWidths.value(role);
+ for (int i = 0; i < m_columns.count(); ++i) {
+ const QByteArray role = m_columns[i];
+ x += m_columnWidths.value(role);
+ if (x - gripWidthTolerance < position.x() && position.x() < x + gripWidthTolerance) {
+ if (i + 1 < m_columns.count()) {
+ return std::optional{Grip{m_columns[i], m_columns[i + 1]}};
+ }
+ return std::optional{Grip{m_columns[i], "rightPadding"}};
}
- return pos.x() >= (rx - grip) && pos.x() <= rx;
- }
- default:
- return false;
}
+ return std::nullopt;
}
QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
const int movingRight = movingLeft + movingWidth - 1;
int targetIndex = 0;
- qreal targetLeft = -m_offset + m_sidePadding;
+ qreal targetLeft = -m_offset + m_leftPadding + unusedSpace();
while (targetIndex < m_columns.count()) {
const QByteArray role = m_columns[targetIndex];
const qreal targetWidth = m_columnWidths.value(role);
qreal KItemListHeaderWidget::roleXPosition(const QByteArray &role) const
{
- qreal x = -m_offset + m_sidePadding;
+ qreal x = -m_offset + m_leftPadding + unusedSpace();
for (const QByteArray &visibleRole : std::as_const(m_columns)) {
if (visibleRole == role) {
return x;
return -1;
}
+qreal KItemListHeaderWidget::unusedSpace() const
+{
+ if (layoutDirection() == Qt::LeftToRight) {
+ return 0;
+ }
+ int unusedSpace = size().width() - m_leftPadding - m_rightPadding;
+ for (int i = 0; i < m_columns.count(); ++i) {
+ const QByteArray role = m_columns[i];
+ unusedSpace -= m_columnWidths.value(role);
+ }
+ return qMax(unusedSpace, 0);
+}
+
#include "moc_kitemlistheaderwidget.cpp"
/*
* SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2022, 2024 Felix Ernst <felixernst@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
void setOffset(qreal offset);
qreal offset() const;
- void setSidePadding(qreal width);
- qreal sidePadding() const;
+ void setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth);
+ qreal leftPadding() const;
+ qreal rightPadding() const;
qreal minimumColumnWidth() const;
*/
void columnWidthChanged(const QByteArray &role, qreal currentWidth, qreal previousWidth);
- void sidePaddingChanged(qreal width);
+ void sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth);
/**
* Is emitted if the user has released the mouse button after adjusting the
void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
private:
- enum PaddingGrip {
- Leading,
- Trailing,
+ struct Grip {
+ QByteArray roleToTheLeft;
+ QByteArray roleToTheRight;
};
void paintRole(QPainter *painter, const QByteArray &role, const QRectF &rect, int orderIndex, QWidget *widget = nullptr) const;
void updatePressedRoleIndex(const QPointF &pos);
void updateHoveredIndex(const QPointF &pos);
int roleIndexAt(const QPointF &pos) const;
- bool isAboveRoleGrip(const QPointF &pos, int roleIndex) const;
- bool isAbovePaddingGrip(const QPointF &pos, PaddingGrip paddingGrip) const;
+
+ /**
+ * @returns std::nullopt if none of the resize grips is below @p position.
+ * Otherwise returns a Grip defined by the two roles on each side of @p position.
+ * @note If the Grip is between a padding and a role, a class-specific "leftPadding" or "rightPadding" role is used.
+ */
+ std::optional<const Grip> isAboveResizeGrip(const QPointF &position) const;
/**
* Creates a pixmap of the role with the index \a roleIndex that is shown
*/
qreal roleXPosition(const QByteArray &role) const;
-private:
- enum RoleOperation { NoRoleOperation, ResizeRoleOperation, ResizePaddingColumnOperation, MoveRoleOperation };
+ /**
+ * @returns 0 for left-to-right layoutDirection().
+ * Otherwise returns the space that is left over when space is distributed between padding and role columns.
+ * Used to make the column headers stay above their information columns for right-to-left layout directions.
+ */
+ qreal unusedSpace() const;
+private:
bool m_automaticColumnResizing;
KItemModelBase *m_model;
qreal m_offset;
- qreal m_sidePadding;
+ qreal m_leftPadding;
+ qreal m_rightPadding;
QList<QByteArray> m_columns;
QHash<QByteArray, qreal> m_columnWidths;
QHash<QByteArray, qreal> m_preferredColumnWidths;
int m_hoveredIndex;
+ std::optional<Grip> m_pressedGrip;
int m_pressedRoleIndex;
- RoleOperation m_roleOperation;
QPointF m_pressedMousePos;
struct MovingRole {
<label>Position of columns</label>
<default>0,1,2,3,4,5,6,7,8</default>
</entry>
- <entry name="SidePadding" type="UInt">
- <label>Side Padding</label>
+ <entry name="LeftPadding" type="UInt">
+ <label>Left side padding</label>
+ <default>20</default>
+ </entry>
+ <entry name="RightPadding" type="UInt">
+ <label>Right side padding</label>
<default>20</default>
</entry>
<entry name="HighlightEntireRow" type="Bool">
#Configuration update for Dolphin
-Version=5
+Version=6
#Rename LeadingPadding to SidePadding
Id=rename-leading-padding
Group=DetailsMode
Key=LeadingPadding,SidePadding
+#Rename SidePadding to LeftPadding
+Id=rename-side-padding
+File=dolphinrc
+Group=DetailsMode
+Key=SidePadding,LeftPadding
+
#Rename Move content-display from detailsMode
Id=move-content-display
File=dolphinrc
// So here the default padding is enabled when the full row highlight is enabled.
if (m_entireRow->isChecked() && !detailsModeSettings->highlightEntireRow()) {
const bool usedDefaults = detailsModeSettings->useDefaults(true);
- const uint defaultSidePadding = detailsModeSettings->sidePadding();
+ const uint defaultLeftPadding = detailsModeSettings->leftPadding();
+ const uint defaultRightPadding = detailsModeSettings->rightPadding();
detailsModeSettings->useDefaults(usedDefaults);
- if (detailsModeSettings->sidePadding() < defaultSidePadding) {
- detailsModeSettings->setSidePadding(defaultSidePadding);
+ if (detailsModeSettings->leftPadding() < defaultLeftPadding) {
+ detailsModeSettings->setLeftPadding(defaultLeftPadding);
+ }
+ if (detailsModeSettings->rightPadding() < defaultRightPadding) {
+ detailsModeSettings->setRightPadding(defaultRightPadding);
}
} else if (!m_entireRow->isChecked() && detailsModeSettings->highlightEntireRow()) {
// The full row click target is disabled so now most of the view area can be used to interact
// with the view background. Having an extra side padding has no usability benefit in this case.
- detailsModeSettings->setSidePadding(0);
+ detailsModeSettings->setLeftPadding(0);
+ detailsModeSettings->setRightPadding(0);
}
detailsModeSettings->setHighlightEntireRow(m_entireRow->isChecked());
detailsModeSettings->setExpandableFolders(m_expandableFolders->isChecked());
QAction *toggleSidePaddingAction = menu->addAction(i18nc("@action:inmenu", "Side Padding"));
toggleSidePaddingAction->setCheckable(true);
- toggleSidePaddingAction->setChecked(view->header()->sidePadding() > 0);
+ toggleSidePaddingAction->setChecked(layoutDirection() == Qt::LeftToRight ? view->header()->leftPadding() > 0 : view->header()->rightPadding() > 0);
QAction *autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths"));
autoAdjustWidthsAction->setCheckable(true);
props.setHeaderColumnWidths(columnWidths);
header->setAutomaticColumnResizing(false);
} else if (action == toggleSidePaddingAction) {
- header->setSidePadding(toggleSidePaddingAction->isChecked() ? 20 : 0);
+ if (toggleSidePaddingAction->isChecked()) {
+ header->setSidePadding(20, 20);
+ } else {
+ header->setSidePadding(0, 0);
+ }
} else {
// Show or hide the selected role
const QByteArray selectedRole = action->data().toByteArray();
props.setHeaderColumnWidths(columnWidths);
}
-void DolphinView::slotSidePaddingWidthChanged(qreal width)
+void DolphinView::slotSidePaddingWidthChanged(qreal leftPaddingWidth, qreal rightPaddingWidth)
{
ViewProperties props(viewPropertiesUrl());
- DetailsModeSettings::setSidePadding(int(width));
+ DetailsModeSettings::setLeftPadding(int(leftPaddingWidth));
+ DetailsModeSettings::setRightPadding(int(rightPaddingWidth));
m_view->writeSettings();
}
} else {
header->setAutomaticColumnResizing(true);
}
- header->setSidePadding(DetailsModeSettings::sidePadding());
+ header->setSidePadding(DetailsModeSettings::leftPadding(), DetailsModeSettings::rightPadding());
}
m_view->endTransaction();
void slotViewContextMenuRequested(const QPointF &pos);
void slotHeaderContextMenuRequested(const QPointF &pos);
void slotHeaderColumnWidthChangeFinished(const QByteArray &role, qreal current);
- void slotSidePaddingWidthChanged(qreal width);
+ void slotSidePaddingWidthChanged(qreal leftPaddingWidth, qreal rightPaddingWidth);
void slotItemHovered(int index);
void slotItemUnhovered(int index);
void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent *event);