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 @@
+
+
+
+
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
+
+
+
+