/******************************************************************************
* 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 <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt) *
* *
* Contact information: contact@sofa-framework.org *
******************************************************************************/
#include "RealGUI.h"
#include <sofa/version.h>
#ifdef SOFA_DUMP_VISITOR_INFO
#include "WindowVisitor.h"
#include "GraphVisitor.h"
#endif
#if SOFA_GUI_QT_HAVE_QT_CHARTS
#include "SofaWindowProfiler.h"
#endif
#if SOFA_GUI_QT_HAVE_NODEEDITOR
#include "SofaWindowDataGraph.h"
#endif
#include <mutex>
#include <QScreen>
#include "QSofaListView.h"
#include "QDisplayPropertyWidget.h"
#include "FileManagement.h"
#include "DisplayFlagsDataWidget.h"
#include "SofaPluginManager.h"
#include "SofaMouseManager.h"
#include "SofaVideoRecorderManager.h"
#include "WDoubleLineEdit.h"
#include "QSofaStatWidget.h"
#include "viewer/SofaViewer.h"
#include <sofa/gui/common/BaseViewer.h>
#include <sofa/simulation/common/xml/XML.h>
#include <sofa/simulation/DeactivatedNodeVisitor.h>
#include <sofa/component/visual/VisualStyle.h>
#include <sofa/helper/AdvancedTimer.h>
#include <sofa/helper/ScopedAdvancedTimer.h>
#include <sofa/simulation/SimulationLoop.h>
#include <sofa/helper/system/SetDirectory.h>
using sofa::helper::system::SetDirectory;
#include <sofa/helper/system/FileSystem.h>
using sofa::helper::system::FileSystem;
#include <sofa/helper/Utils.h>
using sofa::helper::Utils;
#include <sofa/helper/system/FileRepository.h>
using sofa::helper::system::DataRepository;
#include <sofa/gui/common/GuiDataRepository.h>
using sofa::gui::common::GuiDataRepository;
#include <sofa/simulation/SceneLoaderFactory.h>
using sofa::simulation::SceneLoaderFactory;
#include <sofa/simulation/Simulation.h>
#include <sofa/simulation/ExportGnuplotVisitor.h>
#include <QHBoxLayout>
#include <QApplication>
#include <QTimer>
#include <QTextBrowser>
#include <QWidget>
#include <QStackedWidget>
#include <QTreeWidget>
#include <QTextEdit>
#include <QAction>
#include <QMessageBox>
#include <QDockWidget>
#include <QStatusBar>
#include <QSettings>
#include <QMimeData>
#include <QCompleter>
#include <QDesktopServices>
#if (QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
#include <QDesktopWidget>
#endif
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <ctime>
#include <sofa/core/objectmodel/IdleEvent.h>
using sofa::core::objectmodel::IdleEvent;
#include <sofa/simulation/events/SimulationStartEvent.h>
using sofa::simulation::SimulationStartEvent;
#include <sofa/simulation/events/SimulationStopEvent.h>
using sofa::simulation::SimulationStopEvent;
#include <sofa/helper/system/FileMonitor.h>
using sofa::helper::system::FileMonitor;
#include <sofa/core/ObjectFactory.h>
using sofa::core::ObjectFactory;
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
#include "panels/QDocBrowser.h"
using sofa::gui::qt::DocBrowser;
#endif
using sofa::core::ExecParams;
#include <sofa/gui/common/ArgumentParser.h>
using namespace sofa::gui::common;
namespace sofa::gui::qt
{
using sofa::core::objectmodel::BaseObject;
using namespace sofa::helper::system::thread;
using namespace sofa::simulation;
using namespace sofa::core::visual;
/// Custom QApplication class handling FileOpen events for MacOS
class QSOFAApplication : public QApplication
{
public:
QSOFAApplication(int &argc, char ** argv)
: QApplication(argc,argv)
{
QCoreApplication::setOrganizationName("Sofa Consortium");
QCoreApplication::setOrganizationDomain("sofa");
QCoreApplication::setApplicationName("runSofa");
}
#if QT_VERSION < 0x050000
static inline QString translate(const char * context, const char * key, const char * disambiguation,
QCoreApplication::Encoding encoding = QCoreApplication::UnicodeUTF8, int n = -1)
{ return QApplication::translate(context, key, disambiguation, encoding, n); }
#else
static inline QString translate(const char * context, const char * key,
const char * disambiguation = Q_NULLPTR, int n = -1)
{ return QApplication::translate(context, key, disambiguation, n); }
#endif
protected:
bool event(QEvent *event) override
{
switch (event->type())
{
case QEvent::FileOpen:
{
if(this->topLevelWidgets().count() < 1)
return false;
return true;
}
default:
return QApplication::event(event);
}
}
};
RealGUI* gui = nullptr;
QApplication* application = nullptr;
const char* progname="";
class RealGUIFileListener : public sofa::helper::system::FileEventListener
{
public:
RealGUIFileListener(RealGUI* realgui){
m_realgui = realgui;
}
~RealGUIFileListener() override{}
void fileHasChanged(const std::string& filename) override
{
m_realgui->fileOpen(filename, false, true);
}
RealGUI* m_realgui;
};
//======================= STATIC METHODS ========================= {
void RealGUI::setupSurfaceFormat()
{
static std::once_flag flag;
std::call_once(flag, []
{
QSurfaceFormat format;
if(!SOFA_GUI_QT_ENABLE_VSYNC)
{
format.setSwapInterval(0); //Setting an interval value of 0 will turn the vertical refresh syncing off
}
static constexpr int vmajor = 3, vminor = 2;
format.setVersion(vmajor,vminor); //Sets the desired major and minor OpenGL versions.
format.setProfile(QSurfaceFormat::CompatibilityProfile); //Sets the desired OpenGL context profile. CompatibilityProfile = Functionality from earlier OpenGL versions is available.
format.setOption(QSurfaceFormat::DeprecatedFunctions, true); //Used to request that deprecated functions be included in the OpenGL context profile. If not specified, you should get a forward compatible context without support functionality marked as deprecated. This requires OpenGL version 3.0 or higher.
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
if (mArgumentParser)
{
unsigned int viewerMSAANbSampling = 0;
mArgumentParser->getValueFromKey("msaa", viewerMSAANbSampling);
if (viewerMSAANbSampling > 1)
{
msg_info("RealGUI") << "Set multisampling anti-aliasing (MSAA) with " << viewerMSAANbSampling << " samples." ;
format.setSamples(static_cast<int>(viewerMSAANbSampling));
}
}
QSurfaceFormat::setDefaultFormat(format);
});
}
BaseGUI* RealGUI::CreateGUI ( const char* name, sofa::simulation::Node::SPtr root, const char* filename )
{
setupSurfaceFormat();
CreateApplication();
// create interface
gui = new RealGUI ( name );
if ( root )
{
gui->setScene ( root, filename );
gui->setWindowFilePath(QString(filename));
}
InitApplication(gui);
return gui;
}
//------------------------------------
void RealGUI::SetPixmap(std::string pixmap_filename, QPushButton* b)
{
if ( DataRepository.findFile ( pixmap_filename ) )
pixmap_filename = DataRepository.getFile ( pixmap_filename );
b->setIcon(QIcon(QPixmap(QPixmap::fromImage(QImage(pixmap_filename.c_str())))));
}
//------------------------------------
void RealGUI::CreateApplication(int /*_argc*/, char** /*_argv*/)
{
int *argc = new int;
char **argv=new char*[2];
*argc = 1;
argv[0] = strdup ( BaseGUI::GetProgramName() );
argv[1]=nullptr;
application = new QSOFAApplication ( *argc,argv );
//force locale to Standard C
//(must be done immediatly after the QApplication has been created)
const QLocale locale(QLocale::C);
QLocale::setDefault(locale);
}
//------------------------------------
void RealGUI::InitApplication( RealGUI* _gui)
{
const QString pathIcon=(DataRepository.getFirstPath() + std::string( "/icons/SOFA.png" )).c_str();
application->setWindowIcon(QIcon(pathIcon));
if(SOFA_GUI_QT_ENABLE_NATIVE_MENU)
{
// Use the OS'native menu instead of the Qt one
_gui->menubar->setNativeMenuBar(true);
}
else
{
// Use the qt menu instead of the native one in order to standardize the way the menu is showed on every OS
_gui->menubar->setNativeMenuBar(false);
}
// show the gui
_gui->show(); // adding extra line in the console?
}
//======================= STATIC METHODS ========================= }
//======================= CONSTRUCTOR - DESTRUCTOR ========================= {
RealGUI::RealGUI ( const char* viewername)
:
fpsLabel(nullptr),
timeLabel(nullptr),
#ifdef SOFA_DUMP_VISITOR_INFO
windowTraceVisitor(nullptr),
handleTraceVisitor(nullptr),
#endif
m_sofaMouseManager(nullptr),
#if SOFAGUIQT_HAVE_QT5_CHARTS
m_windowTimerProfiler(nullptr),
#endif
#if SOFAGUIQT_HAVE_NODEEDITOR
m_sofaWindowDataGraph(nullptr),
#endif
simulationGraph(nullptr),
m_dumpState(false),
m_dumpStateStream(nullptr),
m_exportGnuplot(false),
m_animateOBJ(false),
m_animationOBJcounter(0),
m_displayComputationTime(false),
m_fullScreen(false),
m_viewer(nullptr),
m_clockBeforeLastStep(0),
propertyWidget(nullptr),
currentTab ( nullptr ),
statWidget(nullptr),
timerStep(nullptr),
backgroundImage(nullptr),
pluginManagerDialog(nullptr),
recentlyOpenedFilesManager(BaseGUI::getConfigDirectoryPath() + "/runSofa.ini"),
m_saveReloadFile(false),
displayFlag(nullptr),
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
m_docbrowser(nullptr),
#endif
m_animationState(false),
m_frameCounter(0),
m_viewerMSAANbSampling(1)
{
setupUi(this);
ExpandAllButton->setIcon(QIcon(":/RealGUI/expandAll"));
CollapseAllButton->setIcon(QIcon(":/RealGUI/collapseAll"));
sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh"));
for (auto* button : {ExpandAllButton, CollapseAllButton, sceneGraphRefreshToggleButton})
{
button->setFixedWidth(button->height());
}
parseOptions();
createPluginManager();
createRecentFilesMenu(); // configure Recently Opened Menu
QDoubleValidator *dtValidator = new QDoubleValidator(dtEdit);
dtValidator->setBottom(0.000000001);
dtEdit->setValidator(dtValidator);
timerStep = new QTimer(this);
connect ( timerStep, SIGNAL ( timeout() ), this, SLOT ( step() ) );
connect ( this, SIGNAL ( quit() ), this, SLOT ( fileExit() ) );
connect ( startButton, SIGNAL ( toggled ( bool ) ), this , SLOT ( playpauseGUI ( bool ) ) );
connect ( ReloadSceneButton, SIGNAL ( clicked() ), this, SLOT ( fileReload() ) );
connect ( dtEdit, SIGNAL ( textChanged ( const QString& ) ), this, SLOT ( setDt ( const QString& ) ) );
connect ( realTimeCheckBox, SIGNAL ( stateChanged ( int ) ), this, SLOT ( updateDtEditState() ) );
connect ( stepButton, SIGNAL ( clicked() ), this, SLOT ( step() ) );
connect ( dumpStateCheckBox, SIGNAL ( toggled ( bool ) ), this, SLOT ( dumpState ( bool ) ) );
connect ( displayComputationTimeCheckBox, SIGNAL ( toggled ( bool ) ), this, SLOT ( displayComputationTime ( bool ) ) );
connect ( exportGnuplotFilesCheckbox, SIGNAL ( toggled ( bool ) ), this, SLOT ( setExportGnuplot ( bool ) ) );
connect ( tabs, SIGNAL ( currentChanged ( int ) ), this, SLOT ( currentTabChanged ( int ) ) );
connect ( ResetViewButton, SIGNAL ( clicked() ), this, SLOT ( resetView() ) );
connect ( SaveViewButton, SIGNAL ( clicked() ), this, SLOT ( saveView() ) );
connect ( screenshotButton, SIGNAL ( clicked() ), this, SLOT ( screenshot() ) );
connect ( sizeW, SIGNAL ( valueChanged ( int ) ), this, SLOT ( setSizeW ( int ) ) );
connect ( sizeH, SIGNAL ( valueChanged ( int ) ), this, SLOT ( setSizeH ( int ) ) );
/// We activate this timer only if the interactive mode is enabled (ie livecoding+mouse mouve event).
if(m_enableInteraction){
timerIdle = new QTimer(this);
connect ( timerIdle, SIGNAL ( timeout() ), this, SLOT ( emitIdle() ) );
timerIdle->start(50);
}
this->setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowTabbedDocks);
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
dockWidget->setFeatures(QDockWidget::AllDockWidgetFeatures);
#else
dockWidget->setFeatures(QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable);
#endif
dockWidget->setAllowedAreas(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea);
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(toolsDockMoved()));
//Status Bar Configuration
fpsLabel = new QLabel ( "9999.9 FPS", statusBar() );
fpsLabel->setMinimumSize ( fpsLabel->sizeHint() );
fpsLabel->clear();
timeLabel = new QLabel ( "Time: 999.9999", statusBar() );
timeLabel->setMinimumSize ( timeLabel->sizeHint() );
timeLabel->clear();
statusBar()->addWidget ( fpsLabel );
statusBar()->addWidget ( timeLabel );
statWidget = new QSofaStatWidget(TabStats);
TabStats->layout()->addWidget(statWidget);
// create al widgets first
m_sofaMouseManager = new SofaMouseManager(this);
createSimulationGraph();
//disable widget, can be bothersome with objects with a lot of data
//createPropertyWidget();
//viewer
informationOnPickCallBack = InformationOnPickCallBack(this);
viewerMap.clear();
createViewer(viewername, true);
currentTabChanged ( tabs->currentIndex() );
createBackgroundGUIInfos(); // add GUI for Background Informations
createWindowVisitor();
createAdvancedTimerProfilerWindow();
m_sofaMouseManager->hide();
SofaVideoRecorderManager::getInstance()->hide();
centerWindow();
tabs->removeTab(tabs->indexOf(TabVisualGraph));
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
m_docbrowser = new DocBrowser(this);
/// Signal to the realGUI that the visibility has changed (eg: to update the menu bar)
connect(m_docbrowser, SIGNAL(visibilityChanged(bool)), this, SLOT(docBrowserVisibilityChanged(bool)));
#endif
// Trigger QDialog for "About" section
connect(helpAboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
m_filelistener = new RealGUIFileListener(this);
}
//------------------------------------
RealGUI::~RealGUI()
{
if( displayFlag != nullptr )
delete displayFlag;
#ifdef SOFA_DUMP_VISITOR_INFO
delete windowTraceVisitor;
delete handleTraceVisitor;
#endif
removeViewer();
FileMonitor::removeListener(m_filelistener);
delete m_filelistener;
}
//======================= CONSTRUCTOR - DESTRUCTOR ========================= }
//======================= OPTIONS DEFINITIONS ========================= {
#ifdef SOFA_DUMP_VISITOR_INFO
void RealGUI::setTraceVisitors(bool b)
{
exportVisitorCheckbox->setChecked(b);
}
#endif
//------------------------------------
//======================= OPTIONS DEFINITIONS ========================= }
//======================= METHODS ========================= {
void RealGUI::docBrowserVisibilityChanged(bool visibility)
{
if(visibility)
helpShowDocBrowser->setText("Hide doc browser");
else
helpShowDocBrowser->setText("Show doc browser");
}
void RealGUI::stepMainLoop () {
application->processEvents();
}
int RealGUI::mainLoop()
{
int retcode;
if (windowFilePath().isNull())
{
retcode = application->exec();
}
else
{
const std::string &filename=windowFilePath().toStdString();
const std::string &extension=SetDirectory::GetExtension(filename.c_str());
if (extension == "simu") fileOpenSimu(filename);
retcode = application->exec();
}
return exitApplication(retcode);
}
//------------------------------------
int RealGUI::closeGUI()
{
QSettings settings;
QScreen* screen = widget->window()->windowHandle()->screen();
settings.beginGroup("viewer");
settings.setValue("screenNumber", QGuiApplication::screens().indexOf(screen));
settings.endGroup();
delete this;
return 0;
}
//------------------------------------
sofa::simulation::Node* RealGUI::currentSimulation()
{
return mSimulation.get();
}
//------------------------------------
void RealGUI::fileOpen ( std::string filename, bool temporaryFile, bool reload )
{
std::vector<std::string> expandedNodes;
if(reload)
{
saveView();
if(simulationGraph)
simulationGraph->getExpandedNodes(expandedNodes);
}
const std::string &extension=SetDirectory::GetExtension(filename.c_str());
if (extension == "simu")
{
return fileOpenSimu(filename);
}
startButton->setChecked(false);
startDumpVisitor();
update();
//Hide all the dialogs to modify the graph
emit ( newScene() );
if ( DataRepository.findFile (filename) )
filename = DataRepository.getFile ( filename );
else
return;
sofa::simulation::xml::numDefault = 0;
if( currentSimulation() ) this->unloadScene();
const std::vector<std::string> sceneArgs = ArgumentParser::extra_args();
mSimulation = sofa::simulation::node::load ( filename, reload, sceneArgs );
sofa::simulation::node::initRoot(mSimulation.get());
if ( mSimulation == nullptr )
{
msg_warning("RealGUI")<<"Failed to load "<<filename.c_str();
return;
}
if(reload)
setSceneWithoutMonitor(mSimulation, filename.c_str(), temporaryFile);
else{
setScene(mSimulation, filename.c_str(), temporaryFile);
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
m_docbrowser->loadHtml( filename ) ;
#endif
}
configureGUI(mSimulation.get());
this->setWindowFilePath(filename.c_str());
setExportGnuplot(exportGnuplotFilesCheckbox->isChecked());
stopDumpVisitor();
if(!expandedNodes.empty())
{
simulationGraph->expandPathFrom(expandedNodes);
}
#if SOFA_GUI_QT_HAVE_QT_CHARTS
if (m_windowTimerProfiler)
m_windowTimerProfiler->resetGraph();
#endif
#if SOFA_GUI_QT_HAVE_NODEEDITOR
if (m_sofaWindowDataGraph)
m_sofaWindowDataGraph->resetNodeGraph(currentSimulation());
#endif
}
//------------------------------------
void RealGUI::emitIdle()
{
// Update all the registered monitor.
FileMonitor::updates(0);
IdleEvent hb;
Node* groot = m_viewer->getScene();
if (groot)
{
groot->propagateEvent(core::execparams::defaultInstance(), &hb);
}
getSofaViewer()->getQWidget()->update();
}
/// This open popup the file selection windows.
void RealGUI::popupOpenFileSelector()
{
const std::string filename(this->windowFilePath().toStdString());
// build the filter with the SceneLoaderFactory
std::string filter, allKnownFilters = "All known (";
SceneLoaderFactory::SceneLoaderList* loaders = SceneLoaderFactory::getInstance()->getEntries();
for (SceneLoaderFactory::SceneLoaderList::iterator it=loaders->begin(); it!=loaders->end(); ++it)
{
if (it!=loaders->begin()) filter +=";;";
filter += (*it)->getFileTypeDesc();
filter += " (";
SceneLoader::ExtensionList extensions;
(*it)->getExtensionList(&extensions);
for (SceneLoader::ExtensionList::iterator itExt=extensions.begin(); itExt!=extensions.end(); ++itExt)
{
if (itExt!=extensions.begin()) filter +=" ";
filter+="*.";
filter+=(*itExt);
allKnownFilters+="*."+(*itExt);
if (*it!=loaders->back() || itExt!=extensions.end()-1) allKnownFilters += " ";
}
filter+=")";
}
allKnownFilters+=")";
filter += ";;Simulation (*.simu)";
filter = allKnownFilters+";;"+filter+";;All (*)"; // the first filter is selected by default
QString selectedFilter( tr(allKnownFilters.c_str()) ); // this does not select the desired filter
QString s = getOpenFileName ( this, filename.empty() ?nullptr:filename.c_str(),
filter.c_str(),
"open file dialog", "Choose a file to open", &selectedFilter
);
if ( s.length() >0 )
{
if (s.endsWith( ".simu") )
fileOpenSimu(s.toStdString());
else
fileOpen (s.toStdString());
}
}
//------------------------------------
void RealGUI::fileOpenSimu ( std::string s )
{
std::ifstream in(s.c_str());
if (!in.fail())
{
std::string filename;
std::string initT, endT, dT, writeName;
in
>> filename
>> initT >> initT
>> endT >> endT >> endT
>> dT >> dT
>> writeName >> writeName;
in.close();
if ( DataRepository.findFile (filename) )
{
filename = DataRepository.getFile ( filename );
simulationName = s;
const std::string::size_type pointSimu = simulationName.rfind(".simu");
simulationName.resize(pointSimu);
fileOpen(filename.c_str());
dtEdit->setText(QString(dT.c_str()));
}
}
}
//------------------------------------
void RealGUI::setSceneWithoutMonitor (Node::SPtr root, const char* filename, bool temporaryFile)
{
if (filename)
{
if (!temporaryFile)
recentlyOpenedFilesManager.openFile(filename);
m_saveReloadFile=temporaryFile;
setTitle ( filename );
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
if (m_docbrowser && filename)
{
m_docbrowser->loadHtml( filename );
}
#endif
}
if (root)
{
//Check the validity of the BBox
const sofa::type::BoundingBox& nodeBBox = root->getContext()->f_bbox.getValue();
if(nodeBBox.isNegligeable())
{
msg_warning("RealGUI") << "Global Bounding Box seems very small; Your viewer settings (based on the bbox) are likely invalid, switching to default value of [-1,-1,-1,1,1,1]."
<< "This is caused by using component which does not implement properly the computeBBox function."
<< "You can remove this warning by manually forcing a value in the parameter bbox=\"minX minY minZ maxX maxY maxZ\" in your root node \n";
const sofa::type::BoundingBox b(-1.0,-1.0,-1.0,1.0,1.0,1.0);
root->f_bbox.setValue(b);
}
mSimulation = root;
eventNewTime();
startButton->setChecked(root->getContext()->getAnimate() );
dtEdit->setText ( QString::number ( root->getDt() ) );
simulationGraph->setRoot(root.get());
simulationGraph->collapseAll();
simulationGraph->expandToDepth(0);
simulationGraph->resizeColumnToContents(0);
statWidget->CreateStats(root.get());
getViewer()->setScene( root, filename );
getViewer()->load();
getViewer()->resetView();
createDisplayFlags( root );
getSofaViewer()->getQWidget()->setFocus();
getSofaViewer()->getQWidget()->show();
getSofaViewer()->getQWidget()->update();
resetScene();
}
}
void RealGUI::setScene(Node::SPtr root, const char* filename, bool temporaryFile)
{
if(m_enableInteraction && filename){
FileMonitor::removeListener(m_filelistener);
FileMonitor::addFile(filename, m_filelistener);
}
setSceneWithoutMonitor(root, filename, temporaryFile) ;
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
if (m_docbrowser && filename)
{
m_docbrowser->loadHtml( filename ) ;
}
#endif
}
//------------------------------------
void RealGUI::unloadScene(bool _withViewer)
{
if(_withViewer && getViewer())
getViewer()->unload();
sofa::simulation::node::unload ( currentSimulation() );
if(_withViewer && getViewer())
getViewer()->setScene(nullptr);
}
//------------------------------------
void RealGUI::setTitle ( std::string windowTitle )
{
std::string str = "SOFA v" + std::string(SOFA_VERSION_STR);
if ( !windowTitle.empty() )
{
str += " - ";
str += windowTitle;
}
#ifdef WIN32
setWindowTitle ( str.c_str() );
#else
this->setWindowTitle(QString(str.c_str()) );
#endif
setWindowFilePath( windowTitle.c_str() );
}
//------------------------------------
void RealGUI::fileReload()
{
std::string filename(this->windowFilePath().toStdString());
QString s = filename.c_str();
if ( filename.empty() )
{
msg_error("RealGUI") << "Reload failed: no file loaded.";
return;
}
if (s.endsWith( ".simu") )
fileOpenSimu(s.toStdString());
else
fileOpen ( s.toStdString(),m_saveReloadFile );
}
//------------------------------------
void RealGUI::fileExit()
{
//Hide all opened ModifyObject windows
emit ( newScene() );
startButton->setChecked ( false);
this->close();
}
void RealGUI::editRecordDirectory()
{
const std::string filename(this->windowFilePath().toStdString());
std::string record_directory;
const QString s = getExistingDirectory ( this, filename.empty() ?nullptr:filename.c_str(), "open directory dialog", "Choose a directory" );
if (s.length() > 0)
{
record_directory = s.toStdString();
if (record_directory.at(record_directory.size()-1) != '/')
record_directory+="/";
}
}
//------------------------------------
void RealGUI::editGnuplotDirectory()
{
const std::string filename(this->windowFilePath().toStdString());
const QString s = getExistingDirectory ( this, filename.empty() ?nullptr:filename.c_str(), "open directory dialog", "Choose a directory" );
if (s.length() > 0)
{
gnuplotDirectory = s.toStdString();
if (gnuplotDirectory.at(gnuplotDirectory.size()-1) != '/')
gnuplotDirectory+="/";
setExportGnuplot(exportGnuplotFilesCheckbox->isChecked());
}
}
//------------------------------------
void RealGUI::showDocBrowser()
{
#if(SOFA_GUI_QT_HAVE_QT5_WEBENGINE)
m_docbrowser->flipVisibility();
#else
msg_warning("RealGUI") << "Doc browser has been disabled because Qt5WebEngine is not available";
#endif
}
//------------------------------------
void RealGUI::showAbout()
{
//create the QDialog for About
AboutSOFADialog* aboutSOFA_dialog = new sofa::gui::qt::AboutSOFADialog(this);
aboutSOFA_dialog->show();
}
//------------------------------------
void RealGUI::showPluginManager()
{
pluginManagerDialog->updatePluginsListView();
pluginManagerDialog->show();
}
//------------------------------------
void RealGUI::showMouseManager()
{
m_sofaMouseManager->updateContent();
m_sofaMouseManager->show();
}
//------------------------------------
void RealGUI::showVideoRecorderManager()
{
SofaVideoRecorderManager::getInstance()->show();
}
//------------------------------------
void RealGUI::showWindowDataGraph()
{
#if SOFA_GUI_QT_HAVE_NODEEDITOR
std::cout << "RealGUI::showWindowDataGraph()" << std::endl;
//m_sofaMouseManager->createGraph();
if (m_sofaWindowDataGraph == nullptr)
{
createSofaWindowDataGraph();
}
m_sofaWindowDataGraph->show();
#endif
}
//------------------------------------
void RealGUI::setViewerResolution ( int w, int h )
{
const QSize winSize = size();
const QSize viewSize = ( getViewer() ) ? getSofaViewer()->getQWidget()->size() : QSize(0,0);
#if (QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
const QRect screen = QApplication::desktop()->availableGeometry(QApplication::desktop()->screenNumber(this));
#else
const QRect screen = QGuiApplication::primaryScreen()->availableGeometry();
#endif
QSize newWinSize(winSize.width() - viewSize.width() + w, winSize.height() - viewSize.height() + h);
if (newWinSize.width() > screen.width()) newWinSize.setWidth(screen.width()-20);
if (newWinSize.height() > screen.height()) newWinSize.setHeight(screen.height()-20);
this->resize(newWinSize);
}
//------------------------------------
void RealGUI::setFullScreen (bool enable)
{
if (enable == m_fullScreen) return;
if (enable)
{
optionTabs->hide();
}
else if (m_fullScreen)
{
optionTabs->show();
}
if (enable)
{
msg_info("RealGUI") << "Set Full Screen Mode";
showFullScreen();
m_fullScreen = true;
dockWidget->setFloating(true);
dockWidget->setVisible(false);
}
else
{
msg_info("RealGUI") << "Set Windowed Mode";
showNormal();
m_fullScreen = false;
dockWidget->setVisible(true);
dockWidget->setFloating(false);
}
if (enable)
{
menuBar()->hide();
statusBar()->hide();
}
else
{
menuBar()->show();
statusBar()->show();
}
}
void RealGUI::centerWindow()
{
//Center the application
#if (QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
const QRect screen = QApplication::desktop()->availableGeometry(QApplication::desktop()->primaryScreen());
#else
const QRect screen = QGuiApplication::primaryScreen()->availableGeometry();
#endif
this->move( ( screen.width() - this->width() ) / 2, ( screen.height() - this->height()) / 2 );
}
//------------------------------------
void RealGUI::setBackgroundColor(const sofa::type::RGBAColor& c)
{
background[0]->setText(QString::number(c[0]));
background[1]->setText(QString::number(c[1]));
background[2]->setText(QString::number(c[2]));
updateBackgroundColour();
}
//------------------------------------
void RealGUI::setBackgroundImage(const std::string& c)
{
backgroundImage->setText(QString(c.c_str()));
updateBackgroundImage();
}
//------------------------------------
void RealGUI::setViewerConfiguration(sofa::component::setting::ViewerSetting* viewerConf)
{
const type::Vec<2,int> &res=viewerConf->resolution.getValue();
if (viewerConf->fullscreen.getValue())
setFullScreen();
else
setViewerResolution(res[0], res[1]);
getViewer()->configure(viewerConf);
}
//------------------------------------
void RealGUI::setMouseButtonConfiguration(sofa::component::setting::MouseButtonSetting *button)
{
m_sofaMouseManager->updateOperation(button);
}
//------------------------------------
void RealGUI::setDumpState(bool b)
{
dumpStateCheckBox->setChecked(b);
}
//------------------------------------
void RealGUI::setLogTime(bool b)
{
displayComputationTimeCheckBox->setChecked(b);
}
//------------------------------------
void RealGUI::setExportState(bool b)
{
exportGnuplotFilesCheckbox->setChecked(b);
}
//------------------------------------
void RealGUI::setGnuplotPath(const std::string &path)
{
gnuplotDirectory = path;
}
//------------------------------------
void RealGUI::createViewer(const char* _viewerName, bool _updateViewerList/*=false*/)
{
if(_updateViewerList)
{
this->updateViewerList();
// the viewer with the key viewerName is already created
if( m_viewer != nullptr && !viewerMap.begin()->first.compare( std::string(_viewerName) ) )
return;
}
for (std::map< helper::SofaViewerFactory::Key, QAction*>::const_iterator iter_map = viewerMap.begin();
iter_map != viewerMap.end(); ++iter_map )
{
if( strcmp( iter_map->first.c_str(), _viewerName ) == 0 )
{
removeViewer();
ViewerQtArgument viewerArg = ViewerQtArgument("viewer", this->widget, m_viewerMSAANbSampling);
registerViewer( helper::SofaViewerFactory::CreateObject(iter_map->first, viewerArg) );
//see to put on checkable
iter_map->second->setChecked(true);
}
else
iter_map->second->setChecked(false);
}
mGuiName = _viewerName;
initViewer( getViewer() );
}
//------------------------------------
void RealGUI::registerViewer(BaseViewer* _viewer)
{
// Change our viewer
if(_viewer == nullptr)
{
msg_error("RealGUI")<<"when registerViewer, the viewer is nullptr";
return;
}
sofa::gui::qt::viewer::SofaViewer* tmpViewer = dynamic_cast<sofa::gui::qt::viewer::SofaViewer*>(_viewer);
if(tmpViewer != nullptr)
{
const sofa::gui::qt::viewer::SofaViewer* old = m_viewer;
m_viewer = tmpViewer;
delete old;
}
else
{
msg_error("RealGUI")<<"when registerViewer, the viewer can't be cast as sofa::gui::qt::viewer::SofaViewer*";
}
}
//------------------------------------
BaseViewer* RealGUI::getViewer()
{
return m_viewer;
}
//------------------------------------
sofa::gui::qt::viewer::SofaViewer* RealGUI::getSofaViewer()
{
return m_viewer;
}
//------------------------------------
void RealGUI::removeViewer()
{
if(m_viewer != nullptr)
{
getSofaViewer()->removeViewerTab(tabs);
delete m_viewer;
m_viewer = nullptr;
}
}
//------------------------------------
void RealGUI::dragEnterEvent( QDragEnterEvent* event)
{
event->accept();
}
//------------------------------------
void RealGUI::dropEvent(QDropEvent* event)
{
QString text;
//Q3TextDrag::decode(event, text);
if (event->mimeData()->hasText())
text = event->mimeData()->text();
std::string filename(text.toStdString());
#ifdef WIN32
filename = filename.substr(8); //removing file:///
#else
filename = filename.substr(7); //removing file://
#endif
if (filename[filename.size()-1] == '\n')
{
filename.resize(filename.size()-1);
filename[filename.size()-1]='\0';
}
if (filename.rfind(".simu") != std::string::npos)
fileOpenSimu(filename);
else fileOpen(filename);
}
//------------------------------------
void RealGUI::init()
{
m_frameCounter = 0;
m_animateOBJ = false;
m_animationOBJcounter = 0;
m_dumpState = false;
m_dumpStateStream = 0;
m_displayComputationTime = false;
m_exportGnuplot = false;
gnuplotDirectory = "";
m_fullScreen = false;
}
//------------------------------------
void RealGUI::createDisplayFlags(Node::SPtr root)
{
if( displayFlag != nullptr)
{
gridLayout1->removeWidget(displayFlag);
delete displayFlag;
displayFlag = nullptr;
}
sofa::component::visual::VisualStyle* visualStyle = nullptr;
if( root )
{
root->get(visualStyle);
if(visualStyle)
{
displayFlag = new DisplayFlagsDataWidget(tabView,"displayFlagwidget",&visualStyle->displayFlags, true);
displayFlag->createWidgets();
displayFlag->updateWidgetValue();
connect( displayFlag, SIGNAL( WidgetDirty(bool) ), this, SLOT(showhideElements() ));
displayFlag->setMinimumSize(50,100);
gridLayout1->addWidget(displayFlag,0,0);
connect(tabs,SIGNAL(currentChanged(int)),displayFlag, SLOT( updateWidgetValue() ));
}
}
}
//------------------------------------
// Update sofa Simulation with the time step
void RealGUI::eventNewStep()
{
static ctime_t beginTime[10];
static const ctime_t timeTicks = CTime::getRefTicksPerSec();
const Node* root = currentSimulation();
if ( m_frameCounter==0 )
{
const ctime_t t = CTime::getRefTime();
for ( int i=0; i<10; i++ )
beginTime[i] = t;
}
++m_frameCounter;
if ( ( m_frameCounter%10 ) == 0 )
{
const ctime_t curtime = CTime::getRefTime();
const int i = ( ( m_frameCounter/10 ) %10 );
const double fps = ( ( double ) timeTicks / ( curtime - beginTime[i] ) ) * ( m_frameCounter<100?m_frameCounter:100 );
showFPS(fps);
beginTime[i] = curtime;
}
if ( m_displayComputationTime && ( m_frameCounter%100 ) == 0 && root!=nullptr )
{
/// @TODO: use AdvancedTimer in GUI to display time statistics
}
}
void RealGUI::showFPS(double fps)
{
if (fpsLabel)
{
char buf[100];
sprintf ( buf, "%.1f FPS", fps );
'sprintf' is deprecated: This function is provided for compatibility reasons only. Due to security concerns inherent in the design of sprintf(3), it is highly recommended that you use snprintf(3) instead.
fpsLabel->setText ( buf );
}
}
//------------------------------------
void RealGUI::eventNewTime()
{
const Node* root = currentSimulation();
if (root && timeLabel)
{
const double time = root->getTime();
char buf[100];
sprintf ( buf, "Time: %.3g, Steps: %i", time, m_frameCounter );
timeLabel->setText ( buf );
}
}
//------------------------------------
void RealGUI::keyPressEvent ( QKeyEvent * e )
{
if (e->modifiers()) return;
// ignore if there are modifiers (i.e. CTRL of SHIFT)
switch ( e->key() )
{
case Qt::Key_O:
// --- export to OBJ
{
exportOBJ ( currentSimulation() );
break;
}
case Qt::Key_P:
// --- export to a succession of OBJ to make a video
{
m_animateOBJ = !m_animateOBJ;
m_animationOBJcounter = 0;
break;
}
case Qt::Key_Space:
{
playpauseGUI(!startButton->isChecked());
break;
}
case Qt::Key_Backspace:
{
resetScene();
break;
}
case Qt::Key_F11:
// --- fullscreen mode
{
setFullScreen(!m_fullScreen);
break;
}
case Qt::Key_Escape:
{
emit(quit());
break;
}
case Qt::Key_S:
{
screenshot();
break;
}
default:
{
if (m_viewer)
m_viewer->keyPressEvent(e);
break;
}
}
}
//------------------------------------
void RealGUI::startDumpVisitor()
{
#ifdef SOFA_DUMP_VISITOR_INFO
Node* root = currentSimulation();
if (root && this->exportVisitorCheckbox->isChecked())
{
m_dumpVisitorStream.str("");
Visitor::startDumpVisitor(&m_dumpVisitorStream, root->getTime());
}
#endif
}
//------------------------------------
void RealGUI::stopDumpVisitor()
{
#ifdef SOFA_DUMP_VISITOR_INFO
if (this->exportVisitorCheckbox->isChecked())
{
Visitor::stopDumpVisitor();
m_dumpVisitorStream.flush();
//Creation of the graph
std::string xmlDoc=m_dumpVisitorStream.str();
handleTraceVisitor->load(xmlDoc);
m_dumpVisitorStream.str("");
}
#endif
}
//------------------------------------
void RealGUI::initViewer(BaseViewer* _viewer)
{
if(_viewer == nullptr)
{
msg_error("RealGUI")<<"when initViewer, the viewer is nullptr";
return;
}
init(); //init data member from RealGUI for the viewer initialisation in the GUI
// Is our viewer not a qt::viewer::SofaViewer ?
sofa::gui::qt::viewer::SofaViewer* sofaViewer = dynamic_cast<sofa::gui::qt::viewer::SofaViewer*>(_viewer);
if( sofaViewer == nullptr )
{
msg_error("RealGUI") << "initViewer failed as given _viewer is not of type sofa::gui::qt::viewer::SofaViewer*";
}
else
{
this->mainWidgetLayout->addWidget(sofaViewer->getQWidget());
sofaViewer->getQWidget()->setFocusPolicy ( Qt::StrongFocus );
sofaViewer->getQWidget()->setSizePolicy ( QSizePolicy ( ( QSizePolicy::Policy ) 7,
( QSizePolicy::Policy ) 7
//, 100, 1,
//sofaViewer->getQWidget()->sizePolicy().hasHeightForWidth() )
));
sofaViewer->getQWidget()->setMinimumSize ( QSize ( 0, 0 ) );
sofaViewer->getQWidget()->setMouseTracking ( true );
sofaViewer->configureViewerTab(tabs);
connect ( sofaViewer->getQWidget(), SIGNAL ( resizeW ( int ) ), sizeW, SLOT ( setValue ( int ) ) );
connect ( sofaViewer->getQWidget(), SIGNAL ( resizeH ( int ) ), sizeH, SLOT ( setValue ( int ) ) );
connect ( sofaViewer->getQWidget(), SIGNAL ( quit ( ) ), this, SLOT ( fileExit ( ) ) );
connect(simulationGraph, SIGNAL(focusChanged(sofa::core::objectmodel::BaseObject*)),
sofaViewer->getQWidget(), SLOT(fitObjectBBox(sofa::core::objectmodel::BaseObject*))
);
connect(simulationGraph, SIGNAL( focusChanged(sofa::core::objectmodel::BaseNode*) ),
sofaViewer->getQWidget(), SLOT( fitNodeBBox(sofa::core::objectmodel::BaseNode*) )
);
// setGUI
textEdit1->setText ( sofaViewer->helpString() );
connect ( this, SIGNAL( newStep()), sofaViewer->getQWidget(), SLOT( update()));
sofaViewer->getQWidget()->setFocus();
sofaViewer->getQWidget()->show();
sofaViewer->getQWidget()->update();
sofaViewer->getPickHandler()->addCallBack(&informationOnPickCallBack );
}
m_sofaMouseManager->setPickHandler(_viewer->getPickHandler());
}
//------------------------------------
void RealGUI::parseOptions()
{
if (mArgumentParser) {
mArgumentParser->getValueFromKey("interactive", m_enableInteraction);
mArgumentParser->getValueFromKey("msaa", m_viewerMSAANbSampling);
if (m_enableInteraction)
msg_warning("runSofa") << "you activated the interactive mode. This is currently an experimental feature "
"that may change or be removed in the future. ";
}
}
//------------------------------------
void RealGUI::createPluginManager()
{
pluginManagerDialog = new SofaPluginManager(this);
pluginManagerDialog->hide();
this->connect( pluginManagerDialog, SIGNAL( libraryAdded() ), this, SLOT( updateViewerList() ));
this->connect( pluginManagerDialog, SIGNAL( libraryRemoved() ), this, SLOT( updateViewerList() ));
}
void RealGUI::createSofaWindowDataGraph()
{
#if SOFA_GUI_QT_HAVE_NODEEDITOR
m_sofaWindowDataGraph = new SofaWindowDataGraph(this, currentSimulation());
m_sofaWindowDataGraph->hide();
#endif
}
//------------------------------------
void RealGUI::createRecentFilesMenu()
{
fileMenu->removeAction(Action);
//const int indexRecentlyOpened=fileMenu->count()-2;
const int indexRecentlyOpened=fileMenu->actions().count();
QMenu *recentMenu = recentlyOpenedFilesManager.createWidget(this);
fileMenu->insertMenu(fileMenu->actions().at(indexRecentlyOpened-1),
recentMenu);
connect(recentMenu, SIGNAL(triggered(QAction *)), this, SLOT(fileRecentlyOpened(QAction *)));
}
//------------------------------------
void RealGUI::createBackgroundGUIInfos()
{
QWidget *colour = new QWidget(TabPage);
QHBoxLayout *colourLayout = new QHBoxLayout(colour);
colourLayout->addWidget(new QLabel(QString("Colour "),colour));
for (unsigned int i=0; i<3; ++i)
{
std::ostringstream s;
s<<"background" <<i;
background[i] = new WDoubleLineEdit(colour,s.str().c_str());
background[i]->setMinValue( 0.0f);
background[i]->setMaxValue( 1.0f);
background[i]->setValue( 1.0f);
background[i]->setMaximumSize(50, 20);
colourLayout->addWidget(background[i]);
connect( background[i], SIGNAL( returnPressed() ), this, SLOT( updateBackgroundColour() ) );
}
QWidget *image = new QWidget(TabPage);
QHBoxLayout *imageLayout = new QHBoxLayout(image);
imageLayout->addWidget(new QLabel(QString("Image "),image));
backgroundImage = new QLineEdit(image);
backgroundImage->setText("backgroundImage");
if ( getViewer() )
backgroundImage->setText( QString(getViewer()->getBackgroundImage().c_str()) );
else
backgroundImage->setText( QString() );
imageLayout->addWidget(backgroundImage);
connect( backgroundImage, SIGNAL( returnPressed() ), this, SLOT( updateBackgroundImage() ) );
((QVBoxLayout*)(TabPage->layout()))->insertWidget(1,colour);
((QVBoxLayout*)(TabPage->layout()))->insertWidget(2,image);
}
//------------------------------------
void RealGUI::createSimulationGraph()
{
simulationGraph = new QSofaListView(SIMULATION,TabGraph,"SimuGraph");
TabGraph->layout()->addWidget(simulationGraph);
connect ( ExportGraphButton, SIGNAL ( clicked() ), simulationGraph, SLOT ( Export() ) );
connect ( ExpandAllButton, SIGNAL ( clicked() ), simulationGraph, SLOT ( expandAll() ) );
connect ( CollapseAllButton, SIGNAL ( clicked() ), simulationGraph, SLOT ( ExpandRootNodeOnly() ) );
connect ( sceneGraphRefreshToggleButton, &QPushButton::clicked , this, &RealGUI::onSceneGraphRefreshButtonClicked );
connect(simulationGraph, &QSofaListView::dirtynessChanged, this, &RealGUI::sceneGraphViewDirtynessChanged);
connect(simulationGraph, &QSofaListView::lockingChanged, this, &RealGUI::sceneGraphViewLockingChanged);
connect(simulationGraph, SIGNAL( RootNodeChanged(sofa::simulation::Node*, const char*) ), this, SLOT ( newRootNode(sofa::simulation::Node* , const char*) ) );
connect(simulationGraph, SIGNAL( NodeRemoved() ), this, SLOT( update() ) );
connect(simulationGraph, SIGNAL( Lock(bool) ), this, SLOT( lockAnimation(bool) ) );
connect(simulationGraph, SIGNAL( RequestSaving(sofa::simulation::Node*) ), this, SLOT( fileSaveAs(sofa::simulation::Node*) ) );
connect(simulationGraph, SIGNAL( RequestExportOBJ(sofa::simulation::Node*, bool) ), this, SLOT( exportOBJ(sofa::simulation::Node*, bool) ) );
connect(simulationGraph, SIGNAL( RequestActivation(sofa::simulation::Node*, bool) ), this, SLOT( activateNode(sofa::simulation::Node*, bool) ) );
connect(simulationGraph, SIGNAL( RequestSleeping(sofa::simulation::Node*, bool) ), this, SLOT( setSleepingNode(sofa::simulation::Node*, bool) ) );
connect(simulationGraph, SIGNAL( Updated() ), this, SLOT( redraw() ) );
connect(simulationGraph, SIGNAL( NodeAdded() ), this, SLOT( update() ) );
connect(simulationGraph, SIGNAL( dataModified( QString ) ), this, SLOT( appendToDataLogFile(QString ) ) );
connect(this, SIGNAL( newScene() ), simulationGraph, SLOT( CloseAllDialogs() ) );
connect(this, SIGNAL( newStep() ), simulationGraph, SLOT( UpdateOpenedDialogs() ) );
std::ifstream file( BaseGUI::getConfigDirectoryPath() + "/sceneGraphLock" );
if(file.is_open())
{
bool isLocked;
file >> isLocked;
if (isLocked)
{
simulationGraph->lock();
}
else
{
simulationGraph->unLock();
}
file.close();
}
else
{
simulationGraph->unLock();
}
}
// This slot is called when the sceneGraph view is set to dirty
void RealGUI::sceneGraphViewDirtynessChanged(bool isDirty)
{
if(isDirty)
{
sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh-dirty"));
}
else if(simulationGraph->isLocked())
{
sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh-locked"));
}
else
{
sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh-unlocked"));
}
}
// This slot is called when the sceneGraph view has been locked/unlocked.
// The locking state indicates how the view is taking into account the scene graph data changes.
// when locked, the vue is not updated. When unlocked all changes are taken into account.
void RealGUI::sceneGraphViewLockingChanged(bool isLocked)
{
if(isLocked)
{
sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh-locked"));
}
else
{
sceneGraphRefreshToggleButton->setIcon(QIcon(":/RealGUI/sceneGraphRefresh-unlocked"));
}
}
// The scene graph update button has three states
// State 0: unlocked (all the changes on the graph are immediately taken into account)
// State 1: locked (the changes on the graph are not done but the simulation graph is set to dirty if
// there is some changes on the graph. A click on the button unlocks the graph (go to state 0).
// State 2: dirty, in that state the button reflect the fact that the scene graph view has changed but not displayed.
// A click on the button refreshes the graph view but does not change the Lock/Unlock state
void RealGUI::onSceneGraphRefreshButtonClicked()
{
if(simulationGraph->isLocked())
{
simulationGraph->unLock();
}
else
{
simulationGraph->lock();
}
std::ofstream file( BaseGUI::getConfigDirectoryPath() + "/sceneGraphLock" );
if(file)
{
file << simulationGraph->isLocked();
file.close();
}
}
void RealGUI::createPropertyWidget()
{
ModifyObjectFlags modifyObjectFlags = ModifyObjectFlags();
modifyObjectFlags.setFlagsForSofa();
propertyWidget = new QDisplayPropertyWidget(modifyObjectFlags);
QDockWidget *dockProperty=new QDockWidget(this);
dockProperty->setAllowedAreas(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea);
dockProperty->setMaximumSize(QSize(300,300));
dockProperty->setWidget(propertyWidget);
connect(dockProperty, SIGNAL(dockLocationChanged(QDockWidget::DockWidgetArea)),
this, SLOT(propertyDockMoved(QDockWidget::DockWidgetArea)));
simulationGraph->setPropertyWidget(propertyWidget);
}
//------------------------------------
void RealGUI::createWindowVisitor()
{
pathDumpVisitor = SetDirectory::GetParentDir(DataRepository.getFirstPath().c_str()) + std::string( "/dumpVisitor.xml" );
#ifndef SOFA_DUMP_VISITOR_INFO
//Remove option to see visitor trace
this->exportVisitorCheckbox->hide();
#else
//Main window containing a QListView only
windowTraceVisitor = new WindowVisitor(this);
windowTraceVisitor->graphView->setSortingEnabled(false);
windowTraceVisitor->hide();
connect ( exportVisitorCheckbox, SIGNAL ( toggled ( bool ) ), this, SLOT ( setExportVisitor ( bool ) ) );
connect(windowTraceVisitor, SIGNAL(WindowVisitorClosed(bool)), this->exportVisitorCheckbox, SLOT(setChecked(bool)));
handleTraceVisitor = new GraphVisitor(windowTraceVisitor);
#endif
}
void RealGUI::createAdvancedTimerProfilerWindow()
{
#if SOFA_GUI_QT_HAVE_QT_CHARTS
m_windowTimerProfiler = new SofaWindowProfiler(this);
m_windowTimerProfiler->hide();
connect( displayTimeProfiler, SIGNAL ( toggled ( bool ) ), this, SLOT ( displayProflierWindow ( bool ) ) );
connect( m_windowTimerProfiler, SIGNAL(closeWindow(bool)), this->displayTimeProfiler, SLOT(setChecked(bool)));
#else
displayTimeProfiler->setEnabled(false);
#endif
}
void RealGUI::newRootNode(sofa::simulation::Node* root, const char* path)
{
const std::string filename(this->windowFilePath().toStdString());
const std::string message="You are about to change the root node of the scene : " + filename +
"to the root node : " + std::string(path) +
"\nThis implies that the simulation singleton has to change its root node.\nDo you want to proceed ?";
if ( QMessageBox::warning ( this, "New root node: ",message.c_str(), QMessageBox::Yes | QMessageBox::Default, QMessageBox::No ) != QMessageBox::Yes )
return;
if(path != nullptr && root != nullptr)
{
getViewer()->setScene(root , path);
getViewer()->load();
getViewer()->resetView();
getSofaViewer()->getQWidget()->update();
statWidget->CreateStats(root);
}
}
//------------------------------------
void RealGUI::activateNode(sofa::simulation::Node* node, bool activate)
{
const QSofaListView* sofalistview = (QSofaListView*)sender();
if (activate)
node->setActive(true);
simulation::DeactivationVisitor v(sofa::core::execparams::defaultInstance(), activate);
node->executeVisitor(&v);
using core::objectmodel::BaseNode;
std::list< BaseNode* > nodeToProcess;
nodeToProcess.push_front((BaseNode*)node);
std::list< BaseNode* > nodeToChange;
//Breadth First approach to activate all the nodes
while (!nodeToProcess.empty())
{
//We take the first element of the list
Node* n= (Node*)nodeToProcess.front();
nodeToProcess.pop_front();
nodeToChange.push_front(n);
//We add to the list of node to process all its children
for(Node::ChildIterator it = n->child.begin(), itend = n->child.end(); it != itend; ++it)
nodeToProcess.push_back(it->get());
}
const ActivationFunctor activator( activate, sofalistview->getListener() );
std::for_each(nodeToChange.begin(),nodeToChange.end(),activator);
nodeToChange.clear();
update();
if ( sofalistview == simulationGraph && activate )
{
if (node == currentSimulation())
{
sofa::simulation::node::initRoot(node);
}
else
{
sofa::simulation::node::init(node);
}
}
}
//------------------------------------
void RealGUI::setSleepingNode(sofa::simulation::Node* node, bool sleeping)
{
node->setSleeping(sleeping);
}
//------------------------------------
void RealGUI::lockAnimation(bool value)
{
if(value)
{
m_animationState = startButton->isChecked();
playpauseGUI(false);
}
else
{
playpauseGUI(m_animationState);
}
}
//------------------------------------
void RealGUI::fileRecentlyOpened(QAction *action)
{
//fileOpen(recentlyOpenedFilesManager.getFilename((unsigned int)id));
fileOpen(action->text().toStdString());
}
//------------------------------------
void RealGUI::playpauseGUI ( bool startSimulation )
{
startButton->setChecked ( startSimulation );
/// If there is no root node we do nothing.
Node* root = currentSimulation();
if (root==nullptr)
return;
/// Set the animation 'on' in the getContext()
currentSimulation()->getContext()->setAnimate ( startSimulation );
if(startSimulation)
{
SimulationStopEvent startEvt;
root->propagateEvent(core::execparams::defaultInstance(), &startEvt);
m_clockBeforeLastStep = 0;
timerStep->start(0);
return;
}
SimulationStartEvent stopEvt;
root->propagateEvent(core::execparams::defaultInstance(), &stopEvt);
timerStep->stop();
return;
}
//------------------------------------
void RealGUI::interactionGUI ( bool )
{
}
//------------------------------------
//called at each step of the rendering
void RealGUI::step()
{
SIMULATION_LOOP_SCOPE
sofa::helper::AdvancedTimer::begin("Animate");
Node* root = currentSimulation();
if ( root == nullptr ) return;
startDumpVisitor();
if ( !getViewer()->ready() ) return;
//root->setLogTime(true);
// If dt is zero, the actual value of dt will be taken from the graph.
double dt = 0.0;
if (realTimeCheckBox->isChecked() && startButton->isChecked())
{
const clock_t currentClock = clock();
// If animation has already started
if (m_clockBeforeLastStep != 0)
{
// then dt <- "time since last step"
dt = (double)(currentClock - m_clockBeforeLastStep) / CLOCKS_PER_SEC;
// dt = std::min(dt, dtEdit->text().toDouble());
}
m_clockBeforeLastStep = currentClock;
}
sofa::simulation::node::animate(root, dt);
sofa::simulation::node::updateVisual(root);
if ( m_dumpState )
sofa::simulation::node::dumpState ( root, *m_dumpStateStream );
if ( m_exportGnuplot )
exportGnuplot(root,gnuplotDirectory);
getViewer()->wait();
eventNewStep();
eventNewTime();
if ( m_animateOBJ )
{
#ifdef CAPTURE_PERIOD
static int counter = 0;
if ( ( counter++ % CAPTURE_PERIOD ) ==0 )
#endif
{
exportOBJ ( currentSimulation(), false );
++m_animationOBJcounter;
}
}
stopDumpVisitor();
emit newStep();
if ( !currentSimulation()->getContext()->getAnimate() )
startButton->setChecked ( false );
#if SOFA_GUI_QT_HAVE_QT_CHARTS
if (displayTimeProfiler->isChecked())
{
m_windowTimerProfiler->pushStepData();
}
#endif
sofa::helper::AdvancedTimer::end("Animate");
}
//------------------------------------
void RealGUI::updateDtEditState()
{
dtEdit->setEnabled(!realTimeCheckBox->isChecked());
}
void RealGUI::setDt(const QString& value)
{
const double dt = value.toDouble();
// Input is validated, but value may be 0 anywway, while it is being entered.
if (dt > 0.0)
currentSimulation()->getContext()->setDt(dt);
}
//------------------------------------
// Reset the simulation to t=0
void RealGUI::resetScene()
{
Node* root = currentSimulation();
startDumpVisitor();
emit ( newScene() );
if (root)
{
m_frameCounter=0;
sofa::simulation::node::reset(root);
eventNewTime();
emit newStep();
}
getViewer()->getPickHandler()->reset();
stopDumpVisitor();
}
//------------------------------------
void RealGUI::screenshot()
{
QString filename;
const bool pngSupport = helper::io::Image::FactoryImage::getInstance()->hasKey("png")
|| helper::io::Image::FactoryImage::getInstance()->hasKey("PNG");
const bool bmpSupport = helper::io::Image::FactoryImage::getInstance()->hasKey("bmp")
|| helper::io::Image::FactoryImage::getInstance()->hasKey("BMP");
if(!pngSupport && !bmpSupport)
{
QMessageBox::warning(this, tr("runSofa"),
tr("Screenshot is not available (PNG or BMP support not found).\n"),
QMessageBox::Cancel);
return;
}
std::string imageString = "Images (*.bmp)";
if(pngSupport)
imageString = "Images (*.png)";
filename = getSaveFileName ( this,
getViewer()->screenshotName().c_str(),
imageString.c_str(),
"save file dialog"
"Choose a filename to save under"
);
viewer::SofaViewer* sofaViewer = getSofaViewer();
if( sofaViewer )
sofaViewer->getQWidget()->repaint();
if ( filename != "" )
{
QString prefix;
const int end = filename.lastIndexOf('_');
if (end > -1) {
prefix = filename.mid(
0,
end+1
);
} else {
prefix = QString::fromStdString(
SetDirectory::GetFileNameWithoutExtension(filename.toStdString().c_str()) + "_");
}
if (!prefix.isEmpty())
getViewer()->setPrefix ( prefix.toStdString(), false );
getViewer()->screenshot ( filename.toStdString() );
}
}
//------------------------------------
void RealGUI::showhideElements()
{
displayFlag->updateDataValue();
getSofaViewer()->getQWidget()->update();
}
//------------------------------------
void RealGUI::update()
{
getSofaViewer()->getQWidget()->update();
statWidget->CreateStats(currentSimulation());
}
//------------------------------------
void RealGUI::updateBackgroundColour()
{
if(getViewer())
getViewer()->setBackgroundColour(background[0]->text().toFloat(),background[1]->text().toFloat(),background[2]->text().toFloat());
getSofaViewer()->getQWidget()->update();
}
//------------------------------------
void RealGUI::updateBackgroundImage()
{
if(getViewer())
getViewer()->setBackgroundImage( backgroundImage->text().toStdString() );
getSofaViewer()->getQWidget()->update();
}
//------------------------------------
void RealGUI::clear()
{
simulationGraph->setRoot(currentSimulation());
statWidget->CreateStats(currentSimulation());
}
//----------------------------------
void RealGUI::redraw()
{
emit newStep();
}
//------------------------------------
void RealGUI::exportOBJ (simulation::Node* root, bool exportMTL )
{
if ( !root ) return;
SCOPED_TIMER_VARNAME(exportOBJTimer, "exportOBJ");
const std::string sceneFileName(this->windowFilePath ().toStdString());
std::ostringstream ofilename;
if ( !sceneFileName.empty() )
{
const char* begin = sceneFileName.c_str();
const char* end = strrchr ( begin,'.' );
if ( !end ) end = begin + sceneFileName.length();
ofilename << std::string ( begin, end );
}
else
ofilename << "scene";
std::stringstream oss;
oss.width ( 5 );
oss.fill ( '0' );
oss << m_animationOBJcounter;
ofilename << '_' << ( oss.str().c_str() );
ofilename << ".obj";
const std::string filename = ofilename.str();
std::cout << "Exporting OBJ Scene "<<filename<<std::endl;
sofa::simulation::node::exportOBJ ( root, filename.c_str(),exportMTL );
}
//------------------------------------
void RealGUI::dumpState ( bool value )
{
m_dumpState = value;
if ( m_dumpState )
{
m_dumpStateStream = new std::ofstream ( "dumpState.data" );
}
else if ( m_dumpStateStream!=nullptr )
{
delete m_dumpStateStream;
m_dumpStateStream = 0;
}
}
//------------------------------------
void RealGUI::displayComputationTime ( bool value )
{
const Node* root = currentSimulation();
m_displayComputationTime = value;
if ( root )
{
if (value)
std::cout << "Activating Timer" << std::endl;
else
std::cout << "Deactivating Timer" << std::endl;
sofa::helper::AdvancedTimer::setEnabled("Animate", value);
}
}
//------------------------------------
void RealGUI::setExportGnuplot ( bool exp )
{
Node* root = currentSimulation();
m_exportGnuplot = exp;
if ( exp && root )
{
const sofa::core::ExecParams* params = sofa::core::execparams::defaultInstance();
InitGnuplotVisitor v(params , gnuplotDirectory);
root->execute( v );
exportGnuplot(root,gnuplotDirectory);
}
}
//------------------------------------
#ifdef SOFA_DUMP_VISITOR_INFO
void RealGUI::setExportVisitor ( bool exp )
{
if (exp)
{
windowTraceVisitor->show();
handleTraceVisitor->clear();
}
else
{
windowTraceVisitor->hide();
}
}
#else
void RealGUI::setExportVisitor ( bool )
{
}
#endif
void RealGUI::displayProflierWindow (bool value)
{
#if SOFA_GUI_QT_HAVE_QT_CHARTS
if (m_windowTimerProfiler == nullptr)
return;
m_windowTimerProfiler->activateATimer(value);
if (value)
m_windowTimerProfiler->show();
else
m_windowTimerProfiler->hide();
#else
SOFA_UNUSED(value);
#endif
}
//------------------------------------
void RealGUI::currentTabChanged ( int index )
{
QWidget* widget = tabs->widget(index);
if ( widget == currentTab ) return;
if ( currentTab == nullptr )
currentTab = widget;
if ( widget == TabGraph )
simulationGraph->update();
else if (widget == TabStats)
statWidget->CreateStats(currentSimulation());
currentTab = widget;
}
//------------------------------------
void RealGUI::changeViewer()
{
QObject* obj = const_cast<QObject*>( QObject::sender() );
if( !obj) return;
QAction* action = static_cast<QAction*>(obj);
action->setChecked(true);
std::map< helper::SofaViewerFactory::Key, QAction* >::const_iterator iter_map;
for ( iter_map = viewerMap.begin(); iter_map != viewerMap.end(); ++iter_map )
{
if ( iter_map->second == action )
{
this->unloadScene();
removeViewer();
createViewer(iter_map->first.c_str());
}
else
{
(*iter_map).second->setChecked(false);
}
}
// Reload the scene
const std::string filename(this->windowFilePath().toStdString());
fileOpen ( filename.c_str() ); // keep the current display flags
}
//------------------------------------
void RealGUI::updateViewerList()
{
// the current list of viewer key with associate QAction
type::vector< helper::SofaViewerFactory::Key > currentKeys;
std::map< helper::SofaViewerFactory::Key, QAction*>::const_iterator iter_map;
for ( iter_map = viewerMap.begin(); iter_map != viewerMap.end(); ++iter_map )
currentKeys.push_back((*iter_map).first);
std::sort(currentKeys.begin(),currentKeys.end());
// the new list (most recent since we load/unload viewer plugin)
type::vector< helper::SofaViewerFactory::Key > updatedKeys;
helper::SofaViewerFactory::getInstance()->uniqueKeys(std::back_inserter(updatedKeys));
std::sort(updatedKeys.begin(),updatedKeys.end());
type::vector< helper::SofaViewerFactory::Key > diffKeys;
std::set_symmetric_difference(currentKeys.begin(),
currentKeys.end(),
updatedKeys.begin(),
updatedKeys.end(),
std::back_inserter(diffKeys)
);
bool viewerRemoved=false;
type::vector< helper::SofaViewerFactory::Key >::const_iterator it;
for( it = diffKeys.begin(); it != diffKeys.end(); ++it)
{
// delete old
std::map< helper::SofaViewerFactory::Key, QAction* >::iterator itViewerMap;
if( (itViewerMap = viewerMap.find(*it)) != viewerMap.end() )
{
if( (*itViewerMap).second->isChecked() )
{
this->unloadScene();
removeViewer();
viewerRemoved = true;
}
//(*itViewerMap).second->disconnect(View);
View->removeAction( (*itViewerMap).second);
viewerMap.erase(itViewerMap);
}
else // add new
{
QAction* action = new QAction(this);
action->setText( helper::SofaViewerFactory::getInstance()->getViewerName(*it) );
//action->setMenuText( helper::SofaViewerFactory::getInstance()->getAcceleratedViewerName(*it) );
action->setToolTip( helper::SofaViewerFactory::getInstance()->getAcceleratedViewerName(*it) );
//action->setToggleAction(true);
action->setCheckable(true);
//action->addTo(View);
View->addAction(action);
viewerMap[*it] = action;
action->setEnabled(true);
connect(action, SIGNAL( triggered() ), this, SLOT( changeViewer() ) );
}
}
// if we unloaded a viewer plugin actually in use
if( viewerRemoved && !viewerMap.empty() )
{
createViewer(viewerMap.begin()->first.c_str());
viewerMap.begin()->second->setChecked(true);
}
}
void RealGUI::toolsDockMoved()
{
QDockWidget* dockWindow = qobject_cast<QDockWidget*>(sender());
if(!dockWindow)
return;
if(dockWindow->isFloating())
dockWindow->resize(500, 700);
}
void RealGUI::propertyDockMoved(Qt::DockWidgetArea /*a*/)
{
QDockWidget* dockWindow = qobject_cast<QDockWidget*>(sender());
if(!dockWindow)
return;
if(dockWindow->isFloating())
dockWindow->resize(500, 700);
}
namespace
{
std::string getFormattedLocalTimeFromTimestamp(time_t timestamp)
{
const tm *timeinfo = localtime(×tamp);
std::ostringstream oss;
oss << std::setfill('0')
<< std::setw(2) << timeinfo->tm_mday << "/" // Day
<< std::setw(2) << (timeinfo->tm_mon + 1) << "/" // Month
<< std::setw(4) << (1900 + timeinfo->tm_year) << " " // Year
<< std::setw(2) << timeinfo->tm_hour << ":" // Hours
<< std::setw(2) << timeinfo->tm_min << ":" // Minutes
<< std::setw(2) << timeinfo->tm_sec; // Seconds
return oss.str();
}
std::string getFormattedLocalTime()
{
return getFormattedLocalTimeFromTimestamp( time( nullptr ) );
}
} // namespace
//------------------------------------
void RealGUI::appendToDataLogFile(QString dataModifiedString)
{
const std::string filename = this->windowFilePath().toStdString() + std::string(".log");
std::ofstream ofs( filename.c_str(), std::ofstream::out | std::ofstream::app );
if (ofs.good())
{
if (m_modifiedLogFiles.find(filename) == m_modifiedLogFiles.end())
{
ofs << std::endl << "--- NEW SESSION: " << getFormattedLocalTime() << " ---" << std::endl;
m_modifiedLogFiles.insert(filename);
}
ofs << dataModifiedString.toStdString();
}
ofs.close();
}
//======================= SIGNALS-SLOTS ========================= }
} // namespace sofa::gui::qt