diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d0d443..3ed9742 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.5) project(LabRecorder - LANGUAGES CXX + LANGUAGES CXX VERSION 1.12.0) -# load LSLAppBoilerplate if not done already +# set up LSL if not done already if(NOT TARGET LSL::lsl) # when building out of tree LSL_ROOT needs to be specified on the cmd line file(TO_CMAKE_PATH "${LSL_INSTALL_ROOT}" LSL_INSTALL_ROOT) @@ -19,7 +19,6 @@ if(NOT TARGET LSL::lsl) endif() # GENERAL CONFIG # -# Meta information about the project set(META_PROJECT_DESCRIPTION "Record LabStreamingLayer streams to XDF data file.") # THIRD PARTY LIBRARIES # @@ -41,39 +40,36 @@ add_executable(${target} recording.h recording.cpp ) -target_compile_features(${target} PRIVATE cxx_auto_type) +target_compile_features(${target} PRIVATE cxx_auto_type cxx_lambda_init_captures) find_package(Threads REQUIRED) -find_package(Boost REQUIRED COMPONENTS filesystem iostreams) -set(zlib_libs "") +find_package(Boost REQUIRED OPTIONAL_COMPONENTS iostreams) if(WIN32) find_package(Boost OPTIONAL_COMPONENTS zlib QUIET) if(TARGET Boost::zlib) - set(zlib_libs "Boost::zlib") - message(STATUS "LabRecorder: Using Boost.zlib for xdfz support") - + set(ZLIB_LIBRARIES Boost::zlib) endif() else() find_package(ZLIB QUIET) - if(ZLIB_FOUND) - message(STATUS "LabRecorder: Using zlib for xdfz support") - set(zlib_libs ${ZLIB_LIBRARIES}) - endif() endif() target_link_libraries(${target} PRIVATE Qt5::Widgets Qt5::Network - Boost::filesystem - Boost::iostreams + Boost::boost Threads::Threads - ${zlib_libs} LSL::lsl ) # Enable xdfz support if Boost.zlib (Windows) or plain zlib (Unix) was found -if(${zlib_libs}) +if(ZLIB_LIBRARIES) + message(STATUS "Found zlib, enabling support for xdfz files") + target_link_libraries(${target} + PRIVATE + ${ZLIB_LIBRARIES} + Boost::iostreams + ) target_compile_definitions(${target} PRIVATE XDFZ_SUPPORT=1) endif() diff --git a/default_config.cfg b/default_config.cfg index 767bad1..ef8245a 100644 --- a/default_config.cfg +++ b/default_config.cfg @@ -1,96 +1,32 @@ -# === Storage Location === -# the default file name can be something like C:\\Recordings\\untitled.xdf, but can also contain a -# placeholder for a running number (incremented per experiment session) called %n, and a -# placeholder for a "block" label %b (if the config script provides a list of block names that -# consitute a session -# The syntax is as in: StorageLocation = "C:\\Recordings\\subject%n\\block_%b.xdf" - -StorageLocation=C:\Recordings\CurrentStudy\exp%n\untitled.xdf - -#OnlineSync=["ActiChamp-0 (User-PC)" post_ALL] - -# === Required Streams === -# This is optionally a list of streams that are required for the recording; -# a warning is issued if one of the streams is not present when the record button is pressed -# The syntax is as in: RequiredStreams = ["BioSemi (MyHostname)","PhaseSpace (MyHostname)","Eyelink (AnotherHostname)"] -# where the format is identical to what the LabRecorder displays in the "Record from streams" list. - -RequiredStreams=[] -OnlineSync=[] -#OnlineSync=[ActiChamp-0 (DM-Laptop) post_ALL, LiveAmpSN-054211-0237 (User-PC) post_ALL ] - -# === Block Names === -# This is optionally a list of blocks that make up a recording session. The blocks are displayed in -# a list box where the experiment can select a block before pressing record. If used, the blocks -# may serve as a reminder of where they are in the experiment, but more practically, can be -# used to determine the file name of the recording. Power users can define scriptable actions -# associated with selecting a block or pressing Start/Stop for a given block (e.g., for remote -# control). -# The syntax is as in: SessionBlocks = [Training,PreBaseline,MainSection,PostBaseline] - -SessionBlocks=[] - -# From here on, none of the following is currently implemented. -# It stays in case LabRecorder goes back to its previous python implementation, or -# it a Lua interpreter ever gets rolled into the current C++ implementation. - -# === Extra checks to apply to some of the streams === -# Note that this is an optional advanced feature that is aimed at power users. -# For a subset of streams, a list of [condition,errormessage,condition,errormessage, ...] can be -# given to inform the experimenter of possible problems with the recording setup (e.g., a device -# was mis-configured); the syntax of the condition strings is XPath 1.0 (applied to the meta-data -# of the stream) and the overall format for the ExtraCondtions variable is that of a Python -# dictionary. -# See sample_config.cfg for an example of the syntax - -ExtraChecks={} - - - -# === Optional script actions === -# Functions can be defined here that get called when the given action in the Experimenter GUI -# is triggered. For example, thi can be used to remote-control an experiment program on the -# Subject's PC. If the function throws an exception, an warning message is presented to the -# user to alert him of a possible problem. The user can choose to continue recording anyway. -# The script actions are implemented as Python functions; they may refer to the previously -# declared config variables. These functions will only be triggered when EnableScriptedActions -# is set to True. - -# set this to true to enable the below remote-control scripts -EnableScriptedActions = False -# you also need to set the correct hostname/IP address that hosts your experiment program -#SNAPHost = ("localhost",7897) - -#def on_init(self): -# print "Intitializing..." - -#def on_selectblock(self,blockname): -# print "Loading block ", blockname, " in SNAP." -# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# sock.settimeout(3) -# sock.connect(self.SNAPHost) -# sock.sendall("config " + blockname + "\n") -# sock.close() - -#def on_startrecord(self,blockname,sessionnumber): -# print "Starting block in SNAP." -# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# sock.settimeout(3) -# sock.connect(self.SNAPHost) -# sock.sendall("setup permutation = " + str(sessionnumber) + "\n") -# sock.sendall("start\n") -# sock.close() - -#def on_stoprecord(self,blockname,sessionnumber): -# print "Stopping block in SNAP." -# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# sock.settimeout(3) -# sock.connect(self.SNAPHost) -# sock.sendall("stop\n") -# sock.close() - -#def on_pauserecord(self,blockname,sessionnumber): -# pass - -#def on_quit(self): -# pass +; === Storage Location === +; the default file name can be something like C:\\Recordings\\untitled.xdf, but can also contain a +; placeholder for a running number (incremented per experiment session) called %n, and a +; placeholder for a "block" label %b (if the config script provides a list of block names that +; consitute a session +; The syntax is as in: StorageLocation = "C:\\Recordings\\subject%n\\block_%b.xdf" + +StorageLocation=C:/Recordings/CurrentStudy/exp%n/untitled.xdf + +;OnlineSync=["ActiChamp-0 (User-PC)" post_ALL] + +; === Required Streams === +; This is optionally a list of streams that are required for the recording; +; a warning is issued if one of the streams is not present when the record button is pressed +; The syntax is as in: RequiredStreams = "BioSemi (MyHostname)","PhaseSpace (MyHostname)","Eyelink (AnotherHostname)" +; where the format is identical to what the LabRecorder displays in the "Record from streams" list. + +RequiredStreams= +SessionBlocks="T1", "T2", "T3" + + +; === Block Names === +; This is optionally a list of blocks that make up a recording session. The blocks are displayed in +; a list box where the experiment can select a block before pressing record. If used, the blocks +; may serve as a reminder of where they are in the experiment, but more practically, can be +; used to determine the file name of the recording. Power users can define scriptable actions +; associated with selecting a block or pressing Start/Stop for a given block (e.g., for remote +; control). +; The syntax is as in: SessionBlocks = "Training","PreBaseline","MainSection","PostBaseline" + +OnlineSync="SendDataC (Testpc) post_ALL", "Test (Testpc) post_clocksync" +; OnlineSync="ActiChamp-0 (DM-Laptop) post_ALL", "LiveAmpSN-054211-0237 (User-PC) post_ALL" diff --git a/fptest.cpp b/fptest.cpp new file mode 100644 index 0000000..6bb0aa6 --- /dev/null +++ b/fptest.cpp @@ -0,0 +1,23 @@ +#include + +int main() { + const double sampling_rate = 500, + sample_interval = 1.0 / sampling_rate, + first_timestamp = 60*60*24*365*48; // e.g. a unix timestamp + double last_timestamp = first_timestamp; + int full = 0, deduced = 0; + const int iterations = 100000; + double timestamps[iterations]; + for(int i=0;i - #include #include #include -#include -#include -#include -#include +#include #include #include #include @@ -26,14 +21,14 @@ ui(new Ui::MainWindow) { connect(ui->actionQuit, &QAction::triggered, this, &QMainWindow::close); connect(ui->actionLoadConfig, &QAction::triggered, [&](){ QString sel = QFileDialog::getOpenFileName(this,"Load Configuration File","","Configuration Files (*.cfg)"); - if (!sel.isEmpty()) load_config(sel.toStdString()); + if (!sel.isEmpty()) load_config(sel); }); connect(ui->browseButton, &QPushButton::clicked, [&](){ QString sel = QFileDialog::getSaveFileName(this,"Save recordings as...", "untitled.xdf", "XDF recordings (*.xdf);;XDF compressed recordings (*.xdfz)"); if (!sel.isEmpty()) ui->locationEdit->setText(sel); }); - //QObject::connect(ui->blockList, &QListWidget::itemClicked, QMainWindow::blockSelected); + connect(ui->blockList, static_cast(&QComboBox::activated), this, &MainWindow::blockSelected); connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::refreshStreams); connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::startRecording); connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::stopRecording); @@ -43,7 +38,7 @@ ui(new Ui::MainWindow) { QMessageBox::about(this, "About LabRecorder", infostr); }); - load_config(config_file); + load_config(config_file.c_str()); timer.reset(new QTimer(this)); connect(&*timer, &QTimer::timeout, this, &MainWindow::statusUpdate); @@ -55,13 +50,13 @@ ui(new Ui::MainWindow) { void MainWindow::statusUpdate() const { if(currentRecording) { auto elapsed = static_cast(lsl::local_clock() - startTime); - - std::ifstream in(recFilename, std::ifstream::ate | std::ifstream::binary); - auto size = in.tellg(); - QString timeString = QStringLiteral("Recording (%1); %2kb)").arg( + auto fileinfo = QFileInfo(recFilename); + fileinfo.refresh(); + auto size = fileinfo.size(); + QString timeString = QStringLiteral("Recording to %1 (%2); %3kb)").arg( + fileinfo.fileName(), QDateTime::fromTime_t(elapsed).toUTC().toString("hh:mm:ss"), QString::number(size / 1000)); - statusBar()->showMessage(timeString); } } @@ -71,123 +66,74 @@ void MainWindow::closeEvent(QCloseEvent *ev) { ev->ignore(); } -void MainWindow::blockSelected(QListWidgetItem *item) { +void MainWindow::blockSelected(const QString& block) { if(currentRecording) QMessageBox::information(this, "Still recording", "Please stop recording before switching blocks.", QMessageBox::Ok); else { - currentBlock = item->text().toStdString(); - + currentBlock = block; // scripted action code here... - } //std::cout << item->text().toStdString() <("RequiredStreams",""); - if(str_requiredStreams != "[]") { - std::string rs_substr = str_requiredStreams.substr(1, str_requiredStreams.size()-2); - boost::algorithm::split(requiredStreams,rs_substr,boost::algorithm::is_any_of(","),boost::algorithm::token_compress_on); - for (auto& requiredStream: requiredStreams){ - boost::algorithm::trim_if(requiredStream,boost::algorithm::is_any_of(" '\"")); - std::cout << requiredStream << std::endl; - } - } + // ---------------------------- + requiredStreams = pt.value("RequiredStreams").toStringList(); // ---------------------------- // online sync streams - // ---------------------------- - std::string str_onlineSyncStreams = pt.get("OnlineSync",""); - if(str_onlineSyncStreams != "[]") { - std::string oss_substr = str_onlineSyncStreams.substr(1, str_onlineSyncStreams.size()-2); - boost::algorithm::split(onlineSyncStreams,oss_substr,boost::algorithm::is_any_of(","),boost::algorithm::token_compress_on); - for(std::string& oss: onlineSyncStreams) { - boost::algorithm::trim_if(oss, boost::algorithm::is_any_of(" '\"")); - std::vectorwords; - boost::algorithm::split(words, oss, boost::algorithm::is_any_of(" "), boost::algorithm::token_compress_on); - - std::string key = std::string(words[0] + " " + words[1]); - - int val = 0; - for (std::size_t l = 2; l < words.size(); l++) { - if (words[l] == "post_clocksync") { val |= lsl::post_clocksync; } - if (words[l] == "post_dejitter") { val |= lsl::post_dejitter; } - if (words[l] == "post_monotonize") { val |= lsl::post_monotonize; } - if (words[l] == "post_threadsafe") { val |= lsl::post_threadsafe; } - if (words[l] == "post_ALL") { val = lsl::post_ALL; } - } - syncOptionsByStreamName.insert(std::make_pair(key, val)); - std::cout << "key = " << key << std::endl; - std::cout << "val = " << val << std::endl; + // ---------------------------- + QStringList onlineSyncStreams = pt.value("OnlineSync", "").toStringList(); + for(QString& oss: onlineSyncStreams) { + QStringList words = oss.split(' ', QString::SkipEmptyParts); + // The first two words ("StreamName (PC)") are the stream identifier + QString key = words.takeFirst() + ' ' + words.takeFirst(); + + int val = 0; + for (auto word: words) { + if (word == "post_clocksync") { val |= lsl::post_clocksync; } + if (word == "post_dejitter") { val |= lsl::post_dejitter; } + if (word == "post_monotonize") { val |= lsl::post_monotonize; } + if (word == "post_threadsafe") { val |= lsl::post_threadsafe; } + if (word == "post_ALL") { val = lsl::post_ALL; } } - + syncOptionsByStreamName[key.toStdString()] = val; + std::cout << "key = " << key.toStdString() << std::endl; + std::cout << "val = " << val << std::endl; } + // ---------------------------- // recording location // ---------------------------- - std::vectorsessionBlocks; - std::string str_sessionBlocks = pt.get("SessionBlocks",""); - if(str_sessionBlocks != "[]") { - std::string sb_substr = str_sessionBlocks.substr(1, str_sessionBlocks.size()-2); - boost::algorithm::split(sessionBlocks,sb_substr,boost::algorithm::is_any_of(","),boost::algorithm::token_compress_on); - - bool selected = false; - for (auto& sessionBlock: sessionBlocks) { - boost::algorithm::trim_if(sessionBlock, boost::algorithm::is_any_of(" '\"")); - QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(sessionBlock), ui->blockList); - ui->blockList->addItem(item); - if(!selected) { - item->setSelected(true); - blockSelected(item); - selected = true; - } - } - } + ui->blockList->addItems(pt.value("SessionBlocks").toStringList()); + if(ui->blockList->count()) + currentBlock = ui->blockList->itemText(0); // get the path as a string - #ifdef win32 - const char* defaultpath = "C:\\Recordings\\CurrentStudy\\exp%n\\untitled.xdf"; - #else //win32 - const char* defaultpath = "exp%n/untitled.xdf"; - #endif //win32 - std::string str_path = pt.get("StorageLocation", defaultpath); - ui->locationEdit->setText(str_path.c_str()); - - // scan the path for %n - std::size_t pos_n = str_path.find("%n"); - - // variables for string/path parsing - std::string abs_path; - boost::filesystem::path p; - - if (pos_n != std::string::npos) - { - abs_path.append(str_path.begin(), str_path.begin() + pos_n); - // find the last value fo %n in the directories - for (int i = 1; i < 10000; i++) - { - p = abs_path + std::to_string(i); // build the path name - if (!boost::filesystem::exists(p)) { + QString str_path = pt.value("StorageLocation", "C:/Recordings/CurrentStudy/exp%n/untitled.xdf").toString(); + ui->locationEdit->setText(str_path); + + // replace %n as experiment number placeholder + int pos_n = str_path.indexOf(QStringLiteral("%n")); + if (pos_n != -1) { + str_path[pos_n + 1] = '1'; + for (int i = 1; i < 10000; i++) { + if (!QDir(str_path.arg(i)).exists()) { // update gui ui->experimentNumberSpin->setValue(i); - - break; // check for it + break; } } } - } catch(std::exception &e) { std::cout << "Problem parsing config file: " << e.what() << std::endl; } @@ -195,72 +141,60 @@ void MainWindow::load_config(const std::string &filename) { refreshStreams(); } +void MainWindow::save_config(QString filename) +{ + QSettings settings(filename, QSettings::Format::IniFormat); + settings.setValue("StorageLocation", ui->locationEdit->text()); + // Stub. +} void MainWindow::refreshStreams() { //std::cout << "refreshing streams ..." < streamNames; - + QStringList streamNames; for(auto& s: resolvedStreams) - streamNames.push_back(s.name()+ " (" + s.hostname()+")"); + streamNames.push_back(QString::fromStdString(s.name()+ " (" + s.hostname()+")")); - // add code to sort the streams here? - std::sort(streamNames.begin(), streamNames.end()); - + streamNames.sort(); missingStreams.clear(); - for(const auto& requiredStream: requiredStreams) { - if (requiredStream.empty()) continue; - auto it = std::find(streamNames.cbegin(), streamNames.cend(), requiredStream); - if (it == streamNames.cend()) + for(const auto& requiredStream: requiredStreams) + if (!streamNames.contains(requiredStream)) missingStreams.push_back(requiredStream); // push this string onto the missing vector - } - - std::sort(missingStreams.begin(), missingStreams.end()); - + missingStreams.sort(); QBrush good_brush, bad_brush; good_brush.setColor(QColor(0,128,0)); bad_brush.setColor(QColor(255,0,0)); - std::vector previouslyChecked; - QListWidgetItem *item; + QStringList previouslyChecked; for(int i=0;istreamList->count();i++) { - item=ui->streamList->item(i); - if(std::find(streamNames.begin(), streamNames.end(), item->text().toStdString())!=streamNames.end()) { - if(item->checkState() == Qt::Checked) - previouslyChecked.push_back(item->text().toStdString()); - } + QListWidgetItem* item = ui->streamList->item(i); + if(!streamNames.contains(item->text()) && item->checkState() == Qt::Checked) + previouslyChecked.push_back(item->text()); } - ui->streamList->clear(); for(auto&& streamName: streamNames) { - item=new QListWidgetItem(QString::fromStdString(streamName), ui->streamList); + QListWidgetItem* item = new QListWidgetItem(streamName, ui->streamList); item->setForeground(good_brush); item->setCheckState(Qt::Unchecked); - if(std::find(previouslyChecked.begin(), previouslyChecked.end(), item->text().toStdString())!=previouslyChecked.end()) - item->setCheckState(Qt::Checked); - - if(std::find(requiredStreams.begin(), requiredStreams.end(), item->text().toStdString())!=requiredStreams.end()) + if(previouslyChecked.contains(streamName) || requiredStreams.contains(streamName)) item->setCheckState(Qt::Checked); - - ui->streamList->addItem(item); + ui->streamList->addItem(item); } for(auto&& missingStream: missingStreams) { - item=new QListWidgetItem(QString::fromStdString(missingStream), ui->streamList); + QListWidgetItem* item = new QListWidgetItem(missingStream, ui->streamList); item->setForeground(bad_brush); item->setCheckState(Qt::Checked); - ui->streamList->addItem(item); + ui->streamList->addItem(item); } - - } void MainWindow::startRecording() { @@ -274,9 +208,7 @@ void MainWindow::startRecording() { QListWidgetItem *item = ui->streamList->item(i); // if a checked stream is now missing if(item->checkState() == Qt::Checked && // if checked - (std::find(missingStreams.begin(), // and missing - missingStreams.end(), - item->text().toStdString())!=missingStreams.end())) { + missingStreams.contains(item->text())) { // are you sure? QMessageBox msgBox; msgBox.setText("At least one of the streams that you checked seems to be offline."); @@ -291,48 +223,38 @@ void MainWindow::startRecording() { } // determine the experiment number block - // scan the path for %n and %b - recFilename = ui->locationEdit->text().toStdString(); - std::size_t pos_n = recFilename.find("%n"); - - if(pos_n < recFilename.size()) { // check to make sure it is there - recFilename.replace(pos_n, 2, std::to_string(ui->experimentNumberSpin->value())); - } - - std::size_t pos_b = recFilename.find("%b"); - if(pos_blocationEdit->text(); + int pos_n = recFilename.indexOf("%n"); + if(pos_n != -1) + recFilename.replace(pos_n, 2, ui->experimentNumberSpin->value()); + + int pos_b = recFilename.indexOf("%b"); + if(pos_b != -1) // check to make sure it is there recFilename.replace(pos_b, 2, currentBlock); - - if(boost::filesystem::exists(recFilename)) { - size_t lastdot = recFilename.find_last_of('.'); - for(int i=1;i<=9999;i++) { // search for highest _oldN - std::string rename_to = recFilename.substr(0, lastdot) + - "_old" + std::to_string(i) + - recFilename.substr(lastdot,recFilename.size()); - - if(!boost::filesystem::exists(rename_to)) { // found it - try { - boost::filesystem::rename(boost::filesystem::path(recFilename), - boost::filesystem::path(rename_to)); - } catch(std::exception &e) { - QMessageBox::information(this,"Permissions issue", QString::fromStdString("Can not rename the file " + recFilename + " to " + rename_to + ": " + e.what()), QMessageBox::Ok); - return; - } - break; - } + + QFileInfo recFileInfo(recFilename); + if(recFileInfo.exists()) { + if(recFileInfo.isDir()) { + QMessageBox::warning(this, "Error", "Recording path already exists and is a directory"); + return; } - //std::cout << recFilename << std::endl; - //std::cout << rename_to << std::endl; + QString rename_to = recFileInfo.baseName() + "_old%1." + recFileInfo.suffix(); + // search for highest _oldN + int i = 1; + while(QFileInfo(rename_to.arg(i)).exists()) i++; + QString newname = rename_to.arg(i); + if(!QFile::rename(recFilename, newname)) { + QMessageBox::warning(this,"Permissions issue", "Can not rename the file " + recFilename + " to " + newname); + return; + } + std::cout << "Moved existing file to " << newname.toStdString() << std::endl; + recFileInfo.refresh(); } - // regardless, we need to check for this one - std::string targetdir = recFilename.substr(0,recFilename.find_last_of("/\\")); - try { - if(!boost::filesystem::exists(targetdir)) - boost::filesystem::create_directories(targetdir); - } catch(std::exception &e) { - std::cout << "with creating directory: " << e.what() << std::endl; - QMessageBox::information(this,"Permissions issue", "Can not create the directory " + QString(targetdir.c_str()) + ". Please check your permissions.", QMessageBox::Ok); + // regardless, we need to create the directory if it doesn't exist + if(!recFileInfo.dir().mkpath(".")) { + QMessageBox::warning(this,"Permissions issue", "Can not create the directory " + recFileInfo.dir().path() + ". Please check your permissions."); return; } @@ -350,14 +272,14 @@ void MainWindow::startRecording() { checkedStreams.push_back(stream); // if it is checked and also missing, watch for it - if(std::find(missingStreams.begin(), missingStreams.end(), ui->streamList->item(i)->text().toStdString())!=missingStreams.end()) + if(missingStreams.contains(ui->streamList->item(i)->text())) watchfor.push_back(ui->streamList->item(i)->text().toStdString()); } for(const std::string& s: watchfor) std::cout << s << std::endl; - currentRecording.reset(new recording(recFilename, checkedStreams, watchfor, syncOptionsByStreamName, 1)); + currentRecording.reset(new recording(recFilename.toStdString(), checkedStreams, watchfor, syncOptionsByStreamName, 1)); ui->stopButton->setEnabled(true); ui->startButton->setEnabled(false); startTime = (int)lsl::local_clock(); diff --git a/mainwindow.h b/mainwindow.h index 4d21294..5e4d14b 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef __WIN32 @@ -41,7 +42,7 @@ class MainWindow : public QMainWindow { private slots: void statusUpdate(void) const; void closeEvent(QCloseEvent *ev) override; - void blockSelected(QListWidgetItem *item); + void blockSelected(const QString& block); void refreshStreams(void); void startRecording(void); void stopRecording(void); @@ -53,20 +54,20 @@ private slots: std::unique_ptr timer; int currentTrial; - std::string currentBlock; + QString currentBlock; std::vector resolvedStreams; std::vector checkedStreams; - std::vector requiredStreams; - std::vector onlineSyncStreams; + QStringList requiredStreams; std::map syncOptionsByStreamName; - std::vector missingStreams; + QStringList missingStreams; - std::string recFilename; + QString recFilename; FILE *p_recFile; // function for loading config file - void load_config(const std::string &filename); + void load_config(QString filename); + void save_config(QString filename); std::unique_ptr ui; // window pointer }; diff --git a/mainwindow.ui b/mainwindow.ui index 6d3de92..d1bfe7a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -28,10 +28,6 @@ Start - - - ../../../record.png../../../record.png - Ctrl+S @@ -169,7 +165,7 @@ - + @@ -184,6 +180,13 @@ + + + + Qt::Vertical + + + @@ -235,10 +238,7 @@ - About - - - Ctrl+A + &About diff --git a/recording.cpp b/recording.cpp index 3247ae9..50372a6 100644 --- a/recording.cpp +++ b/recording.cpp @@ -1,10 +1,12 @@ #include "recording.h" -#include #include #include #ifdef XDFZ_SUPPORT +#define BOOST_IOSTREAMS_NO_LIB +#include #include +#include #endif static const std::string boundary_uuid(reinterpret_cast(boundary_uuid_c), 16); @@ -124,8 +126,11 @@ recording::recording(const std::string& filename, const std::vector #include +#ifdef XDFZ_SUPPORT #include - +using outfile_t = boost::iostreams::filtering_ostream; +#else +#include +using outfile_t = std::ofstream; +#endif #include #if BOOST_VERSION >= 105800 @@ -144,7 +149,7 @@ class recording { private: // the file stream - boost::iostreams::filtering_ostream file_; // the file output stream + outfile_t file_; // the file output stream std::mutex chunk_mut_; // static information bool offsets_enabled_; // whether to collect time offset information alongside with the stream contents