diff --git a/CMakeLists.txt b/CMakeLists.txt index d7ac676..c0cf65d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,7 +183,8 @@ set(MOC_HEADER_FILES ${SRC_ROOT}/SofaVideoRecorderManager.h ${SRC_ROOT}/SofaPluginManager.h ${SRC_ROOT}/SofaSceneGraphWidget.h - ${SRC_ROOT}/WDoubleLineEdit.h + ${SRC_ROOT}/WDoubleLineEdit.h + ${SRC_ROOT}/dockwidgets/InspectorDock.h ${SRC_ROOT}/datawidgets/OptionsGroupWidget.h ${SRC_ROOT}/datawidgets/SelectableItemWidget.h ) @@ -245,6 +246,7 @@ set(SOURCE_FILES ${SRC_ROOT}/SofaSceneGraphWidget.cpp ${SRC_ROOT}/viewer/VisualModelPolicy.cpp ${SRC_ROOT}/QtDataRepository.cpp + ${SRC_ROOT}/dockwidgets/InspectorDock.cpp ${SRC_ROOT}/datawidgets/OptionsGroupWidget.cpp ${SRC_ROOT}/datawidgets/SelectableItemWidget.cpp ) @@ -257,6 +259,8 @@ set(UI_FILES ${SRC_ROOT}/PluginManager.ui ${SRC_ROOT}/ViewerShortcuts.ui ${SRC_ROOT}/VideoRecorderManager.ui + ${SRC_ROOT}/dockwidgets/InspectorDock.ui + ) set(QRC_FILES ${SRC_ROOT}/resources/RealGUI.qrc diff --git a/src/sofa/qt/GUI.ui b/src/sofa/qt/GUI.ui index 4cb8c27..d7263d9 100644 --- a/src/sofa/qt/GUI.ui +++ b/src/sofa/qt/GUI.ui @@ -103,6 +103,10 @@ + + + + @@ -154,11 +158,17 @@ true - + 0 0 + + + 0 + 400 + + QLayout::SetNoConstraint @@ -657,9 +667,6 @@ State 2: dirty, in that state the button reflect the fact that the scene graph v Show node - - false - @@ -686,9 +693,6 @@ State 2: dirty, in that state the button reflect the fact that the scene graph v Show object - - false - @@ -939,6 +943,28 @@ State 2: dirty, in that state the button reflect the fact that the scene graph v Viewer's shortcuts + + + true + + + true + + + Inspector + + + + + true + + + true + + + Controls + + diff --git a/src/sofa/qt/QDisplayDataWidget.cpp b/src/sofa/qt/QDisplayDataWidget.cpp index c3607fc..f2cb4c2 100644 --- a/src/sofa/qt/QDisplayDataWidget.cpp +++ b/src/sofa/qt/QDisplayDataWidget.cpp @@ -202,9 +202,6 @@ bool QDataSimpleEdit::createWidgets() layout->addWidget(innerWidget_.widget.lineEdit); } - - - return true; } diff --git a/src/sofa/qt/RealGUI.cpp b/src/sofa/qt/RealGUI.cpp index c49a709..512aa3e 100644 --- a/src/sofa/qt/RealGUI.cpp +++ b/src/sofa/qt/RealGUI.cpp @@ -35,6 +35,8 @@ #include "SofaWindowDataGraph.h" #endif +#include +#include "dockwidgets/InspectorDock.h" #include #include @@ -158,7 +160,6 @@ class QSOFAApplication : public QApplication QCoreApplication::setOrganizationName("Sofa Consortium"); QCoreApplication::setOrganizationDomain("sofa"); QCoreApplication::setApplicationName("runSofa"); - setStyle(); } @@ -283,6 +284,7 @@ void RealGUI::CreateApplication(int /*_argc*/, char** /*_argv*/) *argc = 1; argv[0] = strdup ( BaseGUI::GetProgramName() ); argv[1]=nullptr; + application = new QSOFAApplication ( *argc,argv ); //force locale to Standard C @@ -361,7 +363,10 @@ RealGUI::RealGUI ( const char* viewername) m_viewerMSAANbSampling(1) { setupUi(this); - + + m_inspectorDock = new InspectorDock(this); + addDockWidget(Qt::RightDockWidgetArea, m_inspectorDock); + ExpandAllButton->setIcon(QIcon(":/RealGUI/expandAll")); CollapseAllButton->setIcon(QIcon(":/RealGUI/collapseAll")); sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh")); @@ -470,6 +475,31 @@ RealGUI::RealGUI ( const char* viewername) connect(helpAboutAction, SIGNAL(triggered()), this, SLOT(showAbout())); m_filelistener = new RealGUIFileListener(this); + + // Replace the menu's actions by the one generated from the docks. + auto action = m_inspectorDock->toggleViewAction(); + action->setText(actionInspector->text()); + View->insertAction(actionInspector, action); + View->removeAction(actionInspector); + actionInspector = action; + + action = dockWidget->toggleViewAction(); + action->setText(actionControls->text()); + View->insertAction(actionControls, action); + View->removeAction(actionControls); + actionControls = action; + + connect(actionViewerShowDocumentation, &QAction::triggered, this, [this](bool){ + QDialog* dialog=new QDialog(); + auto tmp = new Ui::windowViewerShortcuts(); + tmp->setupUi(dialog); + sofa::qt::viewer::SofaViewer* sofaViewer = dynamic_cast(getViewer()); + if(sofaViewer) + tmp->content->setText(sofaViewer->helpString()); + else + tmp->content->setText("There is no documentation for this viewer"); + dialog->open(); + }); } //------------------------------------ @@ -778,6 +808,7 @@ void RealGUI::setSceneWithoutMonitor (Node::SPtr root, const char* filename, boo simulationGraph->resizeColumnToContents(0); statWidget->CreateStats(root.get()); + m_inspectorDock->setCurrentSelection({root}); getViewer()->setScene( root, filename ); getViewer()->load(); getViewer()->resetView(); @@ -817,6 +848,9 @@ void RealGUI::unloadScene(bool _withViewer) if(_withViewer && getViewer()) getViewer()->setScene(nullptr); + + m_inspectorDock->setCurrentSelection({}); + getViewer()->setCurrentSelection({}); } //------------------------------------ @@ -1622,7 +1656,9 @@ void RealGUI::createSimulationGraph() if(m_enableInteraction) { connect(simulationGraph, &QSofaListView::itemSelectionChanged, this, [this](){ - getViewer()->setCurrentSelection(simulationGraph->getCurrentSelectedBases()); + auto selectedItems = simulationGraph->getCurrentSelectedBases(); + getViewer()->setCurrentSelection(selectedItems); + m_inspectorDock->setCurrentSelection(selectedItems); }); }else { diff --git a/src/sofa/qt/RealGUI.h b/src/sofa/qt/RealGUI.h index 0b12675..a813012 100644 --- a/src/sofa/qt/RealGUI.h +++ b/src/sofa/qt/RealGUI.h @@ -27,10 +27,12 @@ #include #include +#include #include "GraphListenerQListView.h" #include "QMenuFilesRecentlyOpened.h" #include "AboutSOFADialog.h" #include "PickHandlerCallBacks.h" +#include "sofa/qt/dockwidgets/InspectorDock.h" #include #include @@ -172,6 +174,8 @@ class SOFA_QT_API RealGUI : public QMainWindow, public Ui::GUI, public sofa::gui std::map< helper::SofaViewerFactory::Key, QAction* > viewerMap; InformationOnPickCallBack informationOnPickCallBack; + InspectorDock* m_inspectorDock; + QWidget* currentTab; QSofaStatWidget* statWidget; QTimer* timerStep; diff --git a/src/sofa/qt/dockwidgets/InspectorDock.cpp b/src/sofa/qt/dockwidgets/InspectorDock.cpp new file mode 100644 index 0000000..a698e2a --- /dev/null +++ b/src/sofa/qt/dockwidgets/InspectorDock.cpp @@ -0,0 +1,230 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program 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 2 of the License, or (at your option) * +* any later version. * +* * +* This program 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 for * +* more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#include "InspectorDock.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace sofa::qt +{ + + +InspectorDock::InspectorDock(QWidget* parent) : QDockWidget(parent) +{ + setupUi(this); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setMinimumWidth(350); +} + +void InspectorDock::setCurrentSelection(const std::set& bases) +{ + m_currentBases = bases; + + if(m_currentBases.empty()) + return; + + updateContentFromBase((*m_currentBases.begin()).get()); +} + +void clearLayout(QLayout *layout) { + if (!layout) + return; + + QLayoutItem *item; + while ((item = layout->takeAt(0)) != nullptr) { + if (QWidget *widget = item->widget()) { + widget->setParent(nullptr); // To be sure the widget is not attached somewhere else. + widget->deleteLater(); // Asynchronous delete + } + else if (QLayout *childLayout = item->layout()) { + clearLayout(childLayout); + delete childLayout; + } + delete item; + } +} + +void InspectorDock::updateContentFromBase(sofa::core::objectmodel::Base* base) +{ + std::vector ordering={"Info", "Properties", "States", "Forces", "Visualization", "Transformation"}; + + + setWindowTitle(QString::fromStdString("Inspector ("+base->getName()+")")); + + // Group data by their group name, if no group is given the data is in the group named "Properties" + std::map> groups; + for(auto& data : base->getDataFields()) + { + auto groupName = data->getGroup(); + if(groupName.empty()) + groupName = "Properties"; + + if(!groups.contains(groupName) ) + { + groups[groupName] = {}; + } + + groups[groupName].emplace_back(data); + } + + for(auto [groupName, data] : groups) + { + if(std::find(ordering.begin(), ordering.end(), groupName)==ordering.end()) + ordering.emplace_back(groupName); + } + + clearLayout(verticalLayout); + + // Create the Info group as it contains elements that are not data. + QGroupBox* box = new QGroupBox(QString::fromStdString("Type"), this); + QFormLayout* formLayout = new QFormLayout(box); + box->setLayout(formLayout); + box->setCheckable(false); + verticalLayout->addWidget(box); + verticalLayout->setAlignment(Qt::AlignTop); + + formLayout->setLabelAlignment(Qt::AlignLeft |Qt::AlignVCenter); + formLayout->setAlignment(Qt::AlignLeft |Qt::AlignVCenter); + formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); + + // Add a lambda to hide/check the elements depending wether the group is check/hidden + QObject::connect(box, &QGroupBox::toggled, [box](bool groupChecked) { + if(groupChecked){ + for (int i = 0; i < box->layout()->count(); ++i) { + QLayoutItem* label = box->layout()->itemAt(i); + label->widget()->show(); + } + }else{ + for (int i = 0; i < box->layout()->count(); ++i) { + QLayoutItem* label = box->layout()->itemAt(i); + label->widget()->hide(); + } + } + }); + formLayout->addRow("Class name:", new QLabel(QString::fromStdString(base->getClassName()))); + if(!base->getTemplateName().empty()) + formLayout->addRow("Parametric type:", new QLabel(QString::fromStdString(base->getTemplateName()))); + + // Creates all the other groups... + for(auto groupName : ordering) + { + if(groups.contains(groupName)) + { + auto& datasInGroup = groups[groupName]; + + QGroupBox* box = new QGroupBox(QString::fromStdString(groupName), this); + QFormLayout* formLayout = new QFormLayout(box); + box->setLayout(formLayout); + box->setCheckable(true); + verticalLayout->addWidget(box); + verticalLayout->setAlignment(Qt::AlignTop); + + formLayout->setLabelAlignment(Qt::AlignLeft |Qt::AlignVCenter); + formLayout->setAlignment(Qt::AlignLeft |Qt::AlignVCenter); + formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); + + // Add a lambda to hide/check the elements depending wether the group is check/hidden + QObject::connect(box, &QGroupBox::toggled, [box](bool groupChecked) { + if(groupChecked){ + for (int i = 0; i < box->layout()->count(); ++i) { + QLayoutItem* label = box->layout()->itemAt(i); + label->widget()->show(); + } + }else{ + for (int i = 0; i < box->layout()->count(); ++i) { + QLayoutItem* label = box->layout()->itemAt(i); + label->widget()->hide(); + } + } + }); + + for(auto data : datasInGroup) + { + ModifyObjectFlags flags; + flags.setFlagsForSofa(); + + DataWidget::CreatorArgument dwarg; + dwarg.parent = this; + dwarg.data = data; + dwarg.name = data->getName(); + dwarg.readOnly = data->isReadOnly(); + if( dynamic_cast(data) != nullptr ) + { + // a bit of a hack for DataFileName widgets. + // A custom widget is used by default if we run this code from the Modeler + + std::string widgetName=data->getWidget(); + if( widgetName.empty() ) + { + data->setWidget("widget_filename"); + } + } + + auto dataWidget = DataWidget::CreateDataWidget(dwarg); + if (dataWidget == nullptr) + { + dataWidget = new QDataSimpleEdit(this,dwarg.data->getName().c_str(), dwarg.data); + dataWidget->createWidgets(); + dataWidget->setDataReadOnly(dwarg.readOnly); + dwarg.readOnly=true; + } + + if(!dwarg.readOnly) + { + connect(dataWidget, &DataWidget::WidgetDirty, this, [dataWidget](bool){ + dataWidget->updateDataValue(); + }); + } + auto label = new QLabel(QString::fromStdString((data->getName()))); + formLayout->addRow(label, dataWidget); + + // Build the tooltip associated with this data + const std::string& help = data->getHelp().c_str(); + const std::string valuetype = data->getValueTypeString(); + const std::string defaultValue = data->getDefaultValueString(); + std::stringstream s; + + s << "" << (!help.empty() ? help : "< No help found >") + << "\n" + << "\nData type: " << valuetype; + if (!defaultValue.empty()) + { + s << "\nDefault value: " << defaultValue; + } + label->setToolTip(QString::fromStdString(s.str())); + dataWidget->setToolTip(QString::fromStdString(s.str())); + } + } + } +} + +} // namespace sofa::qt diff --git a/src/sofa/qt/dockwidgets/InspectorDock.h b/src/sofa/qt/dockwidgets/InspectorDock.h new file mode 100644 index 0000000..ea0811b --- /dev/null +++ b/src/sofa/qt/dockwidgets/InspectorDock.h @@ -0,0 +1,45 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program 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 2 of the License, or (at your option) * +* any later version. * +* * +* This program 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 for * +* more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once +#include +#include +#include +#include + +namespace sofa::qt +{ + +class SOFA_QT_API InspectorDock: public QDockWidget, public Ui::InspectorDock +{ + Q_OBJECT +public: + InspectorDock(QWidget *parent); + + void setCurrentSelection(const std::set& base); + +private: + void updateContentFromBase(sofa::core::objectmodel::Base* base); + + std::set m_currentBases; +}; + +} // namespace sofa::qt diff --git a/src/sofa/qt/dockwidgets/InspectorDock.ui b/src/sofa/qt/dockwidgets/InspectorDock.ui new file mode 100644 index 0000000..066e683 --- /dev/null +++ b/src/sofa/qt/dockwidgets/InspectorDock.ui @@ -0,0 +1,63 @@ + + + InspectorDock + + + + 0 + 0 + 344 + 594 + + + + + 0 + 0 + + + + + 200 + 594 + + + + Inspector + + + + true + + + + + + true + + + + + 0 + 0 + 324 + 555 + + + + + + + + + + + + + + + sofa/qt/config.h + + + +