View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0017972 | Scribus | Tables | public | 2026-09-07 11:53 | 2026-09-07 11:53 |
| Reporter | buttar | Assigned To | |||
| Priority | normal | Severity | feature | Reproducibility | N/A |
| Status | new | Resolution | open | ||
| Platform | Linux | OS | Fedora Workstation | OS Version | 44 |
| Product Version | 1.7.4.svn | ||||
| Summary | 0017972: [patch] Tables: Proportional Row/Column Resize | ||||
| Description | (Third in the series, it applies cleanly after the one in 0017970) * Adds a Ctrl+drag proportional resize mode for table row and column dividers. * Resizing one row/column redistributes the inverse size change proportionally across all following rows/columns while keeping the overall table dimension fixed. * Preserves the relative proportions of the following rows/columns during redistribution. * Keeps existing resize behaviour unchanged: -- plain drag → existing resize behaviour; -- Shift+drag → existing move-following behaviour; -- Shift takes priority if used together with Ctrl. * Ctrl on the final row/column boundary falls back to the existing resize behaviour because there is no following geometry to redistribute. * Respects existing minimum-size constraints: -- column minimum widths; -- content-aware minimum row heights. * Supports pressing/releasing Ctrl during an active resize gesture, with the preview updating immediately. * Updates the resize HUD to indicate proportional mode while Ctrl is active. * Commits the proportional resize as a single grouped Undo operation. * Uses the existing table resize gesture infrastructure; no table-model or file-format changes. * Applies on top of TUX1 and TUX2 without modifying their behaviour. | ||||
| Additional Information | tux = tables UX | ||||
| Tags | #please_test | ||||
| Attached Files | tux3-table-proportional-resize-v1.01.patch (15,992 bytes)
Index: scribus/canvasgesture_table.cpp
===================================================================
--- scribus/canvasgesture_table.cpp (revision 27816)
+++ scribus/canvasgesture_table.cpp (working copy)
@@ -23,6 +23,68 @@
#include "canvasgesture_table.h"
+QList<double> TableGesture::proportionalDistribution(const QList<double>& sizes,
+ const QList<double>& minimumSizes, double targetTotal)
+{
+ Q_ASSERT(sizes.size() == minimumSizes.size());
+
+ QList<double> result = sizes;
+ if (sizes.isEmpty())
+ return result;
+
+ QList<int> active;
+ double minimumTotal = 0.0;
+ for (int i = 0; i < sizes.size(); ++i)
+ {
+ active.append(i);
+ minimumTotal += minimumSizes.at(i);
+ }
+
+ double remaining = qMax(targetTotal, minimumTotal);
+ while (!active.isEmpty())
+ {
+ double sizeTotal = 0.0;
+ for (int index : active)
+ sizeTotal += sizes.at(index);
+
+ if (sizeTotal <= 0.0)
+ break;
+
+ const double factor = remaining / sizeTotal;
+ bool clamped = false;
+ for (int i = active.size() - 1; i >= 0; --i)
+ {
+ const int index = active.at(i);
+ if (sizes.at(index) * factor < minimumSizes.at(index))
+ {
+ result[index] = minimumSizes.at(index);
+ remaining -= result.at(index);
+ active.removeAt(i);
+ clamped = true;
+ }
+ }
+
+ if (clamped)
+ continue;
+
+ double assigned = 0.0;
+ for (int i = 0; i < active.size(); ++i)
+ {
+ const int index = active.at(i);
+ if (i == active.size() - 1)
+ result[index] = qMax(minimumSizes.at(index), remaining - assigned);
+ else
+ {
+ result[index] = sizes.at(index) * factor;
+ assigned += result.at(index);
+ }
+ }
+ break;
+ }
+
+ return result;
+}
+
QPointF TableGesture::globalToTableGrid(const QPointF& point, bool snap)
{
FPoint canvasPoint = m_canvas->globalToCanvas(point);
Index: scribus/canvasgesture_table.h
===================================================================
--- scribus/canvasgesture_table.h (revision 27816)
+++ scribus/canvasgesture_table.h (working copy)
@@ -67,6 +67,14 @@
*/
void paintCellSelection(QPainter* p);
+ /**
+ * Returns @a sizes scaled proportionally to sum to @a targetTotal while
+ * respecting the corresponding @a minimumSizes. Values that hit their
+ * minimum are held there and the remaining space is redistributed.
+ */
+ static QList<double> proportionalDistribution(const QList<double>& sizes,
+ const QList<double>& minimumSizes, double targetTotal);
+
private:
/// The currently edited table.
PageItem_Table* m_table;
Index: scribus/canvasgesture_columnresize.cpp
===================================================================
--- scribus/canvasgesture_columnresize.cpp (revision 27816)
+++ scribus/canvasgesture_columnresize.cpp (working copy)
@@ -29,9 +29,9 @@
event->accept();
m_view->stopGesture();
}
- if (event->key() == Qt::Key_Shift)
+ if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control)
{
- // Re-copy column geometires since resizing behavior will change.
+ // Re-copy column geometries since resizing behavior will change.
m_columnWidths = table()->columnWidths();
m_columnPositions = table()->columnPositions();
}
@@ -39,9 +39,9 @@
void ColumnResize::keyReleaseEvent(QKeyEvent* event)
{
- if (event->key() == Qt::Key_Shift)
+ if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control)
{
- // Re-copy column geometires since resizing behavior will change.
+ // Re-copy column geometries since resizing behavior will change.
m_columnWidths = table()->columnWidths();
m_columnPositions = table()->columnPositions();
}
@@ -52,24 +52,33 @@
event->accept();
QPointF gridPoint = globalToTableGrid(event->globalPosition());
-
- // Perform the actual resize of the row.
- PageItem_Table::ResizeStrategy strategy;
- if (event->modifiers() & Qt::ShiftModifier)
- strategy = PageItem_Table::MoveFollowing;
- else
- strategy = PageItem_Table::ResizeFollowing;
+ const double requestedWidth = gridPoint.x() - table()->columnPosition(m_column);
+ const bool moveFollowing = event->modifiers() & Qt::ShiftModifier;
+ const bool proportional = !moveFollowing
+ && (event->modifiers() & Qt::ControlModifier)
+ && m_column < table()->columns() - 1;
UndoTransaction activeTransaction;
if (UndoManager::undoEnabled())
activeTransaction = UndoManager::instance()->beginTransaction(table()->getUName(), table()->getUPixmap(), Um::TableColumnWidth, QString(), Um::ITable);
table()->doc()->dontResize = true;
- table()->resizeColumn(m_column, gridPoint.x() - table()->columnPosition(m_column), strategy);
- if (strategy == PageItem_Table::MoveFollowing)
+ if (proportional)
+ {
+ resizeColumnProportionally(requestedWidth);
+ for (int column = m_column; column < table()->columns(); ++column)
+ table()->resizeColumn(column, m_columnWidths.at(column), PageItem_Table::MoveFollowing);
+ }
+ else
{
- table()->adjustTableToFrame();
- table()->adjustFrameToTable();
+ const PageItem_Table::ResizeStrategy strategy = moveFollowing
+ ? PageItem_Table::MoveFollowing : PageItem_Table::ResizeFollowing;
+ table()->resizeColumn(m_column, requestedWidth, strategy);
+ if (strategy == PageItem_Table::MoveFollowing)
+ {
+ table()->adjustTableToFrame();
+ table()->adjustFrameToTable();
+ }
}
table()->doc()->dontResize = false;
table()->adjustFrameToTable();
@@ -90,14 +99,21 @@
double requestedWidth = gridPoint.x() - m_columnPositions.at(m_column);
double actualWidth = 0.0;
+ const bool moveFollowing = event->modifiers() & Qt::ShiftModifier;
+ const bool proportional = !moveFollowing
+ && (event->modifiers() & Qt::ControlModifier)
+ && m_column < table()->columns() - 1;
- if (event->modifiers() & Qt::ShiftModifier)
+ if (moveFollowing)
actualWidth = resizeColumnMoveFollowing(requestedWidth);
+ else if (proportional)
+ actualWidth = resizeColumnProportionally(requestedWidth);
else
actualWidth = resizeColumnResizeFollowing(requestedWidth);
// Display width tooltip.
- m_canvas->displayDoubleHUD(event->globalPosition(), tr("Width"), actualWidth);
+ const QString label = proportional ? tr("Width (Proportional)") : tr("Width");
+ m_canvas->displayDoubleHUD(event->globalPosition(), label, actualWidth);
// Update canvas.
m_canvas->update();
@@ -168,3 +184,47 @@
return newWidth;
}
+
+double ColumnResize::resizeColumnProportionally(double width)
+{
+ // Always calculate from the table's committed geometry so repeated mouse
+ // moves preserve the proportions that existed when the drag began.
+ m_columnWidths = table()->columnWidths();
+ m_columnPositions = table()->columnPositions();
+
+ if (m_column >= table()->columns() - 1)
+ return resizeColumnResizeFollowing(width);
+
+ double rangeWidth = 0.0;
+ for (int column = m_column; column < table()->columns(); ++column)
+ rangeWidth += m_columnWidths.at(column);
+
+ const int followingCount = table()->columns() - m_column - 1;
+ const double followingMinimum = followingCount * PageItem_Table::MinimumColumnWidth;
+ const double maximumWidth = qMax(PageItem_Table::MinimumColumnWidth, rangeWidth - followingMinimum);
+ const double newWidth = qBound(PageItem_Table::MinimumColumnWidth, width, maximumWidth);
+
+ QList<double> followingWidths;
+ QList<double> minimumWidths;
+ for (int column = m_column + 1; column < table()->columns(); ++column)
+ {
+ followingWidths.append(m_columnWidths.at(column));
+ minimumWidths.append(PageItem_Table::MinimumColumnWidth);
+ }
+
+ const QList<double> distributed = proportionalDistribution(
+ followingWidths, minimumWidths, rangeWidth - newWidth);
+ m_columnWidths[m_column] = newWidth;
+ for (int i = 0; i < distributed.size(); ++i)
+ m_columnWidths[m_column + 1 + i] = distributed.at(i);
+
+ double columnPosition = m_columnPositions.at(m_column);
+ for (int column = m_column; column < m_columnPositions.size(); ++column)
+ {
+ m_columnPositions[column] = columnPosition;
+ columnPosition += m_columnWidths.at(column);
+ }
+
+ return newWidth;
+}
+
Index: scribus/canvasgesture_columnresize.h
===================================================================
--- scribus/canvasgesture_columnresize.h (revision 27816)
+++ scribus/canvasgesture_columnresize.h (working copy)
@@ -58,6 +58,12 @@
*/
double resizeColumnResizeFollowing(double width);
+ /**
+ * Resizes the column to @a width and redistributes the remaining width
+ * proportionally over all following columns.
+ */
+ double resizeColumnProportionally(double width);
+
private:
/// Column being resized.
int m_column;
Index: scribus/canvasgesture_rowresize.cpp
===================================================================
--- scribus/canvasgesture_rowresize.cpp (revision 27816)
+++ scribus/canvasgesture_rowresize.cpp (working copy)
@@ -29,9 +29,9 @@
event->accept();
m_view->stopGesture();
}
- if (event->key() == Qt::Key_Shift)
+ if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control)
{
- // Re-copy row geometires since resizing behavior will change.
+ // Re-copy row geometries since resizing behavior will change.
m_rowHeights = table()->rowHeights();
m_rowPositions = table()->rowPositions();
}
@@ -39,9 +39,9 @@
void RowResize::keyReleaseEvent(QKeyEvent* event)
{
- if (event->key() == Qt::Key_Shift)
+ if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control)
{
- // Re-copy row geometires since resizing behavior will change.
+ // Re-copy row geometries since resizing behavior will change.
m_rowHeights = table()->rowHeights();
m_rowPositions = table()->rowPositions();
}
@@ -52,27 +52,35 @@
event->accept();
QPointF gridPoint = globalToTableGrid(event->globalPosition());
-
- // Perform the actual resize of the row.
- PageItem_Table::ResizeStrategy strategy;
- if (event->modifiers() & Qt::ShiftModifier)
- strategy = PageItem_Table::MoveFollowing;
- else
- strategy = PageItem_Table::ResizeFollowing;
+ const double requestedHeight = gridPoint.y() - table()->rowPosition(m_row);
+ const bool moveFollowing = event->modifiers() & Qt::ShiftModifier;
+ const bool proportional = !moveFollowing
+ && (event->modifiers() & Qt::ControlModifier)
+ && m_row < table()->rows() - 1;
UndoTransaction activeTransaction;
if (UndoManager::undoEnabled())
activeTransaction = UndoManager::instance()->beginTransaction(table()->getUName(), table()->getUPixmap(), Um::TableRowHeight, QString(), Um::ITable);
table()->doc()->dontResize = true;
- double requestedHeight = gridPoint.y() - table()->rowPosition(m_row);
- requestedHeight = qMax(requestedHeight, m_minRowHeight);
- table()->resizeRow(m_row, requestedHeight, strategy);
-
- if (strategy == PageItem_Table::MoveFollowing)
+ if (proportional)
+ {
+ resizeRowProportionally(requestedHeight);
+ for (int row = m_row; row < table()->rows(); ++row)
+ table()->resizeRow(row, m_rowHeights.at(row), PageItem_Table::MoveFollowing);
+ }
+ else
{
- table()->adjustTableToFrame();
- table()->adjustFrameToTable();
+ const PageItem_Table::ResizeStrategy strategy = moveFollowing
+ ? PageItem_Table::MoveFollowing : PageItem_Table::ResizeFollowing;
+ const double boundedHeight = qMax(requestedHeight, m_minRowHeight);
+ table()->resizeRow(m_row, boundedHeight, strategy);
+
+ if (strategy == PageItem_Table::MoveFollowing)
+ {
+ table()->adjustTableToFrame();
+ table()->adjustFrameToTable();
+ }
}
table()->doc()->dontResize = false;
table()->adjustFrameToTable();
@@ -93,14 +101,21 @@
double requestedHeight = gridPoint.y() - m_rowPositions.at(m_row);
double actualHeight = 0.0;
+ const bool moveFollowing = event->modifiers() & Qt::ShiftModifier;
+ const bool proportional = !moveFollowing
+ && (event->modifiers() & Qt::ControlModifier)
+ && m_row < table()->rows() - 1;
- if (event->modifiers() & Qt::ShiftModifier)
+ if (moveFollowing)
actualHeight = resizeRowMoveFollowing(requestedHeight);
+ else if (proportional)
+ actualHeight = resizeRowProportionally(requestedHeight);
else
actualHeight = resizeRowResizeFollowing(requestedHeight);
// Display height tooltip.
- m_canvas->displayDoubleHUD(event->globalPosition(), tr("Height"), actualHeight);
+ const QString label = proportional ? tr("Height (Proportional)") : tr("Height");
+ m_canvas->displayDoubleHUD(event->globalPosition(), label, actualHeight);
// Update canvas.
m_canvas->update();
@@ -140,6 +155,28 @@
else
m_nextMinRowHeight = PageItem_Table::MinimumRowHeight;
+ // Cache feasible content-aware floors for proportional redistribution.
+ // If a row is already smaller than its natural content height, keep its
+ // current height as the floor so entering proportional mode does not make
+ // the table jump before the pointer moves.
+ m_proportionalMinRowHeights.clear();
+ m_proportionalMinRowHeights.reserve(table->rows());
+ for (int index = 0; index < table->rows(); ++index)
+ {
+ if (index < row)
+ {
+ m_proportionalMinRowHeights.append(PageItem_Table::MinimumRowHeight);
+ continue;
+ }
+
+ bool rowHasContent = false;
+ const double rowNatural = table->naturalRowHeight(index, &rowHasContent);
+ const double contentFloor = rowHasContent
+ ? qMax(PageItem_Table::MinimumRowHeight, rowNatural)
+ : PageItem_Table::MinimumRowHeight;
+ m_proportionalMinRowHeights.append(qMin(table->rowHeight(index), contentFloor));
+ }
+
// Make copies of the row geometries to be used during resize.
m_rowHeights = table->rowHeights();
m_rowPositions = table->rowPositions();
@@ -189,3 +226,50 @@
return newHeight;
}
+
+double RowResize::resizeRowProportionally(double height)
+{
+ // Always calculate from the table's committed geometry so repeated mouse
+ // moves preserve the proportions that existed when the drag began.
+ m_rowHeights = table()->rowHeights();
+ m_rowPositions = table()->rowPositions();
+
+ if (m_row >= table()->rows() - 1)
+ return resizeRowResizeFollowing(height);
+
+ double rangeHeight = 0.0;
+ for (int row = m_row; row < table()->rows(); ++row)
+ rangeHeight += m_rowHeights.at(row);
+
+ double followingMinimum = 0.0;
+ for (int row = m_row + 1; row < table()->rows(); ++row)
+ followingMinimum += m_proportionalMinRowHeights.at(row);
+
+ const double minimumHeight = m_proportionalMinRowHeights.at(m_row);
+ const double maximumHeight = qMax(minimumHeight, rangeHeight - followingMinimum);
+ const double newHeight = qBound(minimumHeight, height, maximumHeight);
+
+ QList<double> followingHeights;
+ QList<double> minimumHeights;
+ for (int row = m_row + 1; row < table()->rows(); ++row)
+ {
+ followingHeights.append(m_rowHeights.at(row));
+ minimumHeights.append(m_proportionalMinRowHeights.at(row));
+ }
+
+ const QList<double> distributed = proportionalDistribution(
+ followingHeights, minimumHeights, rangeHeight - newHeight);
+ m_rowHeights[m_row] = newHeight;
+ for (int i = 0; i < distributed.size(); ++i)
+ m_rowHeights[m_row + 1 + i] = distributed.at(i);
+
+ double rowPosition = m_rowPositions.at(m_row);
+ for (int row = m_row; row < m_rowPositions.size(); ++row)
+ {
+ m_rowPositions[row] = rowPosition;
+ rowPosition += m_rowHeights.at(row);
+ }
+
+ return newHeight;
+}
+
Index: scribus/canvasgesture_rowresize.h
===================================================================
--- scribus/canvasgesture_rowresize.h (revision 27816)
+++ scribus/canvasgesture_rowresize.h (working copy)
@@ -58,6 +58,12 @@
*/
double resizeRowResizeFollowing(double height);
+ /**
+ * Resizes the row to @a height and redistributes the remaining height
+ * proportionally over all following rows.
+ */
+ double resizeRowProportionally(double height);
+
private:
/// Row being resized.
int m_row;
@@ -69,6 +75,8 @@
double m_minRowHeight { 0.0 };
/// Smallest height the following row may be squeezed to (cached at setup).
double m_nextMinRowHeight { 0.0 };
+ /// Content-aware floors used when distributing height proportionally.
+ QList<double> m_proportionalMinRowHeights;
};
#endif // CANVASGESTURE_ROWRESIZE_H
| ||||
| Patch | Yes | ||||