Skip to content
Snippets Groups Projects
MainWindow.cpp 39.33 KiB
#include "MainWindow.h"
#include "TMath.h"
#include "TString.h"
//#include <iostream>
//#include "TList.h"
#include "TStyle.h"
#include "TFile.h"
#include "TTree.h"
#include "TGraph.h"
#include "TAxis.h"

#include "qrootcanvas.h"
#include <QVBoxLayout>
#include <TSystem.h>
#include <QTimer>
#include <QDebug>
#include <QWindow>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
#include <QDoubleValidator>
#include <QIntValidator>
#include <QRegExpValidator>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    // General
    connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::handleOpenProj);
    connect(ui->actionExit, &QAction::triggered, this, &MainWindow::handleClose);
    connect(ui->actionSave, &QAction::triggered, this, &MainWindow::handleSave);
    connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::handleSaveAs);
    connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::handleAbout);
    connect(ui->actionOpenPlot, &QAction::triggered, this, &MainWindow::handleOpenplot);


    connect(ui->actionStart, &QAction::triggered, this, &MainWindow::handleStart);
    connect(ui->actionPause, &QAction::triggered, this, &MainWindow::handlePause);
    connect(ui->actionStop, &QAction::triggered, this, &MainWindow::handleStop);

    // accepts user input and update config
    // quit worker thread and clear plot data immediately when any settings are changed
    connect(ui->openProject, &QPushButton::clicked, this, &MainWindow::handleOpenProj);
    connect(ui->openFileButton, &QPushButton::clicked, this, &MainWindow::onOpenFileClicked);
    ui->runIDInput->setValidator(new QRegExpValidator( QRegExp("[A-Za-z0-9_]+"), ui->runIDInput));
    connect(ui->runIDInput, &QLineEdit::editingFinished, this, &MainWindow::onRunIDInput);
    ui->maxEventNumInput->setValidator(new QIntValidator(1, 10000000,ui->maxEventNumInput));
    connect(ui->maxEventNumInput, &QLineEdit::editingFinished, this, &MainWindow::onMaxEventNumInput);

    // Plot
    QDoubleValidator* rdv = new QDoubleValidator(1, 10000, 6, ui->radiusInput);
    rdv->setNotation(QDoubleValidator::StandardNotation);
    ui->radiusInput->setValidator(rdv);
    connect(ui->radiusInput, &QLineEdit::editingFinished, this, &MainWindow::onRadiusInput);

    ui->phiBinsInput->setValidator(new QIntValidator(1, 720, ui->phiBinsInput));
    QDoubleValidator* phimindv = new QDoubleValidator(-180, 180, 6, ui->phiMinInput);
    phimindv->setNotation(QDoubleValidator::StandardNotation);
    ui->phiMinInput->setValidator(phimindv);
    ui->phiMaxInput->setValidator(phimindv);
    connect(ui->phiBinsInput, &QLineEdit::editingFinished, this, &MainWindow::onPhiBinsInput);
    connect(ui->phiMinInput, &QLineEdit::editingFinished, this, &MainWindow::onPhiMinInput);
    connect(ui->phiMaxInput, &QLineEdit::editingFinished, this, &MainWindow::onPhiMaxInput);

    ui->thetaBinsInput->setValidator(new QIntValidator(1, 360, ui->thetaBinsInput));
    QDoubleValidator* thetamindv = new QDoubleValidator(-90, 90, 6, ui->thetaMinInput);
    thetamindv->setNotation(QDoubleValidator::StandardNotation);
    ui->thetaMinInput->setValidator(thetamindv);
    ui->thetaMaxInput->setValidator(thetamindv);
    connect(ui->thetaBinsInput, &QLineEdit::editingFinished, this, &MainWindow::onThetaBinsInput);
    connect(ui->thetaMinInput, &QLineEdit::editingFinished, this, &MainWindow::onThetaMinInput);
    connect(ui->thetaMaxInput, &QLineEdit::editingFinished, this, &MainWindow::onThetaMaxInput);

    ui->ergBinsInput->setValidator(new QIntValidator(1, 10000, ui->ergBinsInput));
    QDoubleValidator* ergmindv = new QDoubleValidator(0, 50, 6, ui->ergMinInput);
    ergmindv->setNotation(QDoubleValidator::StandardNotation);
    ui->ergMinInput->setValidator(ergmindv);
    ui->ergMaxInput->setValidator(ergmindv);
    connect(ui->ergBinsInput, &QLineEdit::editingFinished, this, &MainWindow::onErgBinsInput);
    connect(ui->ergMinInput, &QLineEdit::editingFinished, this, &MainWindow::onErgMinInput);
    connect(ui->ergMaxInput, &QLineEdit::editingFinished, this, &MainWindow::onErgMaxInput);

    QDoubleValidator* ergcutdv = new QDoubleValidator(0, 50, 6, ui->ergCutLowInput);
    ergcutdv->setNotation(QDoubleValidator::StandardNotation);
    ui->ergCutLowInput->setValidator(ergcutdv);
    ui->ergCutHighInput->setValidator(ergcutdv);
    connect(ui->enableErgCutBox, &QCheckBox::stateChanged, this, &MainWindow::onErgCutStateChanged);
    connect(ui->ergCutLowInput, &QLineEdit::editingFinished, this, &MainWindow::onErgCutLowInput);
    connect(ui->ergCutHighInput, &QLineEdit::editingFinished, this, &MainWindow::onErgCutHighInput);

    connect(ui->saveImageBox, &QCheckBox::stateChanged, this, &MainWindow::onSaveImageStateChanged);
    connect(ui->saveSpectrumBox, &QCheckBox::stateChanged, this, &MainWindow::onSaveSpectrumStateChanged);
    connect(ui->saveImageFormat, &QComboBox::currentTextChanged, this, &MainWindow::onsaveImageFormatChanged);
    connect(ui->saveSpectrumFormat, &QComboBox::currentTextChanged, this, &MainWindow::onsaveSpectrumFormatChanged);

    QDoubleValidator* tmedv = new QDoubleValidator(0, 1000, 6, ui->timeWindowInput);
    tmedv->setNotation(QDoubleValidator::StandardNotation);
    ui->timeWindowInput->setValidator(tmedv);
    connect(ui->timeWindowInput, &QLineEdit::editingFinished, this, &MainWindow::onTimeWindowInput);

    // Mapping
    collectWidgets();
    QIntValidator* chNumv = new QIntValidator(0, 1024, chNumInputs[0]);
    for (int i=0; i<chNumInputs.size(); i++) {
        chNumInputs[i]->setValidator(chNumv);
        connect(chNumInputs[i], &QLineEdit::editingFinished, this, &MainWindow::onChNumInput);
    }

    QDoubleValidator* posxdv = new QDoubleValidator(-10000, 10000, 6, posXInputs[0]);
    posxdv->setNotation(QDoubleValidator::StandardNotation);
    for (int i=0; i<posXInputs.size(); i++) {
        posXInputs[i]->setValidator(posxdv);
        connect(posXInputs[i], &QLineEdit::editingFinished, this, &MainWindow::onPosXInput);
    }

    QDoubleValidator* posydv = new QDoubleValidator(-10000, 10000, 6, posYInputs[0]);
    posydv->setNotation(QDoubleValidator::StandardNotation);
    for (int i=0; i<posYInputs.size(); i++) {
        posYInputs[i]->setValidator(posydv);
        connect(posYInputs[i], &QLineEdit::editingFinished, this, &MainWindow::onPosYInput);
    }

    QDoubleValidator* calidv = new QDoubleValidator(0, 10000, 6, caliCoefInputs[0]);
    calidv->setNotation(QDoubleValidator::StandardNotation);
    for (int i=0; i<caliCoefInputs.size(); i++) {
        caliCoefInputs[i]->setValidator(calidv);
        connect(caliCoefInputs[i], &QLineEdit::editingFinished, this, &MainWindow::onCaliCoefInput);
    }

    for (int i=0; i<chOnBoxes.size(); i++) {
        connect(chOnBoxes[i], &QCheckBox::stateChanged, this, &MainWindow::onChOnStateChanged);
    }

    // user applies the config
    // create a new worker thread, pop up a plot window
//    connect(ui->applySettingsButton, &QPushButton::clicked, this, &MainWindow::onConfigApplied);
    setWindowTitle(tr("Back projection"));
    qDebug() << "Attempting to open a project";
    if(!handleOpenProj())
    {
        qDebug() << "Cannot open project. Exit application.";
//        QCoreApplication::exit(1);
        exit(EXIT_FAILURE);
    }
    ui->statusBar->showMessage("Ready.");
}

void MainWindow::closeEvent (QCloseEvent *event)
{
    QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Close",
                                                               tr("Are you sure you want to quit?\n"),
                                                               QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes,
                                                               QMessageBox::Yes);
    if (resBtn != QMessageBox::Yes) {
        event->ignore();
    } else {
        if (workerThread) {
//            qDebug() << "Exiting thread..";
            // stop worker thread
//            emit threadStopped();
            ui->actionStop->trigger();
            gSystem->Sleep(500);
        }
        event->accept();
    }
}

MainWindow::~MainWindow()
{
    delete ui;
    if (config){
        qDebug() << "write settings on main window closed";
        config->writeSetup(config->outputDir.absolutePath() + "/settings.ini");
        delete config;
        qDebug() << "settings saved";
    }
    if (plotdata)
    {
        delete plotdata;
    }
}

void MainWindow::handleOpenplot()
{
    if(!plotwindow.isNull() && plotwindow->isVisible())
    {
        // bring to front
        plotwindow->raise();
        return;
    }
    qDebug() << "Open new plot window";
    if(!plotdata)
    {
        QMessageBox::critical(this, "Warning", "Apply the settings first by pressing start button.");
        return;
    }
    plotwindow = new QPlotWindow(this, config, plotdata);
    connect(plotwindow, &QPlotWindow::runStarted, ui->actionStart, &QAction::trigger);
    connect(plotwindow, &QPlotWindow::runPaused, ui->actionPause, &QAction::trigger);
    connect(plotwindow,&QPlotWindow::runStopped, ui->actionStop, &QAction::trigger);

    plotwindow->setWindowTitle("Plots");
    plotwindow->setAttribute(Qt::WA_DeleteOnClose);
    plotwindow->resize(plotwindow->sizeHint());
    plotwindow->show();
    plotwindow->showMaximized();
    // update every 1 second
    QTimer *timer = new QTimer(plotwindow);
    connect(timer, &QTimer::timeout, this->plotwindow, QOverload<>::of(&QPlotWindow::redraw));
    timer->start(1000);
}

bool MainWindow::handleOpenProj()
{
    qDebug() << "Open project clicked";
    if (config)
    {
        // save current settings
        config->writeSetup("settings.ini");
        // stop current run
        onConfigChanged();
    }
    // select project dir
    QFileDialog dialog;
    dialog.setWindowTitle("Open a new or existing project");
    dialog.setFileMode(QFileDialog::DirectoryOnly);
    dialog.setOption(QFileDialog::ShowDirsOnly, false);
    if(!dialog.exec())
        return false;

    QFileInfo proj_dir(dialog.directory().absolutePath());
    if (!proj_dir.isDir())
    {
        QMessageBox::critical(this, "Failed to open direcory", QString("%1 is not a directory.").arg(proj_dir.absolutePath()));
        return false;
    }
    if (!proj_dir.isWritable())
    {
        QMessageBox::critical(this, "Permission denied", QString("%1 is not writable.").arg(proj_dir.absolutePath()));
        return false;
    }
    //    qDebug() << QDir::current();

    // if settings.ini does not exist, copy the template settings.ini file to current dir
    QString settingsPath = dialog.directory().absolutePath() + "/settings.ini";
    if (!dialog.directory().exists("settings.ini"))
    {
        if(!QFile::copy(":/Data/Data/settings.ini", settingsPath)){
            QMessageBox::critical(this, "Cannot create file", QString("Cannot create settings.ini in %1").arg(proj_dir.absolutePath()));
            return false;
        }
    }
    // check read permission
    if(!QFileInfo(settingsPath).isReadable() && !QFile::setPermissions(settingsPath, QFileDevice::ReadOwner))
    {
        QMessageBox::critical(this, "Permission denied", QString("Cannot read settings.ini in %1").arg(proj_dir.absolutePath()));
        return false;
    }
    // check write permission
    if(!QFileInfo(settingsPath).isWritable() && !QFile::setPermissions(settingsPath, QFileDevice::WriteOwner))
    {
        QMessageBox::critical(this, "Permission denied", QString("Cannot write to settings.ini in %1").arg(proj_dir.absolutePath()));
        return false;
    }

    // set current path to this dir
    QDir::setCurrent(dialog.directory().absolutePath());
    // read .ini file in current dir
    if (!config) // start up
        config = new Setup("settings.ini");
    else {
        config->readSetup("settings.ini");
    }
    showConfig();
    return true;
}

bool MainWindow::handleSave()
{
    qDebug() << "Save clicked";
    if (config){
        config->writeSetup("settings.ini");
        ui->statusBar->showMessage("Project settings saved.");
        return true;
    }
    return false;
}

bool MainWindow::handleSaveAs()
{
    qDebug() << "Save as clicked";
    if (!config)
        return false;

    // save current settings
    config->writeSetup("settings.ini");
    // stop current run
    onConfigChanged();
    // select new dir
    QFileDialog dialog;
    dialog.setWindowTitle("Save as");
    dialog.setFileMode(QFileDialog::DirectoryOnly);
    dialog.setOption(QFileDialog::ShowDirsOnly, false);
    if(!dialog.exec())
        return false;

    QFileInfo proj_dir(dialog.directory().absolutePath());
    if (!proj_dir.isDir())
    {
        QMessageBox::critical(this, "Failed to open direcory", QString("%1 is not a directory.").arg(proj_dir.absolutePath()));
        return false;
    }
    if (!proj_dir.isWritable())
    {
        QMessageBox::critical(this, "Permission denied", QString("%1 is not writable.").arg(proj_dir.absolutePath()));
        return false;
    }
    //    qDebug() << QDir::current();

    // if settings.ini does not exist, copy the template settings.ini file to current dir
    QString settingsPath = dialog.directory().absolutePath() + "/settings.ini";
    if (!dialog.directory().exists("settings.ini"))
    {
        if(!QFile::copy("settings.ini", settingsPath)){
            QMessageBox::critical(this, "Cannot create file", QString("Cannot create settings.ini in %1").arg(proj_dir.absolutePath()));
            return false;
        }
    }
    // check read permission
    if(!QFileInfo(settingsPath).isReadable() && !QFile::setPermissions(settingsPath, QFileDevice::ReadOwner))
    {
        QMessageBox::critical(this, "Permission denied", QString("Cannot read settings.ini in %1").arg(proj_dir.absolutePath()));
        return false;
    }
    // check write permission
    if(!QFileInfo(settingsPath).isWritable() && !QFile::setPermissions(settingsPath, QFileDevice::WriteOwner))
    {
        QMessageBox::critical(this, "Permission denied", QString("Cannot write to settings.ini in %1").arg(proj_dir.absolutePath()));
        return false;
    }

    // set current path to this dir
    QDir::setCurrent(dialog.directory().absolutePath());
    // read .ini file in current dir
    config->readSetup("settings.ini");
    showConfig();
    ui->statusBar->showMessage(QString("Project saved to %1").arg(QDir::currentPath()));
    return true;
}

void MainWindow::handleClose()
{
    qDebug() << "Close clicked";
    this->close();
}

void MainWindow::handleAbout()
{
    QMessageBox::about(this, tr("About Application"),
             tr("This application is created using Qt 5.13.2. Source code is available at Gitlab."));
//    qDebug() << "About clicked";
}

void MainWindow::notifyThreadFinished()
{
    ui->statusBar->showMessage("Stopped");
    // pop up a message box indicating processing is finished.
//    if (!aborted)
//    {
//        QMessageBox messageBox;
//        messageBox.setText("Image reconstruction finished.");
//        messageBox.setWindowTitle("Finished");
//        messageBox.exec();
//    }
}

void MainWindow::handleStart()
{
    // if config is changed, or user pressed Stop button,
    // workerThread must be null.
    // Apply the new settings and start a new run
    // If it's not null,
    // then resume current run
    if (!workerThread.isNull())
    {
        emit workerStarted();
        ui->statusBar->showMessage("Running");
//        // save current setting
//        config->writeSetup(config->outputDir.absolutePath() + "/settings.ini");
//        qDebug() << "Write settings to " << config->outputDir.absolutePath();
    }
    else{
        onConfigApplied();
    }
}

void MainWindow::handlePause()
{
    if (!workerThread.isNull())
        ui->statusBar->showMessage("Paused.");
//    // save current setting
//    config->writeSetup(config->outputDir.absolutePath() + "/settings.ini");
//    qDebug() << "Write settings to " << config->outputDir.absolutePath();
}

void MainWindow::handleStop()
{
    if (!workerThread.isNull())
        ui->statusBar->showMessage("Stopped.");
    // save current setting
    config->writeSetup(config->outputDir.absolutePath() + "/settings.ini");
    qDebug() << "Write settings to " << config->outputDir.absolutePath();
}

void MainWindow::showConfig()
{
    // display settings in settings.ini file
    ui->projPathLabel->setText(QDir::currentPath());
    ui->filePathLabel->setText(config->filePath);
    ui->runIDInput->setText(config->runID);
    ui->maxEventNumInput->setText(QString::number(config->maxN));

    ui->saveImageBox->setChecked(config->saveImage);
    ui->saveSpectrumBox->setChecked(config->saveSpectrum);
    ui->saveImageFormat->setCurrentText(config->saveImageFormat);
    ui->saveSpectrumFormat->setCurrentText(config->saveSpectrumFormat);

    ui->radiusInput->setText(QString::number(config->R, 'f', 4));
    ui->phiBinsInput->setText(QString::number(config->phiBins));
    ui->phiMinInput->setText(QString::number(config->phiMin * 180 / M_PI, 'f', 4));
    ui->phiMaxInput->setText(QString::number(config->phiMax * 180 / M_PI, 'f', 4));
    ui->thetaBinsInput->setText(QString::number(config->thetaBins));
    ui->thetaMinInput->setText(QString::number(config->thetaMin * 180 / M_PI, 'f', 4));
    ui->thetaMaxInput->setText(QString::number(config->thetaMax * 180 / M_PI, 'f', 4));

    ui->ergBinsInput->setText(QString::number(config->ergBins));
    ui->ergMinInput->setText(QString::number(config->ergMin, 'f', 4));
    ui->ergMaxInput->setText(QString::number(config->ergMax, 'f', 4));

    ui->enableErgCutBox->setChecked(config->energyCut);
    ui->ergCutLowInput->setText(QString::number(config->energyLow, 'f', 4));
    ui->ergCutHighInput->setText(QString::number(config->energyUp, 'f', 4));

    ui->timeWindowInput->setText(QString::number(config->timeWindow, 'f', 4));

    for (int i=0; i<config->channelSettings.size() && i< chNumInputs.size(); i++) {
        chNumInputs[i]->setText(QString::number(config->channelSettings[i].chNum));
        chNumInputs[i]->setReadOnly(true);

        posXInputs[i]->setText(QString::number(config->channelSettings[i].x, 'f', 4));
        posXInputs[i]->setReadOnly(true);

        posYInputs[i]->setText(QString::number(config->channelSettings[i].y, 'f', 4));
        posYInputs[i]->setReadOnly(true);

        caliCoefInputs[i]->setText(QString::number(config->channelSettings[i].caliCoef, 'f', 4));
//        posXInputs[i]->setReadOnly(true);

        chOnBoxes[i]->setChecked(config->channelSettings[i].enabled);
    }

}


bool MainWindow::checkConfig()
{
    // check if file exists
    if (!QFile(config->filePath).exists()){
        QMessageBox::critical(this, "File not exist", QString("Cannot open file %1").arg(config->filePath));
        return false;
    }
    // check if runID already exists
    if (config->outputDir.exists()) {
        // check if it's writable
        if (!QFileInfo(config->outputDir.absolutePath()).isWritable())
        {
            QMessageBox::critical(this, "Permission denied", QString("Cannot write to directory %1").arg(config->outputDir.absolutePath()));
            return false;
        }
        QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Overwrite",
                                                                   QString("Run-%1 already exists. Are you sure you want to overwrite?\n").arg(config->runID),
                                                                   QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes,
                                                                   QMessageBox::Yes);
        if (resBtn != QMessageBox::Yes) {
            return false;
        }
    }
    else {
        // create a new dir for current ID
       if(!config->outputDir.mkpath(".")){
           QMessageBox::critical(this, "Permission denied", "Cannot create output directory for run "+config->runID);
           return false;
       }
       // check read permission
       QString outdirpath = config->outputDir.absolutePath();
       if(!QFileInfo(outdirpath).isReadable() && !QFile::setPermissions(outdirpath, QFileDevice::ReadOwner))
       {
           QMessageBox::critical(this, "Permission denied", QString("Cannot read settings.ini in %1").arg(outdirpath));
           return false;
       }
       // check write permission
       if(!QFileInfo(outdirpath).isWritable() && !QFile::setPermissions(outdirpath, QFileDevice::WriteOwner))
       {
           QMessageBox::critical(this, "Permission denied", QString("Cannot write to settings.ini in %1").arg(outdirpath));
           return false;
       }
    }

    if (config->phiMax <= config->phiMin)
    {
        QMessageBox::warning(this, "Warning", QString("Min phi must be smaller max theta."));
        return false;
    }
    if (config->thetaMax <= config->thetaMin)
    {
        QMessageBox::warning(this, "Warning", QString("Min theta must be smaller than max theta."));
        return false;
    }
    if (config->ergMax <= config->ergMin)
    {
        QMessageBox::warning(this, "Warning", QString("Min energy must be smaller than max energy."));
        return false;
    }
    if (config->energyCut && config->energyUp <= config->energyLow)
    {
        QMessageBox::warning(this, "Warning", QString("Energy low cut must be smaller than energy high cut."));
        return false;
    }

    // check for duplicates
    // lazy nested loop
    for (int i=0; i<config->channelSettings.size(); i++) {
        for (int j=i+1; j<config->channelSettings.size(); j++) {
            if (config->channelSettings[j].chNum == config->channelSettings[i].chNum){
                QMessageBox::warning(this, "Duplicated channel number.",
                                     QString("Channel-%1 and channel-%2 have same channel number.").arg(QString::number(i+1), QString::number(j+1)));
                return false;
            }
        }
    }

    return true;
}

void MainWindow::onConfigChanged()
{
    // quit worker thread, save plot data
    if (!workerThread.isNull()) {
        ui->actionStop->trigger();
        gSystem->Sleep(500);
        ui->statusBar->showMessage("Stopped.");
    }
}

void MainWindow::onConfigApplied()
{
    // user press Start button

    // if config is not changed, i.e., worker thread is running
    // do nothing
    if (!workerThread.isNull()){
        return;
    }
    // otherwise, updating config is finished
    // check config
    if (!checkConfig())
        return;
    // create plot data if not already
    if (!plotdata)
        plotdata = new PlotData(config);
    else {
        // clear plot data
        plotdata->clearAll();
    }
    // check if image space changed
    if (config->imageSpacechaged){
        if (plotdata){
            plotdata->rebinImage();
            plotdata->rebinSpectrum();
        }
        config->imageSpacechaged = false;
    }
    // pop up plot window
    if (plotwindow.isNull() || !plotwindow->isVisible()){
        plotwindow = new QPlotWindow(this, config, plotdata);
        handleOpenplot();
    }
    // start worker thread
    if (workerThread.isNull())
    {
        // update image on worker thread
        workerThread = new Worker(this, config, plotdata);
        connect(workerThread, &Worker::finished, workerThread, &QObject::deleteLater);
        connect(workerThread, &Worker::finished, this, &MainWindow::notifyThreadFinished);
        connect(this, &MainWindow::workerStarted, workerThread, &Worker::handleStart);
        connect(ui->actionPause, &QAction::triggered, workerThread, &Worker::handlePause);
        connect(ui->actionStop, &QAction::triggered, workerThread, &Worker::handleStop);
        workerThread->start();
    }

    ui->statusBar->showMessage("Running.");
//    // save current setting
//    config->writeSetup(config->outputDir.absolutePath() + "/settings.ini");
//    qDebug() << "Write settings to " << config->outputDir.absolutePath();
}

// General
void MainWindow::onOpenFileClicked()
{
    // open data file for processing
    QString fileName = QFileDialog::getOpenFileName(this,
        tr("Open Data File"), QDir::currentPath(), tr("Text Files (*.txt)"));
    if (!fileName.isEmpty() && fileName!=config->filePath){
        onConfigChanged();
        config->filePath = fileName;
        ui->filePathLabel->setText(config->filePath);
    }
}

void MainWindow::onRunIDInput()
{
    // set run id
    QString newid = ui->runIDInput->text();
    if (newid != config->runID){
        onConfigChanged();
        config->runID = newid;
        config->outputDir = QDir(newid);
    }
}

void MainWindow::onMaxEventNumInput()
{
    int eventNum = ui->maxEventNumInput->text().toInt();
    if (eventNum != config->maxN){
        onConfigChanged();
        config->maxN = eventNum;
    }
}

// Plot
void MainWindow::onRadiusInput()
{
    // set radius of projection sphere
    double newradius = ui->radiusInput->text().toDouble();
    if (!doubleEqual(newradius, config->R)){
        onConfigChanged();
        config->R = newradius;
        config->imageSpacechaged = true;
    }
}

void MainWindow::onPhiBinsInput()
{
    int phibins = ui->phiBinsInput->text().toInt();
    if (phibins != config->phiBins){
        onConfigChanged();
        config->phiBins = phibins;
        config->imageSpacechaged = true;
    }
}
void MainWindow::onPhiMinInput()
{
    double phimin = ui->phiMinInput->text().toDouble();
    phimin *= M_PI / 180;
    if (!doubleEqual(phimin, config->phiMin)){
        onConfigChanged();
        config->phiMin = phimin;
        config->imageSpacechaged = true;
    }
}
void MainWindow::onPhiMaxInput()
{
    double phimax = ui->phiMaxInput->text().toDouble();
    phimax *= M_PI / 180;
    if (!doubleEqual(phimax, config->phiMax)){
        onConfigChanged();
        config->phiMax = phimax;
        config->imageSpacechaged = true;
    }
}
void MainWindow::onThetaBinsInput()
{
    int thetabins = ui->thetaBinsInput->text().toInt();
    if (thetabins != config->thetaBins){
        onConfigChanged();
        config->thetaBins = thetabins;
        config->imageSpacechaged = true;
    }
}
void MainWindow::onThetaMinInput()
{
    double thetamin = ui->thetaMinInput->text().toDouble();
    thetamin *= M_PI / 180;
    if (!doubleEqual(thetamin, config->thetaMin)){
        onConfigChanged();
        config->thetaMin = thetamin;
        config->imageSpacechaged = true;
    }
}
void MainWindow::onThetaMaxInput()
{
    double thetamax = ui->thetaMaxInput->text().toDouble();
    thetamax *= M_PI / 180;
    if (!doubleEqual(thetamax, config->thetaMax)){
        onConfigChanged();
        config->thetaMax = thetamax;
        config->imageSpacechaged = true;
    }
}

void MainWindow::onErgBinsInput()
{
    int ergBins = ui->ergBinsInput->text().toInt();
    if (ergBins != config->ergBins){
        onConfigChanged();
        config->ergBins = ergBins;
    }
}

void MainWindow::onErgMinInput()
{
    double ergMin = ui->ergMinInput->text().toDouble();
    if (!doubleEqual(ergMin, config->ergMin))
    {
        onConfigChanged();
        config->ergMin = ergMin;
    }
}

void MainWindow::onErgMaxInput()
{
    double ergMax = ui->ergMaxInput->text().toDouble();
    if (!doubleEqual(ergMax, config->ergMax))
    {
        onConfigChanged();
        config->ergMax = ergMax;
    }
}

void MainWindow::onErgCutStateChanged()
{
    bool ergCutEnabled = ui->enableErgCutBox->isChecked();
    if (ergCutEnabled != config->energyCut)
    {
        onConfigChanged();
        config->energyCut = ergCutEnabled;
    }
}

void MainWindow::onErgCutLowInput()
{
    double ergLow = ui->ergCutLowInput->text().toDouble();
    if (!doubleEqual(ergLow, config->energyLow))
    {
        onConfigChanged();
        config->energyLow = ergLow;
    }
}

void MainWindow::onErgCutHighInput()
{
    double ergHigh = ui->ergCutHighInput->text().toDouble();
    if (!doubleEqual(ergHigh, config->energyUp))
    {
        onConfigChanged();
        config->energyUp = ergHigh;
    }
}


void MainWindow::onSaveImageStateChanged()
{
    bool saveImage = ui->saveImageBox->isChecked();
    if (saveImage != config->saveImage)
    {
        onConfigChanged();
        config->saveImage = saveImage;
    }
}

void MainWindow::onsaveImageFormatChanged(const QString text)
{
    if (text != config->saveImageFormat)
    {
        onConfigChanged();
        config->saveImageFormat = text;
    }
}

void MainWindow::onSaveSpectrumStateChanged()
{
    bool saveSpectrum = ui->saveSpectrumBox->isChecked();
    if (saveSpectrum != config->saveSpectrum)
    {
        onConfigChanged();
        config->saveSpectrum = saveSpectrum;
    }
}

void MainWindow::onsaveSpectrumFormatChanged(const QString text)
{
    if (text != config->saveSpectrumFormat)
    {
        onConfigChanged();
        config->saveSpectrumFormat = text;
    }
}

void MainWindow::onTimeWindowInput()
{
    double tmewin = ui->timeWindowInput->text().toDouble();
    if (!doubleEqual(tmewin, config->timeWindow))
    {
        onConfigChanged();
        config->timeWindow = tmewin;
    }
}

void MainWindow::onChNumInput()
{
    // find index of sender
    QLineEdit* sender_ = qobject_cast<QLineEdit*>(sender());
    if (!sender_)
        return;

    int newChNum = sender_->text().toInt();
    int sender_id = chNumInputs.indexOf(sender_);
    qDebug() << "Index of activated entry = " << sender_id;
    if (sender_id >= config->channelSettings.size()){
        qDebug() << "Add new channel";
        // check duplicates
        for (int i=0; i<config->channelSettings.size(); i++) {
            if (newChNum == config->channelSettings[i].chNum){
                QMessageBox::warning(this, "Duplicated channel number.", QString("Channel-%1 and channel-%2 have same channel number.").arg(QString::number(i+1), QString::number(sender_id+1)));
                return;
            }
        }
        onConfigChanged();
        Channel c;
        c.chNum = newChNum;
        config->channelSettings.append(c);
    }
    else if (newChNum != config->channelSettings[sender_id].chNum)
    {
        onConfigChanged();
        qDebug() << "Old channel num is " << config->channelSettings[sender_id].chNum;
        config->channelSettings[sender_id].chNum = newChNum;
        qDebug() << "New channel num is " << config->channelSettings[sender_id].chNum;
    }
}

void MainWindow::onPosXInput()
{
    // find index of sender
    QLineEdit* sender_ = qobject_cast<QLineEdit*>(sender());
    if (!sender_)
        return;

    double newposx = sender_->text().toDouble();
    int sender_id = posXInputs.indexOf(sender_);
    qDebug() << "Index of activated entry = " << sender_id;
    if (sender_id >= config->channelSettings.size()){
        qDebug() << "Add new channel";
        onConfigChanged();
        Channel c;
        c.x = newposx;
        config->channelSettings.append(c);
    }
    else if (!doubleEqual(newposx, config->channelSettings[sender_id].x))
    {
        onConfigChanged();
        qDebug() << "Old x is " << config->channelSettings[sender_id].x;
        config->channelSettings[sender_id].x = newposx;
        qDebug() << "New x is " << config->channelSettings[sender_id].x;
    }
}

void MainWindow::onPosYInput()
{
    // find index of sender
    QLineEdit* sender_ = qobject_cast<QLineEdit*>(sender());
    if (!sender_)
        return;

    double newposy = sender_->text().toDouble();
    int sender_id = posYInputs.indexOf(sender_);
    qDebug() << "Index of activated entry = " << sender_id;
    if (sender_id >= config->channelSettings.size()){
        qDebug() << "Add new channel";
        onConfigChanged();
        Channel c;
        c.y = newposy;
        config->channelSettings.append(c);
    }
    else if (!doubleEqual(newposy, config->channelSettings[sender_id].y))
    {
        onConfigChanged();
        qDebug() << "Old y is " << config->channelSettings[sender_id].y;
        config->channelSettings[sender_id].y = newposy;
        qDebug() << "New y is " << config->channelSettings[sender_id].y;
    }
}

void MainWindow::onCaliCoefInput()
{
    // find index of sender
    QLineEdit* sender_ = qobject_cast<QLineEdit*>(sender());
    if (!sender_)
        return;

    double newcalicoef = sender_->text().toDouble();
    int sender_id = caliCoefInputs.indexOf(sender_);
    qDebug() << "Index of activated entry = " << sender_id;
    if (sender_id >= config->channelSettings.size()){
        qDebug() << "Add new channel";
        onConfigChanged();
        Channel c;
        c.caliCoef = newcalicoef;
        config->channelSettings.append(c);
    }
    else if (!doubleEqual(newcalicoef, config->channelSettings[sender_id].caliCoef))
    {
        onConfigChanged();
        qDebug() << "Old cali coef is " << config->channelSettings[sender_id].caliCoef;
        config->channelSettings[sender_id].caliCoef = newcalicoef;
        qDebug() << "New cali coef is " << config->channelSettings[sender_id].caliCoef;
    }
}

void MainWindow::onChOnStateChanged()
{
    // find index of sender
    QCheckBox* sender_ = qobject_cast<QCheckBox*>(sender());
    if (!sender_)
        return;

    bool newStat = sender_->isChecked();
    int sender_id = chOnBoxes.indexOf(sender_);
    qDebug() << "Index of activated entry = " << sender_id;
    if (sender_id >= config->channelSettings.size()){
        qDebug() << "Add new channel";
        onConfigChanged();
        Channel c;
        c.enabled = newStat;
        config->channelSettings.append(c);
    }
    else if (newStat != config->channelSettings[sender_id].enabled)
    {
        onConfigChanged();
        qDebug() << "Old channel state is " << config->channelSettings[sender_id].enabled;
        config->channelSettings[sender_id].enabled = newStat;
        qDebug() << "New channelstate is " << config->channelSettings[sender_id].enabled;
    }
}

void MainWindow::collectWidgets()
{
    chNumInputs.append(ui->channelNumInput_1);
    chNumInputs.append(ui->channelNumInput_2);
    chNumInputs.append(ui->channelNumInput_3);
    chNumInputs.append(ui->channelNumInput_4);
    chNumInputs.append(ui->channelNumInput_5);
    chNumInputs.append(ui->channelNumInput_6);
    chNumInputs.append(ui->channelNumInput_7);
    chNumInputs.append(ui->channelNumInput_8);
    chNumInputs.append(ui->channelNumInput_9);
    chNumInputs.append(ui->channelNumInput_10);
    chNumInputs.append(ui->channelNumInput_11);
    chNumInputs.append(ui->channelNumInput_12);
    chNumInputs.append(ui->channelNumInput_13);
    chNumInputs.append(ui->channelNumInput_14);
    chNumInputs.append(ui->channelNumInput_15);
    chNumInputs.append(ui->channelNumInput_16);
    chNumInputs.append(ui->channelNumInput_17);
    chNumInputs.append(ui->channelNumInput_18);
    chNumInputs.append(ui->channelNumInput_19);
    chNumInputs.append(ui->channelNumInput_20);
    chNumInputs.append(ui->channelNumInput_21);
    chNumInputs.append(ui->channelNumInput_22);
    chNumInputs.append(ui->channelNumInput_23);
    chNumInputs.append(ui->channelNumInput_24);
    chNumInputs.append(ui->channelNumInput_25);
    chNumInputs.append(ui->channelNumInput_26);
    chNumInputs.append(ui->channelNumInput_27);
    chNumInputs.append(ui->channelNumInput_28);

    posXInputs.append(ui->posXInput_1);
    posXInputs.append(ui->posXInput_2);
    posXInputs.append(ui->posXInput_3);
    posXInputs.append(ui->posXInput_4);
    posXInputs.append(ui->posXInput_5);
    posXInputs.append(ui->posXInput_6);
    posXInputs.append(ui->posXInput_7);
    posXInputs.append(ui->posXInput_8);
    posXInputs.append(ui->posXInput_9);
    posXInputs.append(ui->posXInput_10);
    posXInputs.append(ui->posXInput_11);
    posXInputs.append(ui->posXInput_12);
    posXInputs.append(ui->posXInput_13);
    posXInputs.append(ui->posXInput_14);
    posXInputs.append(ui->posXInput_15);
    posXInputs.append(ui->posXInput_16);
    posXInputs.append(ui->posXInput_17);
    posXInputs.append(ui->posXInput_18);
    posXInputs.append(ui->posXInput_19);
    posXInputs.append(ui->posXInput_20);
    posXInputs.append(ui->posXInput_21);
    posXInputs.append(ui->posXInput_22);
    posXInputs.append(ui->posXInput_23);
    posXInputs.append(ui->posXInput_24);
    posXInputs.append(ui->posXInput_25);
    posXInputs.append(ui->posXInput_26);
    posXInputs.append(ui->posXInput_27);
    posXInputs.append(ui->posXInput_28);

    posYInputs.append(ui->posYInput_1);
    posYInputs.append(ui->posYInput_2);
    posYInputs.append(ui->posYInput_3);
    posYInputs.append(ui->posYInput_4);
    posYInputs.append(ui->posYInput_5);
    posYInputs.append(ui->posYInput_6);
    posYInputs.append(ui->posYInput_7);
    posYInputs.append(ui->posYInput_8);
    posYInputs.append(ui->posYInput_9);
    posYInputs.append(ui->posYInput_10);
    posYInputs.append(ui->posYInput_11);
    posYInputs.append(ui->posYInput_12);
    posYInputs.append(ui->posYInput_13);
    posYInputs.append(ui->posYInput_14);
    posYInputs.append(ui->posYInput_15);
    posYInputs.append(ui->posYInput_16);
    posYInputs.append(ui->posYInput_17);
    posYInputs.append(ui->posYInput_18);
    posYInputs.append(ui->posYInput_19);
    posYInputs.append(ui->posYInput_20);
    posYInputs.append(ui->posYInput_21);
    posYInputs.append(ui->posYInput_22);
    posYInputs.append(ui->posYInput_23);
    posYInputs.append(ui->posYInput_24);
    posYInputs.append(ui->posYInput_25);
    posYInputs.append(ui->posYInput_26);
    posYInputs.append(ui->posYInput_27);
    posYInputs.append(ui->posYInput_28);

    chOnBoxes.append(ui->channelOnBox_1);
    chOnBoxes.append(ui->channelOnBox_2);
    chOnBoxes.append(ui->channelOnBox_3);
    chOnBoxes.append(ui->channelOnBox_4);
    chOnBoxes.append(ui->channelOnBox_5);
    chOnBoxes.append(ui->channelOnBox_6);
    chOnBoxes.append(ui->channelOnBox_7);
    chOnBoxes.append(ui->channelOnBox_8);
    chOnBoxes.append(ui->channelOnBox_9);
    chOnBoxes.append(ui->channelOnBox_10);
    chOnBoxes.append(ui->channelOnBox_11);
    chOnBoxes.append(ui->channelOnBox_12);
    chOnBoxes.append(ui->channelOnBox_13);
    chOnBoxes.append(ui->channelOnBox_14);
    chOnBoxes.append(ui->channelOnBox_15);
    chOnBoxes.append(ui->channelOnBox_16);
    chOnBoxes.append(ui->channelOnBox_17);
    chOnBoxes.append(ui->channelOnBox_18);
    chOnBoxes.append(ui->channelOnBox_19);
    chOnBoxes.append(ui->channelOnBox_20);
    chOnBoxes.append(ui->channelOnBox_21);
    chOnBoxes.append(ui->channelOnBox_22);
    chOnBoxes.append(ui->channelOnBox_23);
    chOnBoxes.append(ui->channelOnBox_24);
    chOnBoxes.append(ui->channelOnBox_25);
    chOnBoxes.append(ui->channelOnBox_26);
    chOnBoxes.append(ui->channelOnBox_27);
    chOnBoxes.append(ui->channelOnBox_28);

    caliCoefInputs.append(ui->caliCoefInput_1);
    caliCoefInputs.append(ui->caliCoefInput_2);
    caliCoefInputs.append(ui->caliCoefInput_3);
    caliCoefInputs.append(ui->caliCoefInput_4);
    caliCoefInputs.append(ui->caliCoefInput_5);
    caliCoefInputs.append(ui->caliCoefInput_6);
    caliCoefInputs.append(ui->caliCoefInput_7);
    caliCoefInputs.append(ui->caliCoefInput_8);
    caliCoefInputs.append(ui->caliCoefInput_9);
    caliCoefInputs.append(ui->caliCoefInput_10);
    caliCoefInputs.append(ui->caliCoefInput_11);
    caliCoefInputs.append(ui->caliCoefInput_12);
    caliCoefInputs.append(ui->caliCoefInput_13);
    caliCoefInputs.append(ui->caliCoefInput_14);
    caliCoefInputs.append(ui->caliCoefInput_15);
    caliCoefInputs.append(ui->caliCoefInput_16);
    caliCoefInputs.append(ui->caliCoefInput_17);
    caliCoefInputs.append(ui->caliCoefInput_18);
    caliCoefInputs.append(ui->caliCoefInput_19);
    caliCoefInputs.append(ui->caliCoefInput_20);
    caliCoefInputs.append(ui->caliCoefInput_21);
    caliCoefInputs.append(ui->caliCoefInput_22);
    caliCoefInputs.append(ui->caliCoefInput_23);
    caliCoefInputs.append(ui->caliCoefInput_24);
    caliCoefInputs.append(ui->caliCoefInput_25);
    caliCoefInputs.append(ui->caliCoefInput_26);
    caliCoefInputs.append(ui->caliCoefInput_27);
    caliCoefInputs.append(ui->caliCoefInput_28);
}

bool MainWindow::doubleEqual(const double& a, const double & b, const double epsilon)
{
    return std::abs(a-b)<=epsilon;
}