From fac9870c76c3d1e2980cbfa1d0c2583934b25495 Mon Sep 17 00:00:00 2001 From: Gaute Lindkvist Date: Mon, 11 Feb 2019 13:47:59 +0100 Subject: [PATCH] Rework caf Layout to work better when number of columns isn't fully specified --- .../cafPdmUiCore/cafPdmUiOrdering.cpp | 118 ++++++-- .../cafPdmUiCore/cafPdmUiOrdering.h | 16 +- .../cafPdmUiFormLayoutObjectEditor.cpp | 263 +++++++++--------- .../cafPdmUiFormLayoutObjectEditor.h | 14 +- 4 files changed, 246 insertions(+), 165 deletions(-) diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.cpp b/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.cpp index 2a0490b87c..f8dcf40dc7 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.cpp +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.cpp @@ -311,35 +311,105 @@ const std::vector& PdmUiOrdering::uiItemsWithLayo //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -int PdmUiOrdering::nrOfColumns() const +PdmUiOrdering::TableLayout PdmUiOrdering::calculateTableLayout(const QString& uiConfigName) const { - int maxColumns = 0; - int currentRowColumns = 0; - for (const FieldAndLayout& itemAndLayout : m_ordering) - { - int currentColumnSpan = itemAndLayout.second.totalColumnSpan; - if (currentColumnSpan == LayoutOptions::MAX_COLUMN_SPAN) - { - int minimumFieldColumnSpan = 1; - int minimumLabelColumnSpan = 0; - if (itemAndLayout.first->uiLabelPosition() == PdmUiItemInfo::LEFT) - { - minimumLabelColumnSpan = 1; - } - currentColumnSpan = minimumLabelColumnSpan + minimumFieldColumnSpan; - } + TableLayout tableLayout; - if (itemAndLayout.second.newRow) + for (size_t i = 0; i < m_ordering.size(); ++i) + { + if (m_ordering[i].first->isUiHidden(uiConfigName)) continue; + + if (m_ordering[i].second.newRow || i == 0u) { - currentRowColumns = currentColumnSpan; + tableLayout.push_back(RowLayout()); } - else - { - currentRowColumns += currentColumnSpan; - } - maxColumns = std::max(maxColumns, currentRowColumns); + tableLayout.back().push_back(m_ordering[i]); + } + return tableLayout; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int PdmUiOrdering::nrOfColumns(const TableLayout& tableLayout) const +{ + int maxNrOfColumns = 0; + + for (const auto& rowContent : tableLayout) + { + maxNrOfColumns = std::max(maxNrOfColumns, nrOfRequiredColumnsInRow(rowContent)); + } + + return maxNrOfColumns; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int PdmUiOrdering::nrOfRequiredColumnsInRow(const RowLayout& rowItems) const +{ + int totalColumns = 0; + for (const FieldAndLayout& item : rowItems) + { + int totalItemColumns = 0, labelItemColumns = 0, fieldItemColumns = 0; + nrOfColumnsRequiredForItem(item, &totalItemColumns, &labelItemColumns, &fieldItemColumns); + totalColumns += totalItemColumns; + } + return totalColumns; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int PdmUiOrdering::nrOfExpandingItemsInRow(const RowLayout& rowItems) const +{ + int nrOfExpandingItems = 0; + for (const FieldAndLayout& item : rowItems) + { + if (item.second.totalColumnSpan == LayoutOptions::MAX_COLUMN_SPAN) + nrOfExpandingItems++; + } + return nrOfExpandingItems; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmUiOrdering::nrOfColumnsRequiredForItem(const FieldAndLayout& fieldAndLayout, + int* totalColumnsRequired, + int* labelColumnsRequired, + int* fieldColumnsRequired) const +{ + const PdmUiItem* uiItem = fieldAndLayout.first; + CAF_ASSERT(uiItem && totalColumnsRequired && labelColumnsRequired && fieldColumnsRequired); + + LayoutOptions layoutOption = fieldAndLayout.second; + + if (uiItem->isUiGroup()) + { + *totalColumnsRequired = 1; + *labelColumnsRequired = 0; + *fieldColumnsRequired = 0; + } + else + { + *fieldColumnsRequired = 1; + *labelColumnsRequired = 0; + if (uiItem->uiLabelPosition() == PdmUiItemInfo::LEFT) + { + *labelColumnsRequired = 1; + if (layoutOption.leftLabelColumnSpan != LayoutOptions::MAX_COLUMN_SPAN) + { + *labelColumnsRequired = layoutOption.leftLabelColumnSpan; + } + } + *totalColumnsRequired = *labelColumnsRequired + *fieldColumnsRequired; + } + + if (layoutOption.totalColumnSpan != LayoutOptions::MAX_COLUMN_SPAN) + { + *totalColumnsRequired = layoutOption.totalColumnSpan; } - return maxColumns; } //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.h b/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.h index 9dce113c9a..052ee0a0ba 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.h +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiOrdering.h @@ -67,6 +67,8 @@ public: int leftLabelColumnSpan; }; typedef std::pair FieldAndLayout; + typedef std::vector RowLayout; + typedef std::vector TableLayout; PdmUiOrdering(): m_skipRemainingFields(false) { }; virtual ~PdmUiOrdering(); @@ -92,10 +94,18 @@ public: void skipRemainingFields(bool doSkip = true); // Pdm internal methods + + const std::vector uiItems() const; + const std::vector& uiItemsWithLayout() const; - const std::vector uiItems() const; - const std::vector& uiItemsWithLayout() const; - int nrOfColumns() const; + std::vector> calculateTableLayout(const QString& uiConfigName) const; + int nrOfColumns(const TableLayout& tableLayout) const; + int nrOfRequiredColumnsInRow(const RowLayout& rowItems) const; + int nrOfExpandingItemsInRow(const RowLayout& rowItems) const; + void nrOfColumnsRequiredForItem(const FieldAndLayout& fieldAndLayout, + int* totalColumnsRequired, + int* labelColumnsRequired, + int* fieldColumnsRequired) const; bool contains(const PdmUiItem* item) const; bool isIncludingRemainingFields() const; diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.cpp b/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.cpp index 54cc7a18d2..c185499741 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.cpp +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.cpp @@ -76,190 +76,181 @@ caf::PdmUiFormLayoutObjectEditor::~PdmUiFormLayoutObjectEditor() void caf::PdmUiFormLayoutObjectEditor::recursivelyConfigureAndUpdateUiOrderingInGridLayoutColumn( const PdmUiOrdering& uiOrdering, QWidget* containerWidgetWithGridLayout, - const QString& uiConfigName) + const QString& uiConfigName, + QWidget** previousTabOrderWidget) { CAF_ASSERT(containerWidgetWithGridLayout); - int currentRowIndex = -1; - QWidget* previousTabOrderWidget = nullptr; - // Currently, only QGridLayout is supported QGridLayout* parentLayout = dynamic_cast(containerWidgetWithGridLayout->layout()); CAF_ASSERT(parentLayout); - const std::vector& uiItems = uiOrdering.uiItemsWithLayout(); + PdmUiOrdering::TableLayout tableLayout = uiOrdering.calculateTableLayout(uiConfigName); - int columnsPerRow = uiOrdering.nrOfColumns(); + int totalRows = static_cast(tableLayout.size()); + int totalColumns = uiOrdering.nrOfColumns(tableLayout); - int currentColumn = 0; - int itemsInCurrentRow = 1; - for (size_t i = 0; i < uiItems.size(); ++i) + for (int currentRowIndex = 0; currentRowIndex < totalRows; ++currentRowIndex) { - PdmUiItem* currentItem = uiItems[i].first; - PdmUiOrdering::LayoutOptions currentLayout = uiItems[i].second; - int itemColumnSpan = currentLayout.totalColumnSpan; + int currentColumn = 0; - if (currentRowIndex == -1 || currentLayout.newRow) + const PdmUiOrdering::RowLayout& uiItemsInRow = tableLayout[currentRowIndex]; + + int columnsRequiredForCurrentRow = uiOrdering.nrOfRequiredColumnsInRow(uiItemsInRow); + int nrOfExpandingItemsInRow = uiOrdering.nrOfExpandingItemsInRow(uiItemsInRow); + int spareColumnsInRow = totalColumns - columnsRequiredForCurrentRow; + + std::div_t columnsDiv = { 0, 0 }; + if (spareColumnsInRow && nrOfExpandingItemsInRow) { - currentRowIndex++; - parentLayout->setRowStretch(currentRowIndex, 0); + columnsDiv = std::div(spareColumnsInRow, nrOfExpandingItemsInRow); + } - currentColumn = 0; - itemsInCurrentRow = 1; - for (size_t j = i+1; j < uiItems.size(); ++j) + for (size_t i = 0; i < uiItemsInRow.size(); ++i) + { + PdmUiItem* currentItem = uiItemsInRow[i].first; + PdmUiOrdering::LayoutOptions currentLayout = uiItemsInRow[i].second; + + int minimumItemColumnSpan = 0, minimumLabelColumnSpan = 0, minimumFieldColumnSpan = 0; + uiOrdering.nrOfColumnsRequiredForItem(uiItemsInRow[i], &minimumItemColumnSpan, &minimumLabelColumnSpan, &minimumFieldColumnSpan); + bool isExpandingItem = currentLayout.totalColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN; + + int spareColumnsToAssign = 0; + if (isExpandingItem) { - if (uiItems[j].second.newRow) break; - itemsInCurrentRow++; + spareColumnsToAssign += columnsDiv.quot; + if (i == 0) spareColumnsToAssign += columnsDiv.rem; } - } - if (itemColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) - { - itemColumnSpan = columnsPerRow / itemsInCurrentRow; - } - - if (currentItem->isUiHidden(uiConfigName)) continue; - - if (currentItem->isUiGroup()) - { - PdmUiGroup* group = static_cast(currentItem); - - QMinimizePanel* groupBox = findOrCreateGroupBox(containerWidgetWithGridLayout, group, uiConfigName); - - /// Insert the group box at the correct position of the parent layout - parentLayout->addWidget(groupBox, currentRowIndex, currentColumn, 1, itemColumnSpan); + int itemColumnSpan = minimumItemColumnSpan + spareColumnsToAssign; - recursivelyConfigureAndUpdateUiOrderingInGridLayoutColumn(*group, groupBox->contentFrame(), uiConfigName); - - currentColumn += itemColumnSpan; - } - else - { - PdmUiFieldHandle* field = dynamic_cast(currentItem); - - PdmUiFieldEditorHandle* fieldEditor = findOrCreateFieldEditor(containerWidgetWithGridLayout, field, uiConfigName); - - if (fieldEditor) + if (currentItem->isUiGroup()) { - fieldEditor->setUiField(field); + recursivelyAddGroupToGridLayout(currentItem, containerWidgetWithGridLayout, + uiConfigName, parentLayout, currentRowIndex, + currentColumn, itemColumnSpan, previousTabOrderWidget); + currentColumn += itemColumnSpan; + } + else + { + // Also assign required item space that isn't taken up by field and label + spareColumnsToAssign += minimumItemColumnSpan - (minimumLabelColumnSpan + minimumFieldColumnSpan); - // Place the widget(s) into the correct parent and layout - QWidget* fieldCombinedWidget = fieldEditor->combinedWidget(); + PdmUiFieldHandle* field = dynamic_cast(currentItem); - if (fieldCombinedWidget) + PdmUiFieldEditorHandle* fieldEditor = findOrCreateFieldEditor(containerWidgetWithGridLayout, field, uiConfigName); + + if (fieldEditor) { - fieldCombinedWidget->setParent(containerWidgetWithGridLayout); - parentLayout->addWidget(fieldCombinedWidget, currentRowIndex, currentColumn, 1, itemColumnSpan); - } - else - { - PdmUiItemInfo::LabelPosType labelPos = field->uiLabelPosition(uiConfigName); + fieldEditor->setUiField(field); - QWidget* fieldEditorWidget = fieldEditor->editorWidget(); - if (fieldEditorWidget) + // Place the widget(s) into the correct parent and layout + QWidget* fieldCombinedWidget = fieldEditor->combinedWidget(); + + if (fieldCombinedWidget) { - // Hide label - if (labelPos == PdmUiItemInfo::HIDDEN) + fieldCombinedWidget->setParent(containerWidgetWithGridLayout); + parentLayout->addWidget(fieldCombinedWidget, currentRowIndex, currentColumn, 1, itemColumnSpan); + } + else + { + QWidget* fieldEditorWidget = fieldEditor->editorWidget(); + if (!fieldEditorWidget) continue; + int fieldColumnSpan = minimumFieldColumnSpan; + + QWidget* fieldLabelWidget = fieldEditor->labelWidget(); + PdmUiItemInfo::LabelPosType labelPos = PdmUiItemInfo::HIDDEN; + + if (fieldLabelWidget) { - QWidget* fieldLabelWidget = fieldEditor->labelWidget(); - if (fieldLabelWidget) + labelPos = field->uiLabelPosition(uiConfigName); + + if (labelPos == PdmUiItemInfo::HIDDEN) { fieldLabelWidget->hide(); } - - fieldEditorWidget->setParent(containerWidgetWithGridLayout); // To make sure this widget has the current group box as parent. - parentLayout->addWidget(fieldEditorWidget, currentRowIndex, currentColumn, 1, itemColumnSpan, Qt::AlignTop); - - currentColumn += itemColumnSpan; - } - else // Add label - { - QWidget* fieldLabelWidget = fieldEditor->labelWidget(); - - // For label on top we add another layer of QLayouts to avoid messing with the global rows. - if (labelPos == PdmUiItemInfo::TOP) + else if (labelPos == PdmUiItemInfo::TOP) { - QVBoxLayout* labelAndFieldVLayout = new QVBoxLayout(); - parentLayout->addLayout(labelAndFieldVLayout, currentRowIndex, currentColumn, 1, itemColumnSpan, Qt::AlignTop); - if (fieldLabelWidget) - { - labelAndFieldVLayout->addWidget(fieldLabelWidget, 0, Qt::AlignTop); - } - labelAndFieldVLayout->addWidget(fieldEditorWidget, 1, Qt::AlignTop); + QVBoxLayout* labelAndFieldVerticalLayout = new QVBoxLayout(); + parentLayout->addLayout( + labelAndFieldVerticalLayout, currentRowIndex, currentColumn, 1, itemColumnSpan, Qt::AlignTop); + labelAndFieldVerticalLayout->addWidget(fieldLabelWidget, 0, Qt::AlignTop); + labelAndFieldVerticalLayout->addWidget(fieldEditorWidget, 1, Qt::AlignTop); + // Apply margins determined by the editor type + fieldLabelWidget->setContentsMargins(fieldEditor->labelContentMargins()); - currentColumn += itemColumnSpan; } else { - int fieldColumnSpan = currentLayout.totalColumnSpan; - int leftLabelColumnSpan = 0; - if (fieldLabelWidget) + CAF_ASSERT(labelPos == PdmUiItemInfo::LEFT); + int leftLabelColumnSpan = minimumLabelColumnSpan; + if (currentLayout.leftLabelColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN && + currentLayout.totalColumnSpan != PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) { - leftLabelColumnSpan = currentLayout.leftLabelColumnSpan; - if (fieldColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN && - leftLabelColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) - { - // Rounded up half for field. Rest for left label. - fieldColumnSpan = itemColumnSpan / 2 + itemColumnSpan % 2; - leftLabelColumnSpan = itemColumnSpan - fieldColumnSpan; - } - else if (fieldColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) - { - fieldColumnSpan = itemColumnSpan - leftLabelColumnSpan; - } - else if (leftLabelColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) - { - fieldColumnSpan = 1; - leftLabelColumnSpan = itemColumnSpan - fieldColumnSpan; - } - else - { - fieldColumnSpan = itemColumnSpan - leftLabelColumnSpan; - } - CAF_ASSERT(fieldColumnSpan >= 1 && "Need at least one column for the field"); - fieldColumnSpan = std::max(1, fieldColumnSpan); - fieldLabelWidget->setParent(containerWidgetWithGridLayout); - parentLayout->addWidget(fieldLabelWidget, currentRowIndex, currentColumn, 1, leftLabelColumnSpan, Qt::AlignTop); - - // Apply margins determined by the editor type - fieldLabelWidget->setContentsMargins(fieldEditor->labelContentMargins()); + leftLabelColumnSpan += spareColumnsToAssign; + spareColumnsToAssign = 0; } - else + else if (currentLayout.leftLabelColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) { - if (fieldColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) - { - fieldColumnSpan = itemColumnSpan; - } + leftLabelColumnSpan += spareColumnsToAssign / 2; + spareColumnsToAssign -= spareColumnsToAssign / 2; } - fieldEditorWidget->setParent(containerWidgetWithGridLayout); // To make sure this widget has the current group box as parent. - parentLayout->addWidget(fieldEditorWidget, currentRowIndex, currentColumn + leftLabelColumnSpan, 1, fieldColumnSpan, Qt::AlignTop); - currentColumn += itemColumnSpan; + parentLayout->addWidget(fieldLabelWidget, currentRowIndex, currentColumn, 1, leftLabelColumnSpan, Qt::AlignTop); + currentColumn += leftLabelColumnSpan; } } - if (previousTabOrderWidget) + if (labelPos != PdmUiItemInfo::TOP) // Already added if TOP { - QWidget::setTabOrder(previousTabOrderWidget, fieldEditorWidget); - } + fieldColumnSpan += spareColumnsToAssign; + + CAF_ASSERT(fieldColumnSpan >= 1 && "Need at least one column for the field"); + fieldColumnSpan = std::max(1, fieldColumnSpan); + + parentLayout->addWidget(fieldEditorWidget, currentRowIndex, currentColumn, 1, fieldColumnSpan, Qt::AlignTop); + + // Apply margins determined by the editor type + fieldLabelWidget->setContentsMargins(fieldEditor->labelContentMargins()); + + currentColumn += fieldColumnSpan; + } + + if (previousTabOrderWidget && *previousTabOrderWidget) + { + QWidget::setTabOrder(*previousTabOrderWidget, fieldEditorWidget); + } + previousTabOrderWidget = &fieldLabelWidget; } + fieldEditor->updateUi(uiConfigName); } - fieldEditor->updateUi(uiConfigName); } } + int stretchFactor = currentRowIndex == totalRows - 1 ? 1 : 0; + parentLayout->setRowStretch(currentRowIndex, stretchFactor); } +} - // Set last row with content to stretch - if (currentRowIndex >= 0 && currentRowIndex < parentLayout->rowCount()) - { - parentLayout->setRowStretch(currentRowIndex++, 1); - } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void caf::PdmUiFormLayoutObjectEditor::recursivelyAddGroupToGridLayout(PdmUiItem* currentItem, + QWidget* containerWidgetWithGridLayout, + const QString& uiConfigName, + QGridLayout* parentLayout, + int currentRowIndex, + int currentColumn, + int itemColumnSpan, + QWidget** previousTabOrderWidget) +{ + PdmUiGroup* group = static_cast(currentItem); - // Set remaining rows to stretch zero, as we want the row with content to stretch all the way - while (currentRowIndex >= 0 && currentRowIndex < parentLayout->rowCount()) - { - parentLayout->setRowStretch(currentRowIndex++, 0); - } + QMinimizePanel* groupBox = findOrCreateGroupBox(containerWidgetWithGridLayout, group, uiConfigName); + + /// Insert the group box at the correct position of the parent layout + parentLayout->addWidget(groupBox, currentRowIndex, currentColumn, 1, itemColumnSpan); + + recursivelyConfigureAndUpdateUiOrderingInGridLayoutColumn(*group, groupBox->contentFrame(), uiConfigName, previousTabOrderWidget); } //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.h b/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.h index 1855ab618f..2c7dda2e8b 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.h +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.h @@ -47,6 +47,7 @@ class QMinimizePanel; class QGridLayout; +class QWidget; namespace caf { @@ -72,8 +73,17 @@ protected: const QString& uiConfigName) = 0; void recursivelyConfigureAndUpdateUiOrderingInGridLayoutColumn(const PdmUiOrdering& uiOrdering, - QWidget* containerWidgetWithGridLayout, - const QString& uiConfigName); + QWidget* containerWidgetWithGridLayout, + const QString& uiConfigName, QWidget** previousTabOrderWidget = nullptr); + + void recursivelyAddGroupToGridLayout(PdmUiItem* currentItem, + QWidget* containerWidget, + const QString& uiConfigName, + QGridLayout* parentLayout, + int currentRowIndex, + int currentColumn, + int itemColumnSpan, + QWidget** previousTabOrderWidget); QMinimizePanel* findOrCreateGroupBox(QWidget* parent, PdmUiGroup* group, const QString& uiConfigName); PdmUiFieldEditorHandle* findOrCreateFieldEditor(QWidget* parent, PdmUiFieldHandle* field, const QString& uiConfigName);