//################################################################################################## // // Custom Visualization Core library // Copyright (C) 2017 Ceetron Solutions AS // // This library may be used under the terms of either the GNU General Public License or // the GNU Lesser General Public License as follows: // // GNU General Public License Usage // This library is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at <> // for more details. // // GNU Lesser General Public License Usage // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU Lesser General Public License at <> // for more details. // //################################################################################################## #include "cafPdmUiFormLayoutObjectEditor.h" #include "cafPdmObjectHandle.h" #include "cafPdmUiFieldEditorHandle.h" #include "cafPdmUiFieldEditorHelper.h" #include "cafPdmUiFieldHandle.h" #include "cafPdmUiListEditor.h" #include "cafPdmUiObjectHandle.h" #include "cafPdmUiOrdering.h" #include "cafPdmXmlObjectHandle.h" #include "cafAssert.h" #include "QMinimizePanel.h" #include #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- caf::PdmUiFormLayoutObjectEditor::PdmUiFormLayoutObjectEditor() {} //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- caf::PdmUiFormLayoutObjectEditor::~PdmUiFormLayoutObjectEditor() { // If there are field editor present, the usage of this editor has not cleared correctly // The intended usage is to call the method setPdmObject(NULL) before closing the dialog CAF_ASSERT(m_fieldViews.size() == 0); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void caf::PdmUiFormLayoutObjectEditor::slotScrollToSelectedItemsInFieldEditors() const { for (auto fieldView : m_fieldViews) { auto listEditor = dynamic_cast(fieldView.second); if (listEditor) { listEditor->scrollToSelectedItem(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool caf::PdmUiFormLayoutObjectEditor::recursivelyConfigureAndUpdateUiOrderingInNewGridLayout(const PdmUiOrdering& uiOrdering, QWidget* containerWidget, const QString& uiConfigName) { QSize beforeSize = containerWidget->sizeHint(); ensureWidgetContainsEmptyGridLayout(containerWidget); int stretch = recursivelyConfigureAndUpdateUiOrderingInGridLayout(uiOrdering, containerWidget, uiConfigName); QSize afterSize = containerWidget->sizeHint(); if (beforeSize != afterSize) { containerWidget->adjustSize(); } return stretch > 0; } //-------------------------------------------------------------------------------------------------- /// Add all widgets at a recursion level in the form. /// Returns the stretch factor that should be applied at the level above. //-------------------------------------------------------------------------------------------------- int caf::PdmUiFormLayoutObjectEditor::recursivelyConfigureAndUpdateUiOrderingInGridLayout(const PdmUiOrdering& uiOrdering, QWidget* containerWidgetWithGridLayout, const QString& uiConfigName) { int sumRowStretch = 0; CAF_ASSERT(containerWidgetWithGridLayout); QWidget* previousTabOrderWidget = nullptr; // Currently, only QGridLayout is supported QGridLayout* parentLayout = dynamic_cast(containerWidgetWithGridLayout->layout()); CAF_ASSERT(parentLayout); PdmUiOrdering::TableLayout tableLayout = uiOrdering.calculateTableLayout(uiConfigName); int totalRows = static_cast(tableLayout.size()); int totalColumns = uiOrdering.nrOfColumns(tableLayout); for (int currentRowIndex = 0; currentRowIndex < totalRows; ++currentRowIndex) { int currentColumn = 0; 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) { columnsDiv = std::div(spareColumnsInRow, nrOfExpandingItemsInRow); } 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) { spareColumnsToAssign += columnsDiv.quot; if (i == 0) spareColumnsToAssign += columnsDiv.rem; } int itemColumnSpan = minimumItemColumnSpan + spareColumnsToAssign; if (currentItem->isUiGroup()) { int groupStretchFactor = recursivelyAddGroupToGridLayout(currentItem, containerWidgetWithGridLayout, uiConfigName, parentLayout, currentRowIndex, currentColumn, itemColumnSpan); parentLayout->setRowStretch(currentRowIndex, groupStretchFactor); currentColumn += itemColumnSpan; sumRowStretch += groupStretchFactor; } else { PdmUiFieldEditorHandle* fieldEditor = nullptr; PdmUiFieldHandle* field = dynamic_cast(currentItem); if (field) fieldEditor = findOrCreateFieldEditor(containerWidgetWithGridLayout, field, uiConfigName); if (fieldEditor) { // 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(); if (fieldCombinedWidget) { parentLayout->addWidget(fieldCombinedWidget, currentRowIndex, currentColumn, 1, itemColumnSpan); parentLayout->setRowStretch(currentRowIndex, fieldEditor->rowStretchFactor()); sumRowStretch += fieldEditor->rowStretchFactor(); } else { QWidget* fieldEditorWidget = fieldEditor->editorWidget(); if (!fieldEditorWidget) continue; int fieldColumnSpan = minimumFieldColumnSpan; QWidget* fieldLabelWidget = fieldEditor->labelWidget(); PdmUiItemInfo::LabelPosType labelPos = PdmUiItemInfo::HIDDEN; if (fieldLabelWidget) { labelPos = field->uiLabelPosition(uiConfigName); if (labelPos == PdmUiItemInfo::HIDDEN) { fieldLabelWidget->hide(); } else if (labelPos == PdmUiItemInfo::TOP) { 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 { CAF_ASSERT(labelPos == PdmUiItemInfo::LEFT); int leftLabelColumnSpan = minimumLabelColumnSpan; if (currentLayout.leftLabelColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN && currentLayout.totalColumnSpan != PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) { leftLabelColumnSpan += spareColumnsToAssign; spareColumnsToAssign = 0; } else if (currentLayout.leftLabelColumnSpan == PdmUiOrdering::LayoutOptions::MAX_COLUMN_SPAN) { leftLabelColumnSpan += spareColumnsToAssign / 2; spareColumnsToAssign -= spareColumnsToAssign / 2; } parentLayout->addWidget( fieldLabelWidget, currentRowIndex, currentColumn, 1, leftLabelColumnSpan, Qt::AlignTop); currentColumn += leftLabelColumnSpan; // Apply margins determined by the editor type fieldLabelWidget->setContentsMargins(fieldEditor->labelContentMargins()); } } if (labelPos != PdmUiItemInfo::TOP) // Already added if TOP { 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); currentColumn += fieldColumnSpan; } if (previousTabOrderWidget) { QWidget::setTabOrder(previousTabOrderWidget, fieldEditorWidget); } previousTabOrderWidget = fieldEditorWidget; parentLayout->setRowStretch(currentRowIndex, fieldEditor->rowStretchFactor()); sumRowStretch += fieldEditor->rowStretchFactor(); } fieldEditor->updateUi(uiConfigName); } } } CAF_ASSERT(currentColumn <= totalColumns); } containerWidgetWithGridLayout->updateGeometry(); // The magnitude of the stretch should not be sent up, only if there was stretch or not return sumRowStretch; } //-------------------------------------------------------------------------------------------------- /// Create a group and add widgets. Return true if the containing row needs to be stretched. //-------------------------------------------------------------------------------------------------- int caf::PdmUiFormLayoutObjectEditor::recursivelyAddGroupToGridLayout(PdmUiItem* currentItem, QWidget* containerWidgetWithGridLayout, const QString& uiConfigName, QGridLayout* parentLayout, int currentRowIndex, int currentColumn, int itemColumnSpan) { PdmUiGroup* group = static_cast(currentItem); QMinimizePanel* groupBox = findOrCreateGroupBox(containerWidgetWithGridLayout, group, uiConfigName); int stretch = recursivelyConfigureAndUpdateUiOrderingInGridLayout(*group, groupBox->contentFrame(), uiConfigName); /// Insert the group box at the correct position of the parent layout parentLayout->addWidget(groupBox, currentRowIndex, currentColumn, 1, itemColumnSpan); return stretch; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool caf::PdmUiFormLayoutObjectEditor::isUiGroupExpanded(const PdmUiGroup* uiGroup) const { if (uiGroup->hasForcedExpandedState()) return uiGroup->forcedExpandedState(); auto kwMapPair = m_objectKeywordGroupUiNameExpandedState.find(pdmObject()->xmlCapability()->classKeyword()); if (kwMapPair != m_objectKeywordGroupUiNameExpandedState.end()) { QString keyword = uiGroup->keyword(); auto uiNameExpStatePair = kwMapPair->second.find(keyword); if (uiNameExpStatePair != kwMapPair->second.end()) { return uiNameExpStatePair->second; } } return uiGroup->isExpandedByDefault(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QMinimizePanel* caf::PdmUiFormLayoutObjectEditor::findOrCreateGroupBox(QWidget* parent, PdmUiGroup* group, const QString& uiConfigName) { QString groupBoxKey = group->keyword(); QMinimizePanel* groupBox = nullptr; // Find or create groupBox std::map>::iterator it; it = m_groupBoxes.find(groupBoxKey); if (it == m_groupBoxes.end()) { groupBox = new QMinimizePanel(parent); groupBox->enableFrame(group->enableFrame()); groupBox->setTitle(group->uiName(uiConfigName)); groupBox->setObjectName(group->keyword()); connect(groupBox, SIGNAL(expandedChanged(bool)), this, SLOT(groupBoxExpandedStateToggled(bool))); m_newGroupBoxes[groupBoxKey] = groupBox; } else { groupBox = it->second; CAF_ASSERT(groupBox); m_newGroupBoxes[groupBoxKey] = groupBox; } QMargins contentMargins; if (group->enableFrame()) { contentMargins = QMargins(6, 6, 6, 6); } ensureWidgetContainsEmptyGridLayout(groupBox->contentFrame(), contentMargins); // Set Expanded state bool isExpanded = isUiGroupExpanded(group); groupBox->setExpanded(isExpanded); // Update the title to be able to support dynamic group names groupBox->setTitle(group->uiName(uiConfigName)); groupBox->updateGeometry(); return groupBox; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- caf::PdmUiFieldEditorHandle* caf::PdmUiFormLayoutObjectEditor::findOrCreateFieldEditor(QWidget* parent, PdmUiFieldHandle* field, const QString& uiConfigName) { caf::PdmUiFieldEditorHandle* fieldEditor = nullptr; std::map::iterator it = m_fieldViews.find(field->fieldHandle()); if (it == m_fieldViews.end()) { fieldEditor = PdmUiFieldEditorHelper::createFieldEditorForField(field, uiConfigName); if (fieldEditor) { m_fieldViews[field->fieldHandle()] = fieldEditor; fieldEditor->setContainingEditor(this); fieldEditor->setUiField(field); fieldEditor->createWidgets(parent); } else { // This assert happens if no editor is available for a given field // If the macro for registering the editor is put as the single statement // in a cpp file, a dummy static class must be used to make sure the compile unit // is included // // See cafPdmUiCoreColor3f and cafPdmUiCoreVec3d // This assert will trigger for PdmChildArrayField and PdmChildField // Consider to exclude assert or add editors for these types if the assert is reintroduced // CAF_ASSERT(false); } } else { fieldEditor = it->second; } if (fieldEditor) { m_usedFields.insert(field->fieldHandle()); } return fieldEditor; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void caf::PdmUiFormLayoutObjectEditor::ensureWidgetContainsEmptyGridLayout(QWidget* containerWidget, QMargins contentMargins) { CAF_ASSERT(containerWidget); QLayout* layout = containerWidget->layout(); if (layout != nullptr) { // Remove all items from the layout, then reparent the layout to a temporary // This is because you cannot remove a layout from a widget but it gets moved when reparenting. QLayoutItem* item; while ((item = layout->takeAt(0)) != 0) { } QWidget().setLayout(layout); } QGridLayout* gridLayout = new QGridLayout; gridLayout->setContentsMargins(contentMargins); containerWidget->setLayout(gridLayout); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void caf::PdmUiFormLayoutObjectEditor::groupBoxExpandedStateToggled(bool isExpanded) { if (!this->pdmObject()->xmlCapability()) return; QString objKeyword = this->pdmObject()->xmlCapability()->classKeyword(); QMinimizePanel* panel = dynamic_cast(this->sender()); if (!panel) return; m_objectKeywordGroupUiNameExpandedState[objKeyword][panel->objectName()] = isExpanded; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void caf::PdmUiFormLayoutObjectEditor::cleanupBeforeSettingPdmObject() { std::map::iterator it; for (it = m_fieldViews.begin(); it != m_fieldViews.end(); ++it) { PdmUiFieldEditorHandle* fvh = it->second; delete fvh; } m_fieldViews.clear(); m_newGroupBoxes.clear(); std::map>::iterator groupIt; for (groupIt = m_groupBoxes.begin(); groupIt != m_groupBoxes.end(); ++groupIt) { if (!groupIt->second.isNull()) groupIt->second->deleteLater(); } m_groupBoxes.clear(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void caf::PdmUiFormLayoutObjectEditor::configureAndUpdateUi(const QString& uiConfigName) { caf::PdmUiOrdering config; if (pdmObject()) { caf::PdmUiObjectHandle* uiObject = uiObj(pdmObject()); if (uiObject) { uiObject->uiOrdering(uiConfigName, config); } } // Set all fieldViews to be unvisited m_usedFields.clear(); // Set all group Boxes to be unvisited m_newGroupBoxes.clear(); recursivelyConfigureAndUpdateTopLevelUiOrdering(config, uiConfigName); // Remove all fieldViews not mentioned by the configuration from the layout std::vector fvhToRemoveFromMap; for (auto oldFvIt = m_fieldViews.begin(); oldFvIt != m_fieldViews.end(); ++oldFvIt) { if (m_usedFields.count(oldFvIt->first) == 0) { // The old field editor is not present anymore, get rid of it delete oldFvIt->second; fvhToRemoveFromMap.push_back(oldFvIt->first); } } for (size_t i = 0; i < fvhToRemoveFromMap.size(); ++i) { m_fieldViews.erase(fvhToRemoveFromMap[i]); } // Remove all unmentioned group boxes std::map>::iterator itOld; std::map>::iterator itNew; for (itOld = m_groupBoxes.begin(); itOld != m_groupBoxes.end(); ++itOld) { itNew = m_newGroupBoxes.find(itOld->first); if (itNew == m_newGroupBoxes.end()) { // The old groupBox is not present anymore, get rid of it if (!itOld->second.isNull()) delete itOld->second; } } m_groupBoxes = m_newGroupBoxes; // Notify pdm object when widgets have been created caf::PdmUiObjectHandle* uiObject = uiObj(pdmObject()); if (uiObject) { uiObject->onEditorWidgetsCreated(); } } //-------------------------------------------------------------------------------------------------- /// Unused. Should probably remove //-------------------------------------------------------------------------------------------------- void caf::PdmUiFormLayoutObjectEditor::recursiveVerifyUniqueNames(const std::vector& uiItems, const QString& uiConfigName, std::set* fieldKeywordNames, std::set* groupNames) { for (size_t i = 0; i < uiItems.size(); ++i) { if (uiItems[i]->isUiGroup()) { PdmUiGroup* group = static_cast(uiItems[i]); const std::vector& groupChildren = group->uiItems(); QString groupBoxKey = group->keyword(); if (groupNames->find(groupBoxKey) != groupNames->end()) { // It is not supported to have two groups with identical names CAF_ASSERT(false); } else { groupNames->insert(groupBoxKey); } recursiveVerifyUniqueNames(groupChildren, uiConfigName, fieldKeywordNames, groupNames); } else { PdmUiFieldHandle* field = dynamic_cast(uiItems[i]); QString fieldKeyword = field->fieldHandle()->keyword(); if (fieldKeywordNames->find(fieldKeyword) != fieldKeywordNames->end()) { // It is not supported to have two fields with identical names CAF_ASSERT(false); } else { fieldKeywordNames->insert(fieldKeyword); } } } }