diff --git a/.gitsubprojects b/.gitsubprojects index 3a7d14af0b0b382a2230eb75d0fa332635bed47c..70e741d5bdcc37179b1ef17f7631eec2c3818512 100644 --- a/.gitsubprojects +++ b/.gitsubprojects @@ -4,3 +4,4 @@ # git_subproject(Lexis https://github.com/HBPVis/Lexis.git 617eedb) # git_subproject(gmrvlex git@gitlab.gmrv.es:nsviz/gmrvlex.git c20b194) git_subproject(neurolots https://github.com/gmrvvis/neurolots.git 2a1cfeb9) +git_subproject( acuterecorder https://github.com/vg-lab/AcuteRecorder.git 3fe7d5ba) diff --git a/CMakeLists.txt b/CMakeLists.txt index 13cffaddc54f041a8b0e0122363f1357bd376461..05fb8820fff43bac5ab9afaed00e20afd5f28023 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(neurotessmesh VERSION 0.1.1) +project(neurotessmesh VERSION 0.2.0) set(neurotessmesh_VERSION_ABI 1) # Disable in source building @@ -55,8 +55,9 @@ common_find_package(ZeroEQ ${NEUROTESSMESH_OPTS_FIND_ARGS}) common_find_package(gmrvlex ${NEUROTESSMESH_OPTS_FIND_ARGS}) common_find_package(GLUT SYSTEM) common_find_package(Boost COMPONENTS system filesystem SYSTEM) +common_find_package( acuterecorder REQUIRED ) -list(APPEND NEUROTESSMESH_DEPENDENT_LIBRARIES Qt5Core Qt5Widget Qt5OpenGL GLEW neurolots) +list(APPEND NEUROTESSMESH_DEPENDENT_LIBRARIES Qt5Core Qt5Widget Qt5OpenGL GLEW neurolots acuterecorder) if ( ZEROEQ_FOUND ) common_find_package(Threads REQUIRED) diff --git a/Changelog.md b/Changelog.md index 7b2b102b60490b78e3e0c24c85c84ae2de16e4a8..2c63357b74672ca83aef1be6b3e9495eb3c836aa 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,10 @@ ## git master +## version 0.2.0 + +* [!15] Added a recorder option, which allows users to record the application. + ## version 0.0.1 * [!3] Updated Changelog and version. Release 0.0.1 preparation. diff --git a/neurotessmesh/CMakeLists.txt b/neurotessmesh/CMakeLists.txt index e382ba7dc702f5eb5f40fb37b71535dbbaf7f42e..06293939f263035bc380924d6c35da35dbb1de8b 100644 --- a/neurotessmesh/CMakeLists.txt +++ b/neurotessmesh/CMakeLists.txt @@ -46,6 +46,7 @@ set( NEUROTESSMESH_LINK_LIBRARIES nlgeometry nlgenerator nlrender + acuterecorder ) diff --git a/neurotessmesh/ColorSelectionWidget.cpp b/neurotessmesh/ColorSelectionWidget.cpp index d201277af1a7267e62a74941aa6d2c34c75dcc6d..8f395ec1e4ce603a4651747dac87cba9993fa51b 100644 --- a/neurotessmesh/ColorSelectionWidget.cpp +++ b/neurotessmesh/ColorSelectionWidget.cpp @@ -43,6 +43,7 @@ void ColorSelectionWidget::mousePressEvent( QMouseEvent* /*event*/ ) { _color = newColor; emit this->colorChanged( _color ); + repaint(); } } @@ -50,4 +51,5 @@ void ColorSelectionWidget::color( const QColor& color_ ) { _color = color_; emit this->colorChanged( _color ); + repaint(); } diff --git a/neurotessmesh/MainWindow.cpp b/neurotessmesh/MainWindow.cpp index c87d85e22f1c96814c469f13f4a538e5b2b7f224..421e2cd9815cb050b86326db61aa8119fb133bf3 100644 --- a/neurotessmesh/MainWindow.cpp +++ b/neurotessmesh/MainWindow.cpp @@ -19,6 +19,8 @@ #include <deflect/version.h> #endif +#include <acuterecorder/acuterecorder.h> + #include <QFileDialog> #include <QInputDialog> #include <QMessageBox> @@ -30,6 +32,7 @@ MainWindow::MainWindow( QWidget* parent_, bool updateOnIdle_ ) , _lastOpenedFileName( "" ) , _ui( new Ui::MainWindow ) , _openGLWidget( nullptr ) + , _recorder( nullptr ) { _ui->setupUi( this ); @@ -138,11 +141,14 @@ void MainWindow::init( const std::string& zeqSession_ ) connect( _neuronRender, SIGNAL( currentIndexChanged( int )), _openGLWidget, SLOT( changeNeuronPiece( int ))); - _neuronRender->setCurrentIndex( 1 ); + _neuronRender->currentIndexChanged( 1 ); connect( _selectedNeuronRender, SIGNAL( currentIndexChanged( int )), _openGLWidget, SLOT( changeSelectedNeuronPiece( int ))); _selectedNeuronRender->currentIndexChanged( 0 ); + + connect( _ui->actionRecorder , SIGNAL( triggered( void )) , this , + SLOT( openRecorder( void ))); } void MainWindow::showStatusBarMessage ( const QString& message ) @@ -157,6 +163,8 @@ void MainWindow::openBlueConfig( const std::string& fileName, neurotessmesh::Scene::TDataFileType::BlueConfig, targetLabel ); updateNeuronList( ); + _openGLWidget->changeNeuronPiece(_neuronRender->currentIndex()); + _openGLWidget->changeSelectedNeuronPiece(_selectedNeuronRender->currentIndex()); } void MainWindow::openXMLScene( const std::string& fileName ) @@ -164,6 +172,8 @@ void MainWindow::openXMLScene( const std::string& fileName ) _openGLWidget->loadData( fileName, neurotessmesh::Scene::TDataFileType::NsolScene ); updateNeuronList( ); + _openGLWidget->changeNeuronPiece(_neuronRender->currentIndex()); + _openGLWidget->changeSelectedNeuronPiece(_selectedNeuronRender->currentIndex()); } void MainWindow::openSWCFile( const std::string& fileName ) @@ -171,6 +181,8 @@ void MainWindow::openSWCFile( const std::string& fileName ) _openGLWidget->loadData( fileName, neurotessmesh::Scene::TDataFileType::SWC ); updateNeuronList( ); + _openGLWidget->changeNeuronPiece(_neuronRender->currentIndex()); + _openGLWidget->changeSelectedNeuronPiece(_selectedNeuronRender->currentIndex()); } void MainWindow::updateNeuronList( void ) @@ -308,13 +320,58 @@ void MainWindow::showAbout( void ) "<a href=www.gmrv.es>www.gmrv.es</a><br>" "<a href='mailto:gmrv@gmrv.es'>gmrv@gmrv.es</a><br><br>" "<br>(C) 2015. Universidad Rey Juan Carlos<br><br>" - "<img src=':/icons/gmrv_grande.png' > " - "<img src=':/icons/logoURJC.png' ><br><br> " + "<img src=':/icons/rsc/gmrv_grande.png' > " + "<img src=':/icons/rsc/logoURJC.png' ><br><br> " "</p>" "") ); } +void MainWindow::openRecorder( void ) +{ + + // The button stops the recorder if found. + if( _recorder != nullptr ) + { + _ui->actionRecorder->setDisabled( true ); + _recorder->stop(); + + // Recorder will be deleted after finishing. + _recorder = nullptr; + _ui->actionRecorder->setChecked( false ); + return; + } + + RSWParameters params; + params.widgetsToRecord.emplace_back( "Viewport" , _openGLWidget ); + params.widgetsToRecord.emplace_back( "Main Widget" , this ); + params.includeScreens = false; + + if(!_ui->actionAdvancedRecorderOptions->isChecked()) + { + params.showWorker = false; + params.showWidgetSourceMode = false; + params.showSourceParameters = false; + } + + auto dialog = new RecorderDialog( nullptr , params , true ); + dialog->setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + dialog->setFixedSize( 800 , 600 ); + if ( dialog->exec( ) == QDialog::Accepted) + { + _recorder = dialog->getRecorder( ); + connect( _recorder , SIGNAL( finished( )) , + _recorder , SLOT( deleteLater( ))); + connect( _recorder , SIGNAL( finished( )) , + this , SLOT( finishRecording( ))); + _ui->actionRecorder->setChecked( true ); + } else + { + _ui->actionRecorder->setChecked( false ); + } + dialog->deleteLater( ); +} + void MainWindow::updateExtractMeshDock( void ) { if( _ui->actionEditSave->isChecked( )) @@ -364,6 +421,11 @@ void MainWindow::onActionGenerate( int /*value_*/ ) _openGLWidget->regenerateNeuronToEdit( alphaRadius, alphaNeurites ); } +void MainWindow::finishRecording( ) +{ + _ui->actionRecorder->setEnabled( true ); +} + void MainWindow::_generateNeuritesLayout( void ) { unsigned int numDendrites = _openGLWidget->numNeuritesToEdit( ); diff --git a/neurotessmesh/MainWindow.h b/neurotessmesh/MainWindow.h index 4143c1e64db88f800d9637d37fe8ed37553201e0..6b8f8f4f0c1997cc4f28a71b3a174b04746efe3d 100644 --- a/neurotessmesh/MainWindow.h +++ b/neurotessmesh/MainWindow.h @@ -27,6 +27,8 @@ namespace Ui class MainWindow; } +class Recorder; + class MainWindow : public QMainWindow { @@ -56,6 +58,7 @@ public slots: void openXMLSceneThroughDialog( void ); void openSWCFileThroughDialog( void ); void showAbout( void ); + void openRecorder( void ); void updateExtractMeshDock( void ); void updateConfigurationDock( void ); @@ -63,6 +66,10 @@ public slots: void onListClicked( QListWidgetItem *item ); void onActionGenerate( int value_ ); +protected slots: + + void finishRecording( ); + protected: QString _lastOpenedFileName; @@ -100,4 +107,7 @@ private: QComboBox* _neuronRender; QComboBox* _selectedNeuronRender; + + // Recorder + Recorder* _recorder; }; diff --git a/neurotessmesh/OpenGLWidget.cpp b/neurotessmesh/OpenGLWidget.cpp index 8d6d6e25492c194a4a3afe494d265153d3dbbe92..5ff19dde75af751a789bd013f9e21e924d74e617 100644 --- a/neurotessmesh/OpenGLWidget.cpp +++ b/neurotessmesh/OpenGLWidget.cpp @@ -13,6 +13,7 @@ #include <QMouseEvent> #include <QColorDialog> #include <QFileDialog> +#include <QMessageBox> #include <sstream> #include <string> #include <iostream> @@ -67,13 +68,29 @@ OpenGLWidget::~OpenGLWidget( void ) delete _scene; } -void OpenGLWidget::loadData( +bool OpenGLWidget::loadData( const std::string& fileName_, const neurotessmesh::Scene::TDataFileType fileType_, const std::string& target_ ) { makeCurrent( ); - _scene->loadData( fileName_, fileType_, target_ ); + const auto errorString = _scene->loadData( fileName_, fileType_, target_ ); + + if(!errorString.empty()) + { + const QString message = QString("Error loading file '%1'.\n%2") + .arg(QString::fromStdString(fileName_)) + .arg(QString::fromStdString(errorString)); + + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Error Loading Data")); + msgBox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); + msgBox.setText(message); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + } + + return errorString.empty(); } void OpenGLWidget::idleUpdate( bool idleUpdate_ ) @@ -92,7 +109,6 @@ void OpenGLWidget::neuronToEdit( const unsigned int id_ ) update( ); } - unsigned int OpenGLWidget::numNeuritesToEdit( void ) const { return _scene->numEditMorphologyNeurites( ); @@ -366,7 +382,6 @@ void OpenGLWidget::initializeGL( void ) void OpenGLWidget::paintGL( void ) { - makeCurrent( ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); diff --git a/neurotessmesh/OpenGLWidget.h b/neurotessmesh/OpenGLWidget.h index 2d3693beb59d6bef6f646cb45b8e2e81b1b3e4e3..e92bcbe4a90313e9fd1a71fc5749185932ed712f 100644 --- a/neurotessmesh/OpenGLWidget.h +++ b/neurotessmesh/OpenGLWidget.h @@ -44,7 +44,13 @@ public: ~OpenGLWidget( void ); - void loadData( const std::string& fileName_, + /** \brief Loads a dataset with the given parameters. + * \param[in] fileName_ Dataset filename. + * \param[in] fileType_ Dataset type. + * \param[in] target_ BlueConfig target if Blueconfig or empty otherwise. + * + */ + bool loadData( const std::string& fileName_, const neurotessmesh::Scene::TDataFileType fileType_ = neurotessmesh::Scene::TDataFileType::BlueConfig, const std::string& target_ = std::string( "" )); diff --git a/neurotessmesh/Scene.cpp b/neurotessmesh/Scene.cpp index 9eef698572201a85c1f2eb3e6a035a419d8b0c3f..b8b0cf32a851e6b594069f801a18e35552769e87 100644 --- a/neurotessmesh/Scene.cpp +++ b/neurotessmesh/Scene.cpp @@ -186,7 +186,7 @@ namespace neurotessmesh } } - void Scene::loadData( const std::string& fileName_, + std::string Scene::loadData( const std::string& fileName_, const TDataFileType fileType_, #ifdef NSOL_USE_BRION const std::string& target_ @@ -196,6 +196,7 @@ namespace neurotessmesh ) { close( ); + std::string errorString; try{ switch( fileType_ ) { @@ -254,12 +255,16 @@ namespace neurotessmesh { std::cerr << "Error: can't load file: " << fileName_ << std::endl; std::cerr << excep.what( ) << std::endl; + errorString = std::string(excep.what()); } + generateMeshes( ); _boundingBox = computeBoundingBox( ); _camera->position( _boundingBox.center( )); _camera->radius( _boundingBox.radius( ) / sin( _camera->camera()->fieldOfView())); conformRenderTuples( ); + + return errorString; } void Scene::paintUnselectedSoma( bool paint_ ) diff --git a/neurotessmesh/Scene.h b/neurotessmesh/Scene.h index 4f54e95d83738ef5be1c02be08b81146d57adcbc..389491335a8f6a38c4713918e0920f5dce4a05a6 100644 --- a/neurotessmesh/Scene.h +++ b/neurotessmesh/Scene.h @@ -121,13 +121,14 @@ namespace neurotessmesh void generateMeshes( void ); /** - * Method to load neurons data + * Method to load neurons data returns an empty string on success + * or an error string otherwise. * @param fileName path to the file * @param fileType type of file * @param target to load, specific param to BlueConfig data */ NEUROTESSMESH_API - void loadData( const std::string& fileName_, + std::string loadData( const std::string& fileName_, const TDataFileType fileType_, const std::string& target_ = std::string( "" )); diff --git a/neurotessmesh/mainwindow.ui b/neurotessmesh/mainwindow.ui index 5108201b0d763bdb2ed28abcbd0419f24d28756e..0b1b264c4b2d480a68f2d23ac7e68c62b5f340c8 100644 --- a/neurotessmesh/mainwindow.ui +++ b/neurotessmesh/mainwindow.ui @@ -55,6 +55,9 @@ <addaction name="actionRenderOptions"/> <addaction name="actionEditSave"/> <addaction name="actionConfiguration"/> + <addaction name="separator"/> + <addaction name="actionRecorder"/> + <addaction name="actionAdvancedRecorderOptions"/> </widget> <addaction name="menuFile"/> <addaction name="menuOptions"/> @@ -81,6 +84,7 @@ <addaction name="actionWireframe"/> <addaction name="actionCloseData"/> <addaction name="actionRenderOptions"/> + <addaction name="actionRecorder"/> </widget> <action name="actionQuit"> <property name="text"> @@ -279,6 +283,32 @@ <string>Ctrl+C</string> </property> </action> + <action name="actionRecorder"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/rsc/recorder.svg</normaloff>:/icons/rsc/recorder.svg</iconset> + </property> + <property name="text"> + <string>Recorder</string> + </property> + <property name="shortcut"> + <string>Ctrl+R</string> + </property> + </action> + <action name="actionAdvancedRecorderOptions"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Advanced recorder options</string> + </property> + <property name="toolTip"> + <string>Enable advanced recorder options</string> + </property> + </action> </widget> <resources> <include location="resources.qrc"/> diff --git a/neurotessmesh/neurotessmesh.cpp b/neurotessmesh/neurotessmesh.cpp index 26d970cf28db9c3ef35ae2242fc326d2bd2842e1..bc39a29a13c02f9230378d15107113b398185062 100644 --- a/neurotessmesh/neurotessmesh.cpp +++ b/neurotessmesh/neurotessmesh.cpp @@ -47,7 +47,7 @@ int main( int argc, char** argv ) std::string zeqUri; std::string target = std::string( "" ); bool fullscreen = false, initWindowSize = false, initWindowMaximized = false; - int initWindowWidth, initWindowHeight; + int initWindowWidth = 0, initWindowHeight = 0; int ctxOpenGLMajor = DEFAULT_CONTEXT_OPENGL_MAJOR; @@ -192,13 +192,13 @@ int main( int argc, char** argv ) usageMessage( argv[0] ); } - if ( blueConfig != "" ) + if ( !blueConfig.empty() ) mainWindow->openBlueConfig( blueConfig, target ); - if ( swcFile != "" ) + if ( !swcFile.empty() ) mainWindow->openSWCFile( swcFile ); - if ( sceneFile != "" ) + if ( !sceneFile.empty() ) mainWindow->openXMLScene( sceneFile ); } else diff --git a/neurotessmesh/resources.qrc b/neurotessmesh/resources.qrc index 3a7654af86b54d1b5cdc02f47ea6fc19ef068bcf..8f7fe800cc52b4f991a21f51650cd3a52a09e50f 100644 --- a/neurotessmesh/resources.qrc +++ b/neurotessmesh/resources.qrc @@ -12,5 +12,7 @@ <file>rsc/reconstruction.png</file> <file>rsc/home.png</file> <file>rsc/settings.png</file> + <file>rsc/recorder.svg</file> + <file>rsc/neurotessmesh.png</file> </qresource> </RCC> diff --git a/neurotessmesh/rsc/recorder.svg b/neurotessmesh/rsc/recorder.svg new file mode 100644 index 0000000000000000000000000000000000000000..947269bc99779555e5d921b191df4d5d69a1b7e2 --- /dev/null +++ b/neurotessmesh/rsc/recorder.svg @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="123.49702mm" + height="123.49702mm" + viewBox="0 0 123.49705 123.49705" + version="1.1" + id="svg5" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="recorder.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#505050" + bordercolor="#eeeeee" + borderopacity="1" + inkscape:pageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="0" + inkscape:document-units="mm" + showgrid="false" + fit-margin-top="15" + fit-margin-left="15" + fit-margin-right="15" + fit-margin-bottom="15" + lock-margins="false" + width="107.89702mm" + inkscape:zoom="0.74029882" + inkscape:cx="-127.65116" + inkscape:cy="226.93539" + inkscape:window-width="1920" + inkscape:window-height="1013" + inkscape:window-x="2560" + inkscape:window-y="1440" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs2"> + <filter + style="color-interpolation-filters:sRGB" + inkscape:label="Blur" + id="filter721" + x="-0.077007798" + y="-0.077007798" + width="1.1540156" + height="1.1540156"> + <feGaussianBlur + stdDeviation="3 3" + result="blur" + id="feGaussianBlur719" /> + </filter> + </defs> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-38.877594,-60.209427)"> + <circle + style="fill:#2b0000;fill-rule:evenodd;stroke-width:0.264583;filter:url(#filter721)" + id="circle413" + cx="106.41572" + cy="129.05154" + r="46.748512" + transform="matrix(0.80142274,0,0,0.80142274,21.131741,25.626701)" /> + <circle + style="fill:#aa0000;fill-rule:evenodd;stroke-width:0.212043" + id="path129" + cx="101.77579" + cy="123.36657" + r="37.465321" /> + </g> +</svg>