]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Mirror details view mode for right-to-left languages
authorFelix Ernst <felixernst@kde.org>
Sun, 29 Dec 2024 11:42:22 +0000 (11:42 +0000)
committerFelix Ernst <felixernst@kde.org>
Sun, 29 Dec 2024 11:42:22 +0000 (11:42 +0000)
This commit implements mirroring of the details view mode for right-to-
left languages. This is the last of the Dolphin view modes which did
not adapt to right-to-left languages correctly.

Implementation-wise this is mostly about adapting the math so all the
information is placed correctly no matter the view mode or layout
direction. While most of the view actually changes the painting code
for right-to-left languages, for the column header I decided to keep
the logic left-to-right and instead reverse the order of the role
columns.

To implement this mirroring I needed to rework quite a bit of logic, so
I used the opportunity to fix some bugs/behaviur quirks:
- Left and right padding is now saved and restored separately instead
  of only saving the left padding
- Changing the right padding no longer disables "automatic column
  resizing".
- The grip handles for column resizing can now be grabbed when near the
  grip handle instead of only allowing grabbing when slightly to the
  left of the grip.
- Role column headers now only show a hover highlight effect when the
  mouse cursor is actually above that role and not above the grip
  handle or the padding.
- There is now a soft-boarder when shrinking the right padding so
  shrinking the padding "below zero width" will no longer immediately
  clear automatic resize behaviour. So now it is possible to simply
  remove the right padding by resizing it to zero width.

BUG: 449211
BUG: 495942

# Acknowledgement

This work is part of a my project funded through the NGI0 Entrust Fund,
a fund established by NLnet with financial support from the European
Commission's Next Generation Internet programme, under the aegis of DG
Communications Networks, Content and Technology.

15 files changed:
src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/kitemlistheader.cpp
src/kitemviews/kitemlistheader.h
src/kitemviews/kitemlistview.cpp
src/kitemviews/kitemlistwidget.cpp
src/kitemviews/kitemlistwidget.h
src/kitemviews/kstandarditemlistwidget.cpp
src/kitemviews/kstandarditemlistwidget.h
src/kitemviews/private/kitemlistheaderwidget.cpp
src/kitemviews/private/kitemlistheaderwidget.h
src/settings/dolphin_detailsmodesettings.kcfg
src/settings/dolphin_detailsmodesettings.upd
src/settings/viewmodes/viewsettingstab.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h

index 2ae4a1f25291c7e68b8f1e259d9bec5b7ead53f1..5a396de61fd48f83be3144a771a36eea19fce194 100644 (file)
@@ -243,6 +243,20 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
 
     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)) {
@@ -299,34 +313,6 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
         }
     }
 
-    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) {
index bb3153794e2f6a7c18fbea1c76ef26b806018000..97d0cdfbf3f5e908af417a4dd7a632dfa7388872 100644 (file)
@@ -61,10 +61,10 @@ qreal KItemListHeader::preferredColumnWidth(const QByteArray &role) const
     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();
         }
@@ -72,9 +72,14 @@ void KItemListHeader::setSidePadding(qreal width)
     }
 }
 
-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)
index 04519f12c700b96dad3dca54e0255ba2c09dee2c..d84832dab63a608bf9b7631afa2e68f947b2967b 100644 (file)
@@ -59,14 +59,15 @@ public:
     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
index 42c5e25c232fb607fd6d6fa0d2fc68a1ae035971..369415f1ba73c4552f93397af205d03379e1f2ec 100644 (file)
@@ -385,7 +385,7 @@ void KItemListView::setGeometry(const QRectF &rect)
         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);
         }
@@ -2389,7 +2389,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
 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);
 
@@ -2406,7 +2406,7 @@ void KItemListView::updateWidgetColumnWidths(KItemListWidget *widget)
     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)
@@ -2484,9 +2484,9 @@ void KItemListView::applyAutomaticColumnWidths()
     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
index 4c9f259861104b141f6a624b2bda788767ec08f2..dac5ac296c4fa6f59eed834d6231145f594b2219 100644 (file)
@@ -40,7 +40,8 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI
     , m_data()
     , m_visibleRoles()
     , m_columnWidths()
-    , m_sidePadding(0)
+    , m_leftPadding(0)
+    , m_rightPadding(0)
     , m_styleOption()
     , m_siblingsInfo()
     , m_hoverOpacity(0)
@@ -183,18 +184,35 @@ qreal KItemListWidget::columnWidth(const QByteArray &role) const
     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)
@@ -462,9 +480,10 @@ void KItemListWidget::columnWidthChanged(const QByteArray &role, qreal current,
     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 &current, const KItemListStyleOption &previous)
index fdfe5e78a2153093cc2edaab72dc21d9bbddf1e2..e254292c09bc39519747f9366b8ce34b570cbf5d 100644 (file)
@@ -81,8 +81,9 @@ public:
     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;
@@ -202,7 +203,7 @@ protected:
     virtual void dataChanged(const QHash<QByteArray, QVariant> &current, const QSet<QByteArray> &roles = QSet<QByteArray>());
     virtual void visibleRolesChanged(const QList<QByteArray> &current, 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 &current, const KItemListStyleOption &previous);
     virtual void currentChanged(bool current);
     virtual void selectedChanged(bool selected);
@@ -263,7 +264,8 @@ private:
     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;
 
index bc7023e126ff0724984c70682ac88da858c13ea8..05628d3913030895f7f2592d99e20e17329338fc 100644 (file)
@@ -409,15 +409,26 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic
     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);
+            }
         }
     }
 
@@ -524,7 +535,11 @@ QRectF KStandardItemListWidget::selectionRect() const
         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;
     }
@@ -786,9 +801,10 @@ void KStandardItemListWidget::columnWidthChanged(const QByteArray &role, qreal c
     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;
 }
 
@@ -1012,8 +1028,13 @@ void KStandardItemListWidget::updateExpansionArea()
             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;
         }
     }
@@ -1155,8 +1176,8 @@ void KStandardItemListWidget::updatePixmapCache()
     } 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);
         }
@@ -1464,6 +1485,8 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
     // +------+
     // | 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();
@@ -1475,9 +1498,10 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
     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;
@@ -1494,7 +1518,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
         const bool isTextRole = (role == "text");
         if (isTextRole) {
             text = escapeString(text);
-            availableTextWidth -= firstColumnInc - sidePadding();
+            availableTextWidth -= firstColumnInc - (isLeftToRight ? leftPadding() : rightPadding());
         }
 
         if (requiredWidth > availableTextWidth) {
@@ -1504,17 +1528,16 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
 
         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;
         }
     }
@@ -1594,7 +1617,7 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter *painter)
 
         style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
 
-        siblingRect.translate(-siblingRect.width(), 0);
+        siblingRect.translate(layoutDirection() == Qt::LeftToRight ? -siblingRect.width() : siblingRect.width(), 0);
     }
 }
 
index 588ec3548e89aea48e70f3aeabda509962a2553b..7ff97a126c6332e3df9fe87025ab4f9aa12ef472 100644 (file)
@@ -176,7 +176,7 @@ protected:
     void dataChanged(const QHash<QByteArray, QVariant> &current, const QSet<QByteArray> &roles = QSet<QByteArray>()) override;
     void visibleRolesChanged(const QList<QByteArray> &current, 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 &current, const KItemListStyleOption &previous) override;
     void hoveredChanged(bool hovered) override;
     void selectedChanged(bool selected) override;
@@ -213,6 +213,7 @@ private:
     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;
index 02a4f939d13b2e26f0b3e43ed301799a6a57e739..3dc82ad6b0c32744c140d9b039338f95565ad3c1 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * 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()
 {
@@ -82,13 +109,13 @@ void KItemListHeaderWidget::setColumns(const QList<QByteArray> &roles)
         }
     }
 
-    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)
@@ -132,18 +159,35 @@ qreal KItemListHeaderWidget::offset() const
     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
@@ -165,7 +209,7 @@ void KItemListHeaderWidget::paint(QPainter *painter, const QStyleOptionGraphicsI
     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);
@@ -176,7 +220,6 @@ void KItemListHeaderWidget::paint(QPainter *painter, const QStyleOptionGraphicsI
     }
 
     if (!m_movingRole.pixmap.isNull()) {
-        Q_ASSERT(m_roleOperation == MoveRoleOperation);
         painter->drawPixmap(m_movingRole.x, 0, m_movingRole.pixmap);
     }
 }
@@ -185,11 +228,9 @@ void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
 {
     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 {
@@ -201,12 +242,15 @@ void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
 {
     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);
@@ -229,29 +273,15 @@ void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
                 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();
@@ -261,69 +291,51 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
 {
     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();
 
@@ -332,16 +344,42 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
                 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();
     }
 }
 
@@ -349,17 +387,17 @@ void KItemListHeaderWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *even
 {
     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)
@@ -384,8 +422,7 @@ void KItemListHeaderWidget::hoverMoveEvent(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();
@@ -408,14 +445,9 @@ void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current, Qt::Sort
 
 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()) {
@@ -453,7 +485,7 @@ void KItemListHeaderWidget::paintRole(QPainter *painter, const QByteArray &role,
     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) {
@@ -466,7 +498,7 @@ void KItemListHeaderWidget::paintRole(QPainter *painter, const QByteArray &role,
         // 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;
         }
@@ -488,7 +520,7 @@ void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF &pos)
 
 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) {
@@ -504,50 +536,43 @@ void KItemListHeaderWidget::updateHoveredIndex(const QPointF &pos)
 
 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
@@ -581,7 +606,7 @@ int KItemListHeaderWidget::targetOfMovingRole() 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);
@@ -603,7 +628,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
 
 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;
@@ -615,4 +640,17 @@ qreal KItemListHeaderWidget::roleXPosition(const QByteArray &role) const
     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"
index a522fa3a2806bda5640cf72d6eb91df1cd2e54cc..42dfda503ac83b00e59f44b30e89ebe5697de24b 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * 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
  */
@@ -50,8 +51,9 @@ public:
     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;
 
@@ -64,7 +66,7 @@ Q_SIGNALS:
      */
     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
@@ -110,9 +112,9 @@ private Q_SLOTS:
     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;
@@ -120,8 +122,13 @@ private:
     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
@@ -140,20 +147,26 @@ private:
      */
     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 {
index 98fe0efff98a29e21a40f19aed86f902f499df36..52d85fa122ceaf6e195823e1481b91d7e4b069d5 100644 (file)
             <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">
index da8f4b9cd8c72eed80112b086d38842d41ffd3ad..f9def96b6927263a8093c9eb1f7ff9cdf0ccf243 100644 (file)
@@ -1,5 +1,5 @@
 #Configuration update for Dolphin
-Version=5
+Version=6
 
 #Rename LeadingPadding to SidePadding
 Id=rename-leading-padding
@@ -7,6 +7,12 @@ File=dolphinrc
 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
index 5aca58ba1e7f9fc29559fe6f16746e6b2d7a9a00..fc9e94131889ca0fbb9b49530309b84b949ec837 100644 (file)
@@ -154,15 +154,20 @@ void ViewSettingsTab::applySettings()
         // 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());
index 8302620e424480bcaf4e768d4140f9d9d7159dff..a18f5376986d79f9d456ce73284256b67ddac12e 100644 (file)
@@ -1239,7 +1239,7 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF &pos)
 
     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);
@@ -1272,7 +1272,11 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF &pos)
             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();
@@ -1325,10 +1329,11 @@ void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray &role, qr
     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();
 }
 
@@ -2184,7 +2189,7 @@ void DolphinView::applyViewProperties(const ViewProperties &props)
         } else {
             header->setAutomaticColumnResizing(true);
         }
-        header->setSidePadding(DetailsModeSettings::sidePadding());
+        header->setSidePadding(DetailsModeSettings::leftPadding(), DetailsModeSettings::rightPadding());
     }
 
     m_view->endTransaction();
index e78a9ca9d9a16b2022302e64851cdc41d9289005..d1667334e30d39336dd7dd05284f599ff735e409 100644 (file)
@@ -692,7 +692,7 @@ private Q_SLOTS:
     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);