[office/kmymoney/5.2] kmymoney/views: Absorb manual column resizes on the side of the dragged separator

Thomas Baumgart <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 54f8ff1362c081e1e9374ce9c2eb9bfd1022ff5d by Thomas Baumgart, on behalf of Simone Iori.
Committed on 28/07/2026 at 06:08.
Pushed by tbaumgart into branch '5.2'.

Absorb manual column resizes on the side of the dragged separator

LedgerView::adjustDetailColumn() keeps the columns filling the
viewport exactly by handing the whole difference to the detail column,
and it runs after every section resize — including the ones caused by
the user dragging a separator. Two consequences:

* the detail column cannot be widened with its right separator, since
  the correction takes the added width off it again;
* dragging a separator to the right of the detail column moves the
  detail column's edge instead of the separator, because the correction
  lands on the other side of the drag.

Separators to the left of the detail column behave correctly already,
which is what makes the current behavior feel inconsistent.

With this change, while the user drags a separator the columns to its
right absorb the difference, cascading to the next one when a column
reaches its minimum width; the detail column remains the absorber in
every other case, such as the view being resized. The separator now
stays where it was dropped, and the columns keep filling the viewport
exactly as before, so no horizontal scrollbar appears.

Two notes for review:

* When several columns have to give way, the width is taken from the
  nearest one first until it reaches its minimum, rather than
  distributed proportionally over all of them. Both are defensible;
  this one felt the most natural in use. See the discussion on the
  bug report.
* SplitView carries a copy of the same logic with the memo column as
  its absorber and has the same problem. I left it out of this change
  on purpose so the approach can be settled on one focused diff first;
  extending it afterwards is straightforward.

BUG: 510377
FIXED-IN: 5.2.3
(cherry picked from commit 3d4134d4dc55c085bb3e5cc3efbeaf06c81340b5)

M  +59   -4    kmymoney/views/ledgerview.cpp

https://invent.kde.org/office/kmymoney/-/commit/54f8ff1362c081e1e9374ce9c2eb9bfd1022ff5d

diff --git a/kmymoney/views/ledgerview.cpp b/kmymoney/views/ledgerview.cpp
index 185f2de03..b92c09b69 100644
--- a/kmymoney/views/ledgerview.cpp
+++ b/kmymoney/views/ledgerview.cpp
@@ -91,6 +91,7 @@ public:
         , infoMessage(new KMessageWidget(q))
         , editor(nullptr)
         , adjustableColumn(JournalModel::Column::Detail)
+        , userResizedColumn(-1)
         , adjustingColumn(false)
         , showValuesInverted(false)
         , newTransactionPresent(false)
@@ -514,6 +515,38 @@ public:
         }
     }
 
+    /**
+     * Returns the columns that take up the difference between the width of
+     * all columns and the width of the viewport, in the order in which they
+     * shall be used.
+     *
+     * When the user drags a section separator, the columns to the right of
+     * the modified one absorb the change, so that the separator stays where
+     * it was dropped. In all other cases (e.g. the view itself is resized)
+     * the detail column takes the difference, as it always did.
+     */
+    QVector<int> absorberColumns() const
+    {
+        QVector<int> columns;
+        const auto header = q->horizontalHeader();
+
+        if (userResizedColumn >= 0) {
+            for (int visualIndex = header->visualIndex(userResizedColumn) + 1; visualIndex < header->count(); ++visualIndex) {
+                const auto logicalIndex = header->logicalIndex(visualIndex);
+                if (!header->isSectionHidden(logicalIndex) && (header->sectionResizeMode(logicalIndex) == QHeaderView::Interactive)) {
+                    columns.append(logicalIndex);
+                }
+            }
+        }
+
+        // the detail column remains the last resort, e.g. when the columns
+        // to the right of the modified one cannot shrink any further
+        if (!columns.contains(adjustableColumn) && !header->isSectionHidden(adjustableColumn)) {
+            columns.append(adjustableColumn);
+        }
+        return columns;
+    }
+
     void resetMaxLineCache()
     {
         auto m = q->LedgerView::model();
@@ -533,6 +566,7 @@ public:
     TransactionEditorBase* editor;
     QHash<const QAbstractItemModel*, QStyledItemDelegate*> delegates;
     int adjustableColumn;
+    int userResizedColumn;
     bool adjustingColumn;
     bool showValuesInverted;
     bool newTransactionPresent;
@@ -574,6 +608,11 @@ LedgerView::LedgerView(QWidget* parent)
     // See LedgerView::resizeSection().
     connect(horizontalHeader(), &QHeaderView::sectionResized, this, [&](int logicalIndex, int oldSize, int newSize) {
         Q_EMIT sectionResized(this, d->columnSelector->configGroupName(), logicalIndex, oldSize, newSize);
+        // when the user drags a separator, the columns to the right of the
+        // modified one absorb the difference. See Private::absorberColumns().
+        const auto draggingSeparator =
+            (QApplication::mouseButtons() & Qt::LeftButton) && (horizontalHeader()->cursor().shape() == Qt::SplitHCursor);
+        d->userResizedColumn = draggingSeparator ? logicalIndex : -1;
         QMetaObject::invokeMethod(this, "adjustDetailColumn", Qt::QueuedConnection, Q_ARG(int, viewport()->width()), Q_ARG(bool, false));
     });
 
@@ -1351,16 +1390,32 @@ void LedgerView::adjustDetailColumn(int newViewportWidth, bool informOtherViews)
         }
         totalColumnWidth += header->sectionSize(i);
     }
-    const int delta = newViewportWidth - totalColumnWidth;
-    const int newWidth = header->sectionSize(d->adjustableColumn) + delta;
-    if (newWidth > 10) {
+    int delta = newViewportWidth - totalColumnWidth;
+    if (delta != 0) {
+        const int minimumWidth = qMax(header->minimumSectionSize(), 10);
         QSignalBlocker blocker(header);
         if (informOtherViews)
             blocker.unblock();
-        header->resizeSection(d->adjustableColumn, newWidth);
+
+        // hand the difference to the absorber columns one after the other
+        // until it is used up or none of them can take any more of it
+        const auto columns = d->absorberColumns();
+        for (const int column : columns) {
+            if (delta == 0) {
+                break;
+            }
+            const int currentWidth = header->sectionSize(column);
+            const int newWidth = qMax(minimumWidth, currentWidth + delta);
+            if (newWidth == currentWidth) {
+                continue;
+            }
+            header->resizeSection(column, newWidth);
+            delta -= newWidth - currentWidth;
+        }
     }
 
     // remember that we're done this time
+    d->userResizedColumn = -1;
     d->adjustingColumn = false;
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.