MainWindow.cpp 24.4 KB
Newer Older
Thiago Santini's avatar
Thiago Santini committed
1 2 3
#include "MainWindow.h"
#include "ui_MainWindow.h"

Thiago Santini's avatar
Thiago Santini committed
4 5
#include <QSysInfo>

Thiago Santini's avatar
Thiago Santini committed
6
// TODO: automatically detect suitable window positions on first init
7

Thiago Santini's avatar
Thiago Santini committed
8 9 10 11 12 13
void MainWindow::createExtraMenus()
{
    ui->menuBar->addAction("References");
    ui->menuBar->addAction("About");
}

14 15
MainWindow::MainWindow(QWidget* parent)
    : ERWidget("EyeRecToo", parent)
16 17 18 19 20 21 22 23 24 25 26 27 28
    , settings(nullptr)
    , lEyeWidget(nullptr)
    , rEyeWidget(nullptr)
    , fieldWidget(nullptr)
    , audioRecorder(nullptr)
    , synchronizer(nullptr)
    , gazeEstimationWidget(nullptr)
    , journalThread(nullptr)
    , journal(nullptr)
    , networkStream(nullptr)
    , logWidget(nullptr)
    , performanceMonitorWidget(nullptr)
    , postProcessingWidget(nullptr)
29
    , ui(new Ui::MainWindow)
Thiago Santini's avatar
Thiago Santini committed
30
{
31
    ui->setupUi(this);
Thiago Santini's avatar
Thiago Santini committed
32 33

    createExtraMenus();
34
    connect(ui->menuBar, SIGNAL(triggered(QAction*)), this, SLOT(menuOption(QAction*)));
Thiago Santini's avatar
Thiago Santini committed
35

36 37
    settings = new QSettings(gCfgDir + "/" + "EyeRecToo.ini", QSettings::IniFormat);
    cfg.load(settings);
Thiago Santini's avatar
Thiago Santini committed
38

39
    ui->statusBar->showMessage(QString("This is version %1").arg(VERSION));
40
    setWindowIcon(QIcon(":/icons/eyerectoo.ico"));
Thiago Santini's avatar
Thiago Santini committed
41 42

    if (!cfg.workingDirectory.isEmpty())
43 44
        setWorkingDirectory(cfg.workingDirectory);
    ui->pwd->setText(QDir::currentPath());
Thiago Santini's avatar
Thiago Santini committed
45

46
    ui->blinker->hide();
Thiago Santini's avatar
Thiago Santini committed
47

48 49 50 51
    logWidget = new LogWidget("Log Widget");
    logWidget->setDefaults(true, { 480, 240 });
    setupWidget(logWidget, settings, ui->log);
    gLogWidget = logWidget;
Thiago Santini's avatar
Thiago Santini committed
52

Thiago Santini's avatar
Thiago Santini committed
53 54 55 56 57 58
    /*
     * WARNING: DO NOT REMOVE THIS CALL to QCameraInfo::availableCameras()
     * Logically, its meaningless, but it guarantees that DirectShow will work
     * properly by CoInitializing it in the main thread.
     */
    volatile QList<QCameraInfo> tmp = QCameraInfo::availableCameras();
59
    Q_UNUSED(tmp);
60 61 62
    checkDelimitiers<EyeData>();
    checkDelimitiers<FieldData>();
    checkDelimitiers<DataTuple>();
Thiago Santini's avatar
Thiago Santini committed
63

64
    gPerformanceMonitor.setFrameDrop(true);
Thiago Santini's avatar
Thiago Santini committed
65 66 67 68

    /*
     * Asynchronous elements
     */
69 70 71 72 73 74 75 76 77 78 79 80 81
    lEyeWidget = new CameraWidget("Left Eye Widget", ImageProcessor::Eye);
    lEyeWidget->setDefaults(true, { 320, 240 });
    setupWidget(lEyeWidget, settings, ui->leftEyeCam);
    QThread::msleep(200);
    rEyeWidget = new CameraWidget("Right Eye Widget", ImageProcessor::Eye);
    rEyeWidget->setDefaults(true, { 320, 240 });
    setupWidget(rEyeWidget, settings, ui->rightEyeCam);
    QThread::msleep(200);
    fieldWidget = new CameraWidget("Field Widget", ImageProcessor::Field);
    fieldWidget->setDefaults(true, { 854, 480 });
    setupWidget(fieldWidget, settings, ui->fieldCam);

    audioRecorder = new AudioRecorder();
Thiago Santini's avatar
Thiago Santini committed
82

Thiago Santini's avatar
Thiago Santini committed
83 84 85 86
    /*
     * Synchronizer
     */
    synchronizer = new Synchronizer();
87 88 89 90
    connect(lEyeWidget, SIGNAL(newData(EyeData)),
        synchronizer, SLOT(newLeftEyeData(EyeData)), Qt::QueuedConnection);
    connect(rEyeWidget, SIGNAL(newData(EyeData)),
        synchronizer, SLOT(newRightEyeData(EyeData)));
Thiago Santini's avatar
Thiago Santini committed
91
    connect(fieldWidget, SIGNAL(newData(FieldData)),
92
        synchronizer, SLOT(newFieldData(FieldData)));
Thiago Santini's avatar
Thiago Santini committed
93 94 95 96

    /*
     * Synchronous elements
     */
97 98 99
    gazeEstimationWidget = new GazeEstimationWidget("Gaze Estimation Widget");
    gazeEstimationWidget->setDefaults(false);
    setupWidget(gazeEstimationWidget, settings, ui->gazeEstimation);
Thiago Santini's avatar
Thiago Santini committed
100
    connect(synchronizer, SIGNAL(newData(DataTuple)),
101 102 103
        gazeEstimationWidget, SIGNAL(inDataTuple(DataTuple)));
    connect(fieldWidget, SIGNAL(newClick(Timestamp, QPoint, QSize)),
        gazeEstimationWidget, SIGNAL(newClick(Timestamp, QPoint, QSize)));
Thiago Santini's avatar
Thiago Santini committed
104 105

    connect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)),
106
        fieldWidget, SLOT(preview(DataTuple)));
Thiago Santini's avatar
Thiago Santini committed
107 108 109 110 111

    journalThread = new QThread();
    journalThread->setObjectName("Journal");
    journalThread->start();
    journalThread->setPriority(QThread::NormalPriority);
112
    journal = new DataRecorderThread("Journal", DataTuple::header());
Thiago Santini's avatar
Thiago Santini committed
113 114 115 116 117 118
    journal->moveToThread(journalThread);
    QMetaObject::invokeMethod(journal, "create");

    networkStream = new NetworkStream();

    networkStream->start(2002);
119
    connect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)), networkStream, SLOT(push(DataTuple)));
Thiago Santini's avatar
Thiago Santini committed
120

121 122 123
    performanceMonitorWidget = new PerformanceMonitorWidget("Performance Monitor Widget");
    performanceMonitorWidget->setDefaults(false);
    setupWidget(performanceMonitorWidget, settings, ui->performanceMonitor);
124

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    postProcessingWidget = new PostProcessingWidget("Post Processing Widget");
    postProcessingWidget->setDefaults(false);
    setupWidget(postProcessingWidget, settings, ui->postProcessing);
    ui->postProcessing->hide();
    connect(postProcessingWidget, SIGNAL(setWorkingDirectory(QString)),
        this, SLOT(setWorkingDirectory(QString)));
    connect(postProcessingWidget, SIGNAL(restorePreviousPwd()),
        this, SLOT(restorePreviousPwd()));
    connect(postProcessingWidget, SIGNAL(resetCalibration(CollectionTuple::TupleType)),
        gazeEstimationWidget, SIGNAL(resetCalibration(CollectionTuple::TupleType)));
    connect(postProcessingWidget, SIGNAL(injectCalibrationTuple(CollectionTuple)),
        gazeEstimationWidget, SIGNAL(newTuple(CollectionTuple)));
    connect(postProcessingWidget, SIGNAL(requestCalibration()),
        gazeEstimationWidget, SIGNAL(calibrationRequest()));
    connect(postProcessingWidget, SIGNAL(outDataTuple(DataTuple)),
        gazeEstimationWidget, SIGNAL(inDataTuple(DataTuple)));

    // replay previews
    connect(postProcessingWidget, SIGNAL(present(DataTuple)),
        fieldWidget, SLOT(preview(DataTuple)));
    connect(postProcessingWidget, SIGNAL(leftEyePresent(EyeData)),
        lEyeWidget, SLOT(preview(EyeData)));
    connect(postProcessingWidget, SIGNAL(rightEyePresent(EyeData)),
        rEyeWidget, SLOT(preview(EyeData)));

    connect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)),
        postProcessingWidget, SLOT(inDataTuple(DataTuple)));
    connect(gazeEstimationWidget, SIGNAL(calibrationFinished(bool, QString)),
        postProcessingWidget, SLOT(calibrationFinished(bool, QString)));

Thiago Santini's avatar
Thiago Santini committed
155 156
    // GUI to Widgets signals
    connect(this, SIGNAL(stopRecording()),
157
        audioRecorder, SLOT(stopRecording()));
Thiago Santini's avatar
Thiago Santini committed
158
    connect(this, SIGNAL(startRecording()),
159 160 161 162 163 164 165
        audioRecorder, SLOT(startRecording()));
    connect(this, SIGNAL(startRecording()),
        lEyeWidget, SLOT(startRecording()));
    connect(this, SIGNAL(stopRecording()),
        lEyeWidget, SLOT(stopRecording()));
    connect(this, SIGNAL(startRecording()),
        rEyeWidget, SLOT(startRecording()));
Thiago Santini's avatar
Thiago Santini committed
166
    connect(this, SIGNAL(stopRecording()),
167
        rEyeWidget, SLOT(stopRecording()));
Thiago Santini's avatar
Thiago Santini committed
168
    connect(this, SIGNAL(startRecording()),
169
        fieldWidget, SLOT(startRecording()));
Thiago Santini's avatar
Thiago Santini committed
170
    connect(this, SIGNAL(stopRecording()),
171
        fieldWidget, SLOT(stopRecording()));
Thiago Santini's avatar
Thiago Santini committed
172
    connect(this, SIGNAL(startRecording()),
173
        gazeEstimationWidget, SLOT(startRecording()));
Thiago Santini's avatar
Thiago Santini committed
174
    connect(this, SIGNAL(stopRecording()),
175 176 177 178 179
        gazeEstimationWidget, SLOT(stopRecording()));
    connect(this, SIGNAL(startRecording()),
        journal, SIGNAL(startRecording()));
    connect(this, SIGNAL(stopRecording()),
        journal, SIGNAL(stopRecording()));
Thiago Santini's avatar
Thiago Santini committed
180

181 182
    loadSoundEffect("rec-start.wav", gExeDir, recStartSound);
    loadSoundEffect("rec-stop.wav", gExeDir, recStopSound);
183

184
    setupWidget(this, settings);
185

186
    /***************************************************************************
Thiago Santini's avatar
Thiago Santini committed
187 188 189
	 *  Commands
	 **************************************************************************/

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
    // Calibration
    connect(&commandManager, SIGNAL(toggleCalibration()),
        gazeEstimationWidget, SLOT(toggleCalibration()));
    connect(&commandManager, SIGNAL(toggleMarkerCollection()),
        gazeEstimationWidget, SLOT(toggleMarkerCollection()));
    connect(&commandManager, SIGNAL(toggleRemoteCalibration()),
        gazeEstimationWidget, SLOT(toggleRemoteCalibration()));

    // Recording
    connect(&commandManager, SIGNAL(toggleRecording()),
        ui->recordingToggle, SLOT(click()));
    connect(&commandManager, SIGNAL(toggleRemoteRecording()),
        this, SLOT(toggleRemoteRecording()));

    // Additionals
    connect(&commandManager, SIGNAL(freezeCameraImages()),
        this, SLOT(freezeCameraImages()));
    connect(&commandManager, SIGNAL(unfreezeCameraImages()),
        this, SLOT(unfreezeCameraImages()));
209
    connect(&commandManager, SIGNAL(togglePreview()),
210
        this, SLOT(togglePreview()));
Thiago Santini's avatar
Thiago Santini committed
211 212 213 214 215 216 217
}

MainWindow::~MainWindow()
{
    delete ui;
}

218
void MainWindow::closeEvent(QCloseEvent* event)
Thiago Santini's avatar
Thiago Santini committed
219
{
220 221 222 223 224 225 226
    if (ui->recordingToggle->isChecked()) {
        ui->recordingToggle->setChecked(false);
        on_recordingToggle_clicked();
        // should be equivalent, but just in case click() is connected with a
        // queued connection in the future :-)
        // ui->recordingToggle->click();
    }
Thiago Santini's avatar
Thiago Santini committed
227

228 229 230 231 232
    if (gPostProcessing) {
        if (postProcessingWidget) // direct so pwd is restored before we save the settings
            QMetaObject::invokeMethod(postProcessingWidget, "on_actionClose_Recording_triggered", Qt::DirectConnection);
    }

233 234 235 236
    cfg.workingDirectory = QDir::currentPath();
    if (settings) {
        cfg.save(settings);
        save(settings);
237 238 239 240 241 242 243 244 245

        auto testSave = [this](ERWidget* w) { if (w) w->save(this->settings); };
        testSave(logWidget);
        testSave(lEyeWidget);
        testSave(rEyeWidget);
        testSave(fieldWidget);
        testSave(gazeEstimationWidget);
        testSave(performanceMonitorWidget);
        testSave(postProcessingWidget);
246
    }
Thiago Santini's avatar
Thiago Santini committed
247

248
    qInfo() << "Closing Left Eye Widget...";
249
    if (lEyeWidget) {
Thiago Santini's avatar
Thiago Santini committed
250 251
        lEyeWidget->close();
        lEyeWidget->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
252
        lEyeWidget = nullptr;
Thiago Santini's avatar
Thiago Santini committed
253
    }
254
    qInfo() << "Closing Right Eye Widget...";
255
    if (rEyeWidget) {
Thiago Santini's avatar
Thiago Santini committed
256 257
        rEyeWidget->close();
        rEyeWidget->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
258
        rEyeWidget = nullptr;
Thiago Santini's avatar
Thiago Santini committed
259
    }
260
    qInfo() << "Closing Field Widget...";
261
    if (fieldWidget) {
Thiago Santini's avatar
Thiago Santini committed
262 263
        fieldWidget->close();
        fieldWidget->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
264
        fieldWidget = nullptr;
265
    }
Thiago Santini's avatar
Thiago Santini committed
266

267
    qInfo() << "Closing Gaze Estimation Widget...";
Thiago Santini's avatar
Thiago Santini committed
268 269 270 271 272 273 274 275 276
    if (gazeEstimationWidget) {
        gazeEstimationWidget->close();
        gazeEstimationWidget->deleteLater();
    }

    qInfo() << "Stoping network stream...";
    if (networkStream)
        networkStream->deleteLater();

277
    gPerformanceMonitor.report();
278 279 280 281 282

    qInfo() << "Closing Performance Monitor Widget...";
    if (performanceMonitorWidget) {
        performanceMonitorWidget->close();
        performanceMonitorWidget->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
283
        performanceMonitorWidget = nullptr;
284 285
    }

286 287 288 289 290 291 292
    qInfo() << "Stopping Post Processing Widget";
    if (postProcessingWidget) {
        postProcessingWidget->close();
        postProcessingWidget->deleteLater();
        postProcessingWidget = nullptr;
    }

293
    qInfo() << "Closing Log Widget...";
Thiago Santini's avatar
Thiago Santini committed
294
    if (logWidget) {
Thiago Santini's avatar
Thiago Santini committed
295
        gLogWidget = nullptr;
296
        logWidget->close();
Thiago Santini's avatar
Thiago Santini committed
297
        logWidget->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
298
        logWidget = nullptr;
Thiago Santini's avatar
Thiago Santini committed
299 300
    }

301 302 303
    qInfo() << "Stopping journal and synchronizer";
    if (journal) {
        journal->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
304
        journal = nullptr;
305
    }
Thiago Santini's avatar
Thiago Santini committed
306

307 308 309 310
    if (audioRecorder) {
        audioRecorder->deleteLater();
        audioRecorder = nullptr;
    }
311 312 313

    if (synchronizer) {
        synchronizer->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
314
        synchronizer = nullptr;
315 316 317
    }

    if (settings) {
Thiago Santini's avatar
Thiago Santini committed
318
        settings->deleteLater();
Thiago Santini's avatar
Thiago Santini committed
319
        settings = nullptr;
320
    }
Thiago Santini's avatar
Thiago Santini committed
321 322 323 324 325 326 327 328 329 330 331 332

    event->accept();
}

void MainWindow::setWorkingDirectory(QString dir)
{
    previousPwd = QDir::currentPath();
    QDir::setCurrent(dir);
    ui->pwd->setText(dir);
    qInfo() << "PWD set to" << dir;
}

333 334 335 336 337 338 339
void MainWindow::restorePreviousPwd()
{
    if (!previousPwd.isEmpty())
        setWorkingDirectory(previousPwd);
    previousPwd = "";
}

Thiago Santini's avatar
Thiago Santini committed
340 341 342
void MainWindow::on_changePwdButton_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
343 344 345
        QDir::currentPath(),
        QFileDialog::ShowDirsOnly
            | QFileDialog::DontResolveSymlinks);
Thiago Santini's avatar
Thiago Santini committed
346 347 348 349 350 351 352 353 354 355 356 357

    if (!dir.isEmpty()) {
        setWorkingDirectory(dir);
    }
}

void MainWindow::setSubjectName(QString newSubjectName)
{
    QRegExp re("[a-zA-Z0-9-_]*");
    Q_ASSERT(re.isValid());

    if (!re.exactMatch(newSubjectName)) {
Thiago Santini's avatar
Thiago Santini committed
358
        QMessageBox::warning(nullptr,
359 360 361 362
            "Invalid subject name.",
            QString("Invalid name: \"%1\".\n\nSubject names may contain only letters, numbers, dashes (-), and underscores (_).\nIn regex terms: %2").arg(newSubjectName, re.pattern()),
            QMessageBox::Ok,
            QMessageBox::NoButton);
Thiago Santini's avatar
Thiago Santini committed
363 364 365 366 367 368 369 370 371 372 373 374 375 376
        return;
    }

    ui->subject->setText(newSubjectName);
    if (newSubjectName.isEmpty())
        ui->changeSubjectButton->setText("Set");
    else
        ui->changeSubjectButton->setText("Change");

    qInfo() << "Subject set to" << newSubjectName;
}

void MainWindow::on_changeSubjectButton_clicked()
{
377
    QString newSubjectName = QInputDialog::getText(this, "Set subject", "Subject name:", QLineEdit::Normal, QString(), nullptr, Qt::CustomizeWindowHint);
Thiago Santini's avatar
Thiago Santini committed
378 379 380 381 382 383 384 385
    setSubjectName(newSubjectName);
}

bool MainWindow::setupSubjectDirectory()
{
    QString subject = ui->subject->text();

    if (subject.isEmpty()) {
386
        QString tmpSubjectName = QString::number(QDateTime::currentMSecsSinceEpoch() / 1000);
Thiago Santini's avatar
Thiago Santini committed
387
        QMessageBox msgBox(this);
388 389 390
        QPushButton* continueButton = msgBox.addButton("Start Anyway", QMessageBox::ActionRole);
        QPushButton* addButton = msgBox.addButton("Add Subject", QMessageBox::ActionRole);
        QPushButton* cancelButton = msgBox.addButton("Cancel", QMessageBox::ActionRole);
Thiago Santini's avatar
Thiago Santini committed
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
        msgBox.setText(QString("Currently there is no test subject.\nIf record is started, subject will be set to %1.\t").arg(tmpSubjectName));
        msgBox.exec();
        if (msgBox.clickedButton() == continueButton)
            setSubjectName(tmpSubjectName);
        else if (msgBox.clickedButton() == addButton)
            on_changeSubjectButton_clicked();
        else if (msgBox.clickedButton() == cancelButton)
            return false;

        if (ui->subject->text().isEmpty()) // smartass user still entered an empty string, show him who's the boss
            setSubjectName(tmpSubjectName);
    }

    QString path = QDir::currentPath() + "/" + ui->subject->text();

    unsigned int recording = 0;
407
    if (QDir(path).exists()) {
Thiago Santini's avatar
Thiago Santini committed
408 409 410 411
        QDirIterator it(path);
        while (it.hasNext()) {
            QRegExp re("[0-9]*");
            QString subDir = it.next();
412
            subDir = subDir.mid(path.size() + 1);
Thiago Santini's avatar
Thiago Santini committed
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
            if (re.exactMatch(subDir)) {
                unsigned int subDirRecId = subDir.toInt();
                if (subDirRecId > recording)
                    recording = subDirRecId;
            }
        }
    }
    recording++;

    path += "/" + QString::number(recording);

    previousPwd = QDir().currentPath();
    QDir().mkpath(path);
    setWorkingDirectory(path);

    return true;
}

void MainWindow::on_recordingToggle_clicked()
{
    if (ui->recordingToggle->isChecked()) {
        if (!setupSubjectDirectory()) {
            ui->recordingToggle->setChecked(false);
            return;
437 438 439 440
        }
        storeMetaDataHead();
        QMetaObject::invokeMethod(performanceMonitorWidget, "on_resetCounters_clicked");
        qInfo() << "Record starting (Subject:" << ui->subject->text() << ")";
Thiago Santini's avatar
Thiago Santini committed
441 442 443 444 445
        ui->changeSubjectButton->setEnabled(false);
        ui->changePwdButton->setEnabled(false);
        emit startRecording();
        ui->recordingToggle->setText("Finish");
        connect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)),
446
            journal, SIGNAL(newData(DataTuple)));
Thiago Santini's avatar
Thiago Santini committed
447 448
        QTimer::singleShot(500, this, SLOT(effectiveRecordingStart())); // TODO: right now we wait a predefined amount of time; ideally, we should wait for an ack from everyone involved
        ui->recordingToggle->setEnabled(false);
Thiago Santini's avatar
Thiago Santini committed
449
        recStartSound.play();
Thiago Santini's avatar
Thiago Santini committed
450 451 452 453
    } else {
        qInfo() << "Record stopped (Subject:" << ui->subject->text() << ")";
        emit stopRecording();
        disconnect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)),
454 455 456
            journal, SIGNAL(newData(DataTuple)));
        storeMetaDataTail();
        killTimer(elapsedTimeUpdateTimer);
Thiago Santini's avatar
Thiago Santini committed
457 458 459 460
        elapsedTime.invalidate();
        ui->elapsedTime->setText("00:00:00");
        ui->recordingToggle->setText("Start");
        ui->blinker->hide();
461
        restorePreviousPwd();
Thiago Santini's avatar
Thiago Santini committed
462 463
        ui->changeSubjectButton->setEnabled(true);
        ui->changePwdButton->setEnabled(true);
Thiago Santini's avatar
Thiago Santini committed
464
        recStopSound.play();
465 466
        gPerformanceMonitor.report();
    }
Thiago Santini's avatar
Thiago Santini committed
467 468 469 470 471 472 473 474 475 476 477 478
}
void MainWindow::effectiveRecordingStart()
{
    elapsedTime.restart();
    elapsedTimeUpdateTimer = startTimer(500);
    ui->recordingToggle->setEnabled(true);
    qInfo() << "Record started.";
}

void MainWindow::timerEvent(QTimerEvent* event)
{
    if (event->timerId() == elapsedTimeUpdateTimer) {
479
        ui->elapsedTime->setText(QDateTime::fromTime_t((elapsedTime.elapsed() / 1000 + 0.5), Qt::UTC).toString("hh:mm:ss"));
Thiago Santini's avatar
Thiago Santini committed
480 481 482 483 484 485 486 487

        if (ui->blinker->isVisible())
            ui->blinker->hide();
        else
            ui->blinker->show();
    }
}

488
void MainWindow::widgetButtonReact(QMainWindow* window, bool checked)
Thiago Santini's avatar
Thiago Santini committed
489 490 491 492
{
    if (!window)
        return;

493
    if (checked) {
Thiago Santini's avatar
Thiago Santini committed
494 495
        window->show();
        window->raise();
496
        window->activateWindow();
497 498 499
        window->setFocus();
        if (window->isMinimized())
            window->showNormal();
Thiago Santini's avatar
Thiago Santini committed
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
    } else
        window->hide();
}

void MainWindow::on_leftEyeCam_clicked()
{
    widgetButtonReact(lEyeWidget, ui->leftEyeCam->isChecked());
}

void MainWindow::on_rightEyeCam_clicked()
{
    widgetButtonReact(rEyeWidget, ui->rightEyeCam->isChecked());
}

void MainWindow::on_fieldCam_clicked()
{
    widgetButtonReact(fieldWidget, ui->fieldCam->isChecked());
}

void MainWindow::on_gazeEstimation_clicked()
{
    widgetButtonReact(gazeEstimationWidget, ui->gazeEstimation->isChecked());
}

Thiago Santini's avatar
Thiago Santini committed
524 525 526 527 528
void MainWindow::on_log_clicked()
{
    widgetButtonReact(logWidget, ui->log->isChecked());
}

529 530 531 532 533
void MainWindow::on_performanceMonitor_clicked()
{
    widgetButtonReact(performanceMonitorWidget, ui->performanceMonitor->isChecked());
}

534 535 536 537 538
void MainWindow::on_postProcessing_clicked()
{
    widgetButtonReact(postProcessingWidget, ui->postProcessing->isChecked());
}

539
void MainWindow::freezeCameraImages()
Thiago Santini's avatar
Thiago Santini committed
540
{
541 542
    disconnect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)),
        fieldWidget, SLOT(preview(DataTuple)));
Thiago Santini's avatar
Thiago Santini committed
543 544
}

545
void MainWindow::unfreezeCameraImages()
Thiago Santini's avatar
Thiago Santini committed
546
{
547 548
    connect(gazeEstimationWidget, SIGNAL(outDataTuple(DataTuple)),
        fieldWidget, SLOT(preview(DataTuple)));
Thiago Santini's avatar
Thiago Santini committed
549 550 551 552 553 554 555 556 557 558 559 560
}

void MainWindow::menuOption(QAction* action)
{
    if (action->text().toLower() == "references")
        showReferencesDialog();
    if (action->text().toLower() == "about")
        showAboutDialog();
}

void MainWindow::showReferencesDialog()
{
561 562 563 564 565 566 567 568 569
    ReferenceList::add("Santini et al.",
        "PuReST: Robust pupil tracking for real-time pervasive eye tracking",
        "ETRA", "2018b",
        "https://doi.org/10.1145/3204493.3204578");
    ReferenceList::add("Santini et al.",
        "PuRe: Robust pupil detection for real-time pervasive eye tracking",
        "CVIU", "2018a",
        "https://www.sciencedirect.com/science/article/pii/S1077314218300146");
    ReferenceList::add("Santini et al.",
Thiago Santini's avatar
Thiago Santini committed
570 571
        "EyeRecToo: Open-source Software for Real-time Pervasive Head-mounted Eye Tracking",
        "VISAPP", "2017a",
572 573
        "http://www.scitepress.org/DigitalLibrary/PublicationsDetail.aspx?ID=gLeoir7PxnI=&t=1");
    ReferenceList::add("Santini et al.",
Thiago Santini's avatar
Thiago Santini committed
574 575
        "CalibMe: Fast and Unsupervised Eye Tracker Calibration for Gaze-Based Pervasive Human-Computer Interaction",
        "CHI", "2017b",
576 577
        "http://dl.acm.org/citation.cfm?id=3025453.3025950");
    ReferenceList::add("Fuhl et al.",
Thiago Santini's avatar
Thiago Santini committed
578 579
        "ElSe: Ellipse Selection for Robust Pupil Detection in Real-World Environments",
        "ETRA", "2016",
580 581
        "http://dl.acm.org/citation.cfm?id=2857505");
    ReferenceList::add("Fuhl et al.",
Thiago Santini's avatar
Thiago Santini committed
582 583
        "ExCuSe: Robust Pupil Detection in Real-World Scenarios",
        "LNCS", "2015",
584
        "https://link.springer.com/chapter/10.1007/978-3-319-23192-1_4");
Thiago Santini's avatar
Thiago Santini committed
585
#ifdef STARBURST
586
    ReferenceList::add("Li et al.",
Thiago Santini's avatar
Thiago Santini committed
587 588
        "Starburst: A Hybrid Algorithm for Video-based Eye Tracking Combining Feature-based and Model-based Approaches",
        "CVPR", "2005",
589
        "http://ieeexplore.ieee.org/abstract/document/1565386/");
Thiago Santini's avatar
Thiago Santini committed
590 591
#endif
#ifdef SWIRSKI
592
    ReferenceList::add("Swirski et al.",
Thiago Santini's avatar
Thiago Santini committed
593 594
        "Robust real-time pupil tracking in highly off-axis images",
        "ETRA", "2012",
595
        "http://dl.acm.org/citation.cfm?id=2168585");
Thiago Santini's avatar
Thiago Santini committed
596
#endif
597
    ReferenceList::add("Garrido-Jurado et al.",
Thiago Santini's avatar
Thiago Santini committed
598 599
        "Automatic generation and detection of highly reliable fiducial markers under occlusion",
        "Pattern Recognition", "2014",
600 601
        "http://dl.acm.org/citation.cfm?id=2589359");
    ReferenceList::add("Bradski et al.",
Thiago Santini's avatar
Thiago Santini committed
602 603
        "OpenCV",
        "Dr. Dobb’s Journal of Software Tools", "2000",
604 605
        "http://www.drdobbs.com/open-source/the-opencv-library/184404319");
    ReferenceList::add("Qt Project",
Thiago Santini's avatar
Thiago Santini committed
606 607
        "Qt Framework",
        "Online", "2017",
608
        "http://www.qt.io");
Thiago Santini's avatar
Thiago Santini committed
609 610 611 612 613 614 615 616

    QString msg("EyeRecToo utilizes methods developed by multiple people. ");
    msg.append("This section provides a list of these methods so you can easily cite the ones you use :-)<br><br><br>");
    QMessageBox::information(this, "References", msg.append(ReferenceList::text()), QMessageBox::NoButton);
}

void MainWindow::showAboutDialog()
{
617
    QString msg = QString("EyeRecToo v%1<br><br>").arg(VERSION);
Thiago Santini's avatar
Thiago Santini committed
618
    msg.append("Contact: <a href=\"mailto:thiago.santini@uni-tuebingen.de?Subject=[EyeRecToo] Contact\" target=\"_top\">thiago.santini@uni-tuebingen.de</a><br><br>");
619
    msg.append("Copyright &copy; 2019 Thiago Santini<br><br>");
620
    msg.append(QString("Build: %1 %2").arg(GIT_BRANCH).arg(GIT_COMMIT_HASH));
621 622 623 624 625 626 627 628 629 630
    msg.append("<br><br>");
    msg.append("EyeRecToo dynamically links to:<br>");
    msg.append("-Qt (LGPL)<br>");
    msg.append("-FFmpeg (LGPL)<br>");
    msg.append("-Eigen (LGPL)<br>");
    msg.append("-libusb (LGPL)<br>");
    msg.append("-pthreads4w (LGPL)<br>");
    msg.append("-OpenCV (3-clause BSD)<br>");
    msg.append("-libjpeg-turbo (3-clause BSD)<br>");
    msg.append("-libuvc (BSD)<br>");
631
    QMessageBox::about(this, "About", msg);
Thiago Santini's avatar
Thiago Santini committed
632
}
633

634
void MainWindow::setupWidget(ERWidget* widget, QSettings* settings, QPushButton* button)
635
{
636 637 638
    // TODO: we might consider eventually moving each ERWidget settings to their own file
    widget->load(settings);
    widget->setup();
Thiago Santini's avatar
Thiago Santini committed
639

640
    // Sanitize position
641 642
    bool inScreen = false;
    for (int i = 0; i < QApplication::desktop()->screenCount(); i++)
643
        inScreen |= QApplication::desktop()->screenGeometry(i).contains(widget->pos(), true);
644
    if (!inScreen)
645
        widget->move(QApplication::desktop()->screenGeometry().topLeft());
646

647 648 649 650 651
    if (button) {
        button->setChecked(widget->isVisible());
        connect(widget, SIGNAL(closed(bool)),
            button, SLOT(setChecked(bool)));
    }
652

653 654 655 656
    connect(widget, SIGNAL(keyPress(QKeyEvent*)),
        &commandManager, SLOT(keyPress(QKeyEvent*)));
    connect(widget, SIGNAL(keyRelease(QKeyEvent*)),
        &commandManager, SLOT(keyRelease(QKeyEvent*)));
657
}
658

Thiago Santini's avatar
Thiago Santini committed
659
void MainWindow::toggleRemoteRecording()
660
{
661 662 663 664
    // If the subject name is empty, use the remote label
    if (!ui->recordingToggle->isChecked() && ui->subject->text().isEmpty())
        setSubjectName("remote");
    ui->recordingToggle->click();
665
}
666 667 668

void MainWindow::togglePreview()
{
669
    gFreezePreview = !gFreezePreview;
670
}
Thiago Santini's avatar
Thiago Santini committed
671 672 673

void MainWindow::storeMetaDataHead()
{
674 675 676 677 678 679
    QFile file(metaDataFile);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream out(&file);

    QDateTime utc = QDateTime::currentDateTimeUtc();
680
    out << "start_utc" << Token::Delimiter << utc.toString(metaDateFormat) << Token::Newline;
Thiago Santini's avatar
Thiago Santini committed
681
    out << "start_timer" << Token::Delimiter << format(gTimer.elapsed()) << Token::Newline;
682 683 684 685 686 687 688 689 690
    out << "version" << Token::Delimiter << VERSION << Token::Newline;
    out << "build" << Token::Delimiter << QString("%1 %2").arg(GIT_BRANCH).arg(GIT_COMMIT_HASH) << Token::Newline;

    QSysInfo system;
    (void)system; // MSVC is giving an unused variable warning so let's satisfy it...
    out << "OS" << Token::Delimiter << QString("%1 %2").arg(system.productType()).arg(system.productVersion()) << Token::Newline;
    out << "Host" << Token::Delimiter << system.machineHostName() << Token::Newline;

    file.close();
Thiago Santini's avatar
Thiago Santini committed
691 692 693 694
}

void MainWindow::storeMetaDataTail()
{
695 696 697 698
    QFile file(metaDataFile);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
        return;
    QTextStream out(&file);
Thiago Santini's avatar
Thiago Santini committed
699

700
    QDateTime utc = QDateTime::currentDateTimeUtc();
701
    out << "end_utc" << Token::Delimiter << utc.toString(metaDateFormat) << Token::Newline;
Thiago Santini's avatar
Thiago Santini committed
702
    out << "end_timer" << Token::Delimiter << format(gTimer.elapsed()) << Token::Newline;
Thiago Santini's avatar
Thiago Santini committed
703

704
    file.close();
Thiago Santini's avatar
Thiago Santini committed
705
}