@@ -42,23 +42,25 @@ MainWindow::MainWindow(QWidget *parent, const char *config_file)
4242 });
4343
4444 // Wheenver lineEdit_template is changed, print the final result.
45- connect (ui->lineEdit_template , &QLineEdit::textChanged, this , &MainWindow::printReplacedFilename);
45+ connect (
46+ ui->lineEdit_template , &QLineEdit::textChanged, this , &MainWindow::printReplacedFilename);
4647 auto spinchanged = static_cast <void (QSpinBox::*)(int )>(&QSpinBox::valueChanged);
4748 connect (ui->experimentNumberSpin , spinchanged, this , &MainWindow::printReplacedFilename);
48- connect (ui->spinBox_run , spinchanged, this , &MainWindow::printReplacedFilename);
4949
5050 // Signals for builder-related edits -> buildFilename
5151 connect (ui->rootBrowseButton , &QPushButton::clicked, [this ]() {
52- this ->ui ->rootEdit ->setText (QFileDialog::getExistingDirectory (this , " Study root folder..." ));
52+ this ->ui ->rootEdit ->setText (
53+ QFileDialog::getExistingDirectory (this , " Study root folder..." ));
5354 this ->buildFilename ();
5455 });
5556 connect (ui->rootEdit , &QLineEdit::editingFinished, this , &MainWindow::buildFilename);
56- connect (ui->lineEdit_participant , &QLineEdit::editingFinished, this , &MainWindow::buildFilename);
57+ connect (
58+ ui->lineEdit_participant , &QLineEdit::editingFinished, this , &MainWindow::buildFilename);
5759 connect (ui->lineEdit_session , &QLineEdit::editingFinished, this , &MainWindow::buildFilename);
5860 connect (ui->lineEdit_acq , &QLineEdit::editingFinished, this , &MainWindow::buildFilename);
5961 connect (ui->input_blocktask , &QComboBox::currentTextChanged, this , &MainWindow::buildFilename);
60- connect (ui->check_bids , &QCheckBox::toggled, [this ](bool checked){
61- auto & box = *ui->lineEdit_template ;
62+ connect (ui->check_bids , &QCheckBox::toggled, [this ](bool checked) {
63+ auto & box = *ui->lineEdit_template ;
6264 box.setReadOnly (checked);
6365 if (checked) {
6466 legacyTemplate = box.text ();
@@ -124,7 +126,7 @@ void MainWindow::load_config(QString filename) {
124126 // ----------------------------
125127 // online sync streams
126128 // ----------------------------
127- QStringList onlineSyncStreams = pt.value (" OnlineSync" , " " ).toStringList ();
129+ QStringList onlineSyncStreams = pt.value (" OnlineSync" , QStringList () ).toStringList ();
128130 for (QString &oss : onlineSyncStreams) {
129131 QStringList words = oss.split (' ' , QString::SkipEmptyParts);
130132 // The first two words ("StreamName (PC)") are the stream identifier
@@ -154,18 +156,53 @@ void MainWindow::load_config(QString filename) {
154156 ui->input_blocktask ->clear ();
155157 ui->input_blocktask ->insertItems (0 , taskNames);
156158
159+ // StorageLocation
160+ QString studyRoot;
161+ legacyTemplate.clear ();
157162 // StudyRoot
158- if (pt.contains (" StudyRoot" )) {
159- ui->rootEdit ->setText (pt.value (" StudyRoot" ).toString ());
163+ if (pt.contains (" StudyRoot" )) { studyRoot = pt.value (" StudyRoot" ).toString (); }
164+
165+ if (pt.contains (" StorageLocation" )) {
166+ if (!studyRoot.isEmpty ())
167+ throw std::runtime_error (" Both StudyRoot and StorageLocation specified" );
168+ if (pt.contains (" PathTemplate" ))
169+ throw std::runtime_error (" Both StorageLocation and PathTemplate specified" );
170+
171+ QString str_path = pt.value (" StorageLocation" ).toString ();
172+ QString path_root;
173+ auto index = str_path.indexOf (' %' );
174+ if (index != -1 ) {
175+ // When a % is encountered, the studyroot gets set to the
176+ // longest path before the placeholder, e.g.
177+ // foo/bar/baz%a/untitled.xdf gets split into
178+ // foo/bar and baz%a/untitled.xdf
179+ path_root = str_path.left (index);
180+ } else
181+ // Otherwise, it's split into folder and constant filename
182+ path_root = str_path;
183+ path_root = QFileInfo (path_root).path ();
184+ legacyTemplate = str_path.remove (0 , path_root.length () + 1 );
185+ // absolute path, nothing to be done
186+ studyRoot = QFileInfo (path_root).absolutePath ();
187+ ui->lineEdit_template ->setText (legacyTemplate);
188+ }
189+ if (pt.contains (" PathTemplate" )) legacyTemplate = pt.value (" PathTemplate" ).toString ();
190+
191+ if (studyRoot.isEmpty ())
192+ studyRoot = QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation) +
193+ QDir::separator () + " CurrentStudy" ;
194+ ui->rootEdit ->setText (studyRoot);
195+
196+ if (legacyTemplate.isEmpty ()) {
197+ ui->check_bids ->setChecked (true );
198+ // Legacy takes the form path/to/study/exp%n/%b.xdf
199+ legacyTemplate = " exp%n/block_%b.xdf" ;
160200 } else {
161- ui->rootEdit -> setText (
162- QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation) );
201+ ui->check_bids -> setChecked ( false );
202+ ui-> lineEdit_template -> setText (legacyTemplate );
163203 }
164204
165- // StorageLocation
166- QString str_path = pt.value (" StorageLocation" , " " ).toString ();
167- ui->lineEdit_template ->setText (str_path);
168- if (str_path.length () == 0 ) { buildFilename (); }
205+ buildFilename ();
169206
170207 // Check the wild-card-replaced filename to see if it exists already.
171208 // If it does then increment the exp number.
@@ -175,18 +212,20 @@ void MainWindow::load_config(QString filename) {
175212 if (recFilename.contains (QStringLiteral (" %n" ))) {
176213 for (int i = 1 ; i < 1001 ; i++) {
177214 ui->experimentNumberSpin ->setValue (i);
178- if (!QDir (replaceFilename (recFilename)). exists ()) { break ; }
215+ if (!QFileInfo::exists (replaceFilename (recFilename))) break ;
179216 }
180217 }
181-
218+
182219 } catch (std::exception &e) { qWarning () << " Problem parsing config file: " << e.what (); }
183220 // std::cout << "refreshing streams ..." <<std::endl;
184221 refreshStreams ();
185222}
186223
187224void MainWindow::save_config (QString filename) {
188225 QSettings settings (filename, QSettings::Format::IniFormat);
189- settings.setValue (" StorageLocation" , ui->lineEdit_template ->text ());
226+ settings.setValue (" StudyRoot" , ui->lineEdit_template ->text ());
227+ if (!ui->check_bids ->isChecked ())
228+ settings.setValue (" PathTemplate" , ui->lineEdit_template ->text ());
190229 qInfo () << requiredStreams;
191230 settings.setValue (" RequiredStreams" , requiredStreams);
192231 // Stub.
@@ -261,12 +300,12 @@ void MainWindow::startRecording() {
261300 if (msgBox.exec () != QMessageBox::Yes) return ;
262301 }
263302
264- // Handle wildcards in filename.
265303 QString recFilename = replaceFilename (ui->lineEdit_template ->text ());
266304 if (recFilename.isEmpty ()) {
267305 QMessageBox::critical (this , " Filename empty" , " Can not record without a file name" );
268306 return ;
269307 }
308+ recFilename.prepend (ui->rootEdit ->text () + QDir::separator ());
270309
271310 QFileInfo recFileInfo (recFilename);
272311 if (recFileInfo.exists ()) {
@@ -275,8 +314,8 @@ void MainWindow::startRecording() {
275314 this , " Error" , " Recording path already exists and is a directory" );
276315 return ;
277316 }
278- QString rename_to = recFileInfo.absolutePath () + ' / ' + recFileInfo. baseName () +
279- " _old%1." + recFileInfo.suffix ();
317+ QString rename_to = recFileInfo.absolutePath () + QDir::separator () +
318+ recFileInfo. baseName () + " _old%1." + recFileInfo.suffix ();
280319 // search for highest _oldN
281320 int i = 1 ;
282321 while (QFileInfo::exists (rename_to.arg (i))) i++;
@@ -351,72 +390,44 @@ void MainWindow::selectNoStreams() {
351390 }
352391}
353392
354- void MainWindow::buildFilename () {
355- // This function is only called when a widget within Location Builder is activated.
393+ void MainWindow::buildBidsTemplate () {
394+ // path/to/CurrentStudy/sub-%p/ses-%s/eeg/sub-%p_ses-%s_task-%b[_acq-%a]_run-%r_eeg.xdf
356395
357- // Fill root folder with default if needed.
358- if (ui->rootEdit ->text ().isEmpty ()) {
359- QStringList fileparts =
360- QStringList (QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation))
361- << " CurrentStudy" ;
362- ui->rootEdit ->setText (QDir::toNativeSeparators (fileparts.join (QDir::separator ())));
396+ // Make sure the BIDS required fields are full.
397+ if (ui->lineEdit_participant ->text ().isEmpty ()) { ui->lineEdit_participant ->setText (" P001" ); }
398+ if (ui->lineEdit_session ->text ().isEmpty ()) { ui->lineEdit_session ->setText (" S001" ); }
399+ if (ui->input_blocktask ->currentText ().isEmpty ()) {
400+ ui->input_blocktask ->setCurrentText (" Default" );
363401 }
364402
403+ // Folder hierarchy
404+ QStringList fileparts{" sub-%p" , " ses-%s" , " eeg" };
405+
406+ // filename
407+ QString fname = " sub-%p_ses-%s_task-%b" ;
408+ if (!ui->lineEdit_acq ->text ().isEmpty ()) { fname.append (" _acq-%a" ); }
409+ fname.append (" _run-%r_eeg.xdf" );
410+ fileparts << fname;
411+ ui->lineEdit_template ->setText (fileparts.join (QDir::separator ()));
412+ }
413+
414+ void MainWindow::buildFilename () {
415+ // This function is only called when a widget within Location Builder is activated.
416+
365417 // Build the file location in parts, starting with the root folder.
366- QStringList fileparts = QStringList (ui->rootEdit ->text ());
367- bool hasTasks = ui->input_blocktask ->currentText ().isEmpty ();
368-
369- bool use_bids = ui->check_bids ->isChecked ();
370- if (use_bids) {
371- // path/to/CurrentStudy/sub-%p/ses-%s/eeg/sub-%p_ses-%s_task-%b[_acq-%a]_run-%r_eeg.xdf
372-
373- // Make sure the BIDS required fields are full.
374- if (ui->lineEdit_participant ->text ().isEmpty ()) { ui->lineEdit_participant ->setText (" P001" ); }
375- if (ui->lineEdit_session ->text ().isEmpty ()) { ui->lineEdit_session ->setText (" S001" ); }
376- if (ui->input_blocktask ->currentText ().isEmpty ()) { ui->input_blocktask ->setCurrentText (" Default" ); }
377-
378- // Folder hierarchy
379- fileparts << " sub-%p"
380- << " ses-%s"
381- << " eeg" ;
382-
383- // filename
384- QString fname = " sub-%p_ses-%s_task-%b" ;
385- if (!ui->lineEdit_acq ->text ().isEmpty ()) { fname.append (" _acq-%a" ); }
386- fname.append (" _run-%r_eeg.xdf" );
387- fileparts << fname;
388- } else // !use_bids == use legacy
389- {
390- // Legacy takes the form path/to/study/exp%n/%b.xdf
391- fileparts << " exp%n" ;
392-
393- QString fname = " " ;
394- if (hasTasks) {
395- fname.append (" %b.xdf" );
396- } else {
397- fname.append (" untitled.xdf" );
398- }
399- fileparts << fname;
400- }
401- QString recFilename = QDir::toNativeSeparators (fileparts.join (QDir::separator ()));
418+ if (ui->check_bids ->isChecked ()) buildBidsTemplate ();
419+ QString tpl = ui->lineEdit_template ->text ();
402420
403421 // Auto-increment Spin/Run Number if necessary.
404- if (recFilename .contains (QStringLiteral ( " %n" ))) {
422+ if (tpl .contains (" %n" ) || tpl. contains ( " %r " )) {
405423 for (int i = 1 ; i < 1001 ; i++) {
406424 ui->experimentNumberSpin ->setValue (i);
407- if (!QDir (replaceFilename (recFilename)).exists ()) { break ; }
408- }
409- }
410- if (recFilename.contains (QStringLiteral (" %r" ))) {
411- for (int i = 1 ; i < 1001 ; i++) {
412- ui->spinBox_run ->setValue (i);
413- if (!QDir (replaceFilename (recFilename)).exists ()) { break ; }
425+ if (!QFileInfo::exists (replaceFilename (tpl))) break ;
414426 }
415427 }
416428 // Sometimes lineEdit_template doesn't change so printReplacedFilename isn't triggered.
417429 // So trigger manually.
418- if (ui->lineEdit_template ->text () == recFilename) { printReplacedFilename (); }
419- ui->lineEdit_template ->setText (recFilename);
430+ printReplacedFilename ();
420431}
421432
422433QString MainWindow::replaceFilename (QString fullfile) const {
@@ -426,9 +437,10 @@ QString MainWindow::replaceFilename(QString fullfile) const {
426437 // Legacy takes the form path/to/study/exp%n/%b.xdf
427438 // Where %n will be replaced by the contents of the experimentNumberSpin widget
428439 // and %b will be replaced by the contents of the blockList widget.
429- fullfile.replace (" %n" , QString (" %1" ).arg (ui->experimentNumberSpin ->value (), 3 , 10 , QChar (' 0' )));
440+ QString run = QString (" %1" ).arg (ui->experimentNumberSpin ->value (), 3 , 10 , QChar (' 0' ));
441+ fullfile.replace (" %n" , run);
430442 fullfile.replace (" %b" , ui->input_blocktask ->currentText ());
431-
443+
432444 // BIDS
433445 // See https://docs.google.com/document/d/1ArMZ9Y_quTKXC-jNXZksnedK2VHHoKP3HCeO5HPcgLE/
434446 // path/to/study/sub-<participant_label>/ses-<session_label>/eeg/sub-<participant_label>_ses-<session_label>_task-<task_label>[_acq-<acq_label>]_run-<run_index>_eeg.xdf
@@ -437,7 +449,7 @@ QString MainWindow::replaceFilename(QString fullfile) const {
437449 fullfile.replace (" %p" , ui->lineEdit_participant ->text ());
438450 fullfile.replace (" %s" , ui->lineEdit_session ->text ());
439451 fullfile.replace (" %a" , ui->lineEdit_acq ->text ());
440- fullfile.replace (" %r" , QString ( " %1 " ). arg (ui-> spinBox_run -> value (), 3 , 10 , QChar ( ' 0 ' )) );
452+ fullfile.replace (" %r" , run );
441453
442454 return fullfile;
443455}
@@ -464,6 +476,7 @@ QString MainWindow::find_config_file(const char *filename) {
464476 }
465477 QFileInfo exeInfo (QCoreApplication::applicationFilePath ());
466478 QString defaultCfgFilename (exeInfo.completeBaseName () + " .cfg" );
479+ qInfo () << defaultCfgFilename;
467480 QStringList cfgpaths;
468481 cfgpaths << QDir::currentPath ()
469482 << QStandardPaths::standardLocations (QStandardPaths::ConfigLocation) << exeInfo.path ();
@@ -477,5 +490,6 @@ QString MainWindow::find_config_file(const char *filename) {
477490}
478491
479492void MainWindow::printReplacedFilename () {
480- ui->locationLabel ->setText (replaceFilename (ui->lineEdit_template ->text ()));
493+ ui->locationLabel ->setText (
494+ ui->rootEdit ->text () + ' \n ' + replaceFilename (ui->lineEdit_template ->text ()));
481495}
0 commit comments