/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


#include <omp.h>
#include <cstring>
#include <iostream>

////////////////////// Qt includes
#include <QtGui>
#include <QtGlobal>
#include <QMessageBox>
#include <QFileDialog>
#include <QMenuBar>
#include <QAction>
#include <QColorDialog>
#include <QInputDialog>
#include <QThread>
#include <QClipboard>


#include <qcustomplot.h>

/////////////////////// Local includes
#include "MainWindow.hpp"
#include "../nongui/globals.hpp"
#include "AboutMineXpertDlg.hpp"
#include "Application.hpp"
#include "../../libmass/MassSpectrum.hpp"

#include "AbstractMultiPlotWnd.hpp"

#include "../nongui/MassSpecDataFileFormatAnalyzer.hpp"
#include "../nongui/MassSpecDataFileLoaderSqlite3.hpp"
#include "../nongui/MassSpecDataFileLoaderPwiz.hpp"
#include "../nongui/MassSpecDataFileLoaderPwiz.hpp"
#include "../nongui/MassSpecDataFileLoaderMs1.hpp"
#include "../nongui/MassSpecDataFileLoaderXy.hpp"
#include "../nongui/MassSpecDataFileLoaderBrukerXy.hpp"
#include "../nongui/MassSpecDataFileLoaderDx.hpp"
#include "IsoSpecDlg.hpp"
#include "PeakShaperDlg.hpp"
#include "ScriptingWnd.hpp"


namespace msXpSmineXpert
{


/*/js/ Class: MainWindow
 * <comment>This is the main mineXpert program window. It is not instantiable
 * through the JavaScript environment. Some funcitonalities are available to
 * allow highlevel tasks to be automated. The MainWindow single instance of
 * the minexpert software program is made available to the JavaScript
 * environment under the object name mainWinwod, with an alias
 * mainWnd.</comment>
 */


//! Construct a MainWindow instance with initialization of the module name.
/*!

  The module name is the application name of the program inside of the
  msXpertSuite software package. Currently, two modules exist: massXpert and
  mineXpert.

  This function calls setupWindow() to perform the actual initialization.

*/
MainWindow::MainWindow(const QString &moduleName) : m_moduleName{moduleName}
{
  if(!setupWindow())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
}


//! Destruct \c this MainWindow instance.
MainWindow::~MainWindow()
{
  writeSettings();

  // Close the analysis file.
  m_analysisFile.close();

  // The AnalysisPreferences were allocated on the heap.
  delete mpa_analysisPreferences;

  while(!m_colorList.isEmpty())
    delete m_colorList.takeFirst();

  while(!m_usedColorList.isEmpty())
    delete m_usedColorList.takeFirst();
}


//! Handler of the close event signal.
void
MainWindow::closeEvent(QCloseEvent *event)
{
  writeSettings();
  event->accept();
}


/*/js/
 * MainWindow.quit()
 *
 * Quit the application.
 */

//! Quit the application.
void
MainWindow::quit()
{
  close();
}


//! Setup the window. Calls initialize().
bool
MainWindow::setupWindow()
{

  // Create the actions and menus of the main window.
  createMenusAndActions();

  QPixmap pixmap(":/images/icons/32x32/minexpert.png");
  QIcon icon(pixmap);
  setWindowIcon(icon);

  setAttribute(Qt::WA_DeleteOnClose);
  statusBar()->setSizeGripEnabled(true);

  mp_statusBarLabel = new QLabel(this);
  statusBar()->addPermanentWidget(mp_statusBarLabel);
  mp_statusBarLabel->setVisible(false);

  mp_statusBarProgressBar = new QProgressBar(this);
  mp_statusBarProgressBar->setAlignment(Qt::AlignLeft);

  statusBar()->addPermanentWidget(mp_statusBarProgressBar);
  mp_statusBarProgressBar->setVisible(false);

  // We want to have a cancel push button available.
  const QIcon cancelIcon       = QIcon(":/images/red-cross-cancel.png");
  mp_statusBarCancelPushButton = new QPushButton(cancelIcon, "", this);
  connect(mp_statusBarCancelPushButton,
          &QPushButton::clicked,
          this,
          &MainWindow::cancelPushButtonClicked);
  statusBar()->addPermanentWidget(mp_statusBarCancelPushButton);
  mp_statusBarCancelPushButton->setVisible(false);

  readSettings();

  initialize();

  // Cannot initialize scripting before initialize() because all the windows
  // (including the scripting one) must have been created at the time we
  // initialize scripting (we do export to the scripting environment all the
  // window objects).

  initializeScripting();

  return true;
}


//! Initialize the window and all the depending widgets.
/*!

  The initialization actually allocates the various windows and dialog windows.

  Also, the list of available colors is filled-in.

*/
bool
MainWindow::initialize()
{
  // Allocate all the main windows

  /********* Console window **********/
  mp_consoleWnd = new ConsoleWnd(dynamic_cast<QWidget *>(this), m_moduleName);

  /********* Scripting window **********/
  mp_scriptingWnd = new msXpSmineXpert::ScriptingWnd(
    dynamic_cast<QWidget *>(this), m_moduleName);


  /********* m/z integration parameters window window **********/
  mp_mzIntegrationParamsWnd = new MzIntegrationParamsWnd(this);

  /********* Sql data slicer configuration window **********/
  mp_sqlMassDataSlicerWnd = new SqlMassDataSlicerWnd(this, m_moduleName);

  /********* TIC chromatogram window **********/
  mp_ticChromWnd = new TicChromWnd(dynamic_cast<QWidget *>(this));
  // When a plot is destroyed, let this window know it is asking for the
  // plot color to be recycled.
  connect(mp_ticChromWnd,
          &AbstractMultiPlotWnd::recyclePlottingColor,
          this,
          &MainWindow::recyclePlottingColor);
  // All the data-displaying and data-integration-triggering window need to
  // know the integration parameters defined by the user.
  mp_ticChromWnd->setMzIntegrationParamsWnd(mp_mzIntegrationParamsWnd);

  /********* Mass spectrum window **********/
  mp_massSpecWnd = new MassSpecWnd(dynamic_cast<QWidget *>(this));
  // When a plot is destroyed, let this window know it is asking for the
  // plot color to be recycled.
  connect(mp_massSpecWnd,
          &AbstractMultiPlotWnd::recyclePlottingColor,
          this,
          &MainWindow::recyclePlottingColor);
  // All the data-displaying and data-integration-triggering window need to
  // know the integration parameters defined by the user.
  mp_massSpecWnd->setMzIntegrationParamsWnd(mp_mzIntegrationParamsWnd);

  /********* Drift spectrum window **********/
  mp_driftSpecWnd = new DriftSpecWnd(dynamic_cast<QWidget *>(this));
  // When a plot is destroyed, let this window know it is asking for the
  // plot color to be recycled.
  connect(mp_driftSpecWnd,
          &AbstractMultiPlotWnd::recyclePlottingColor,
          this,
          &MainWindow::recyclePlottingColor);
  // All the data-displaying and data-integration-triggering window need to
  // know the integration parameters defined by the user.
  mp_driftSpecWnd->setMzIntegrationParamsWnd(mp_mzIntegrationParamsWnd);

  /********* Color map window **********/
  mp_colorMapWnd = new ColorMapWnd(dynamic_cast<QWidget *>(this));
  // When a plot is destroyed, let this window know it is asking for the
  // plot color to be recycled.
  connect(mp_colorMapWnd,
          &AbstractMultiPlotWnd::recyclePlottingColor,
          this,
          &MainWindow::recyclePlottingColor);
  // All the data-displaying and data-integration-triggering window need to
  // know the integration parameters defined by the user.
  mp_colorMapWnd->setMzIntegrationParamsWnd(mp_mzIntegrationParamsWnd);

  // Now make the connections for all the relevant windows:

  // When the m/z integration parameters change in their setting window,
  // they emit the signal. We need to respond to these changes, by updating
  // the values in the widget.

  connect(mp_mzIntegrationParamsWnd,
          &MzIntegrationParamsWnd::mzIntegrationParamsChangedSignal,
          mp_ticChromWnd,
          &TicChromWnd::mzIntegrationParamsChanged);
  //
  // The dialog window that lists all the open spectra.
  mp_openSpectraDlg = new OpenSpectraDlg(dynamic_cast<QWidget *>(this));

  // The MzIntegrationParamsWnd was already created above, because we were
  // going to need it for the various data-displaying and data-integrating
  // windows.

  // Start the XIC extraction window.
  mp_xicExtractionWnd = new XicExtractionWnd(dynamic_cast<QWidget *>(this));

  m_colorList.append(new QColor(QColor().fromRgb(0xff0000)));
  m_colorList.append(new QColor(QColor().fromRgb(0x185e00)));
  m_colorList.append(new QColor(QColor().fromRgb(0x0000ff)));
  m_colorList.append(new QColor(QColor().fromRgb(0xffa400)));
  m_colorList.append(new QColor(QColor().fromRgb(0x00ffd7)));
  m_colorList.append(new QColor(QColor().fromRgb(0x7a00ff)));
  m_colorList.append(new QColor(QColor().fromRgb(0xff009c)));
  m_colorList.append(new QColor(QColor().fromRgb(0x0072ff)));
  m_colorList.append(new QColor(QColor().fromRgb(0x19ff00)));
  m_colorList.append(new QColor(QColor().fromRgb(0xf08080)));
  m_colorList.append(new QColor(QColor().fromRgb(0x4682b4)));
  m_colorList.append(new QColor(QColor().fromRgb(0x556b2f)));
  m_colorList.append(new QColor(QColor().fromRgb(0xff00ff)));
  m_colorList.append(new QColor(QColor().fromRgb(0x7dbee7)));

  return true;
}


//! Initialize the JavaScript scripting framework.
void
MainWindow::initializeScripting()
{

  // Make available to the scripting environment all the entities that will
  // be useful to script mineXpert.

  // Placeholder, although at the moment we do not use it.
  QScriptValue scriptValue;

  // mainWindow/mainWnd

  mp_scriptingWnd->publishQObject(
    this,
    Q_NULLPTR /* parent QObject */,
    "mainWindow",
    "mainWnd",
    "Make available the main window under name "
    "\"mainWindow\" with alias \"mainWnd\".\n",
    "The main window is the program main window from which all ther other "
    "windows depend (the TIC chromatogram, mass spectrum, drift spectrum, "
    "color map, "
    "console, scripting console windows, for example).",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // consoleWnd

  mp_scriptingWnd->publishQObject(
    mp_consoleWnd,
    Q_NULLPTR /* parent QObject */,
    "consoleWnd",
    QString(),
    "Make available the console window under name \"consoleWnd\".\n",
    "The console window displays all the useful messages about the program "
    "performing the various actions required by the user, thus providing "
    "useful feedback to the user",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // ticChromWnd

  mp_scriptingWnd->publishQObject(
    mp_ticChromWnd,
    Q_NULLPTR /* parent QObject */,
    "ticChromWnd",
    QString(),
    "Make available the TIC chromatogram window under name \"ticChromWnd\".\n",
    "The TIC chromatogram window displays the total ion current chromatogram "
    "for "
    "the opened mass spectrometry files. It can also show TIC chromatograms "
    "obtained "
    "after an integration operation; in which case it is a XIC (extracted ion "
    "current) "
    "chromatogram",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // massSpecWnd

  mp_scriptingWnd->publishQObject(
    mp_massSpecWnd,
    Q_NULLPTR /* parent QObject */,
    "massSpecWnd",
    QString(),
    "Make available the mass spectrum window under name \"massSpecWnd\".\n",
    "The mass spectrum window displays the mass spectra obtained as a result "
    "of an integration either from the TIC chromatogram, the drift spectrum or "
    "the color map.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // driftSpecWnd

  mp_scriptingWnd->publishQObject(
    mp_driftSpecWnd,
    Q_NULLPTR /* parent QObject */,
    "driftSpecWnd",
    QString(),
    "Make available the drift spectrum window under name \"driftSpecWnd\".\n",
    "The drift spectrum displays the drift spectra obtained as a result "
    "of an integration either from the TIC chromatogram, the mass spectrum or "
    "the color map.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // colorMapWnd

  mp_scriptingWnd->publishQObject(
    mp_colorMapWnd,
    Q_NULLPTR /* parent QObject */,
    "colorMapWnd",
    QString(),
    "Make available the color map window under name \"colorMapWnd\".\n",
    "The color map window displays the mass/drift spectra in the form of a map "
    "with the drift time on the abscissa and the m/z data on the ordinates. "
    "The "
    "intensity of the ion current is color-coded.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // xicExtractionWnd

  mp_scriptingWnd->publishQObject(
    mp_xicExtractionWnd,
    Q_NULLPTR /* parent QObject */,
    "xicExtractionWnd",
    QString(),
    "Make available the XIC extraction window under name "
    "\"xicExtractionWnd\".\n",
    "The XIC extraction window allows the user to set the parameters for XIC "
    "extraction from the "
    "data files currently selected in the OpenSpectra dialog window.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // mzIntegrationParamsWnd

  mp_scriptingWnd->publishQObject(
    mp_mzIntegrationParamsWnd,
    Q_NULLPTR /* parent QObject */,
    "mzIntegrationParamsWnd",
    QString(),
    "Make available the m/z integration parameters window under name "
    "\"mzIntegrationParamsWnd\".\n",
    "The m/z integration parameters window hosts a set of parameters that "
    "configure the "
    "integration of various mass spectra into one single mass spectrum, "
    "typically from "
    "the TIC chromatogram window or from the color map drift data window.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // sqlMassDataSlicerWnd

  mp_scriptingWnd->publishQObject(
    mp_sqlMassDataSlicerWnd,
    Q_NULLPTR /* parent QObject */,
    "sqlMassDataSlicerWnd",
    QString(),
    "Make available the mass data slicer parameters window under name "
    "\"sqlMassDataSlicerWnd\".\n",
    "The mass data slicer parameters window hosts a set of parameters that "
    "configure the "
    "export of data from a SQLite3 db format data file to a number of smaller "
    "data files.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // openSpectraDlg

  mp_scriptingWnd->publishQObject(
    mp_openSpectraDlg,
    Q_NULLPTR /* parent QObject */,
    "openSpectraDlg",
    QString(),
    "Make available the open spectra dialog under name \"openSpectraDlg\".\n",
    "The open spectra dialog window displays all the mass spectra that are "
    "currently opened in the program. It is possible to perform actions on the "
    "selected items, like showing/hiding/closing...",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // scriptingWnd

  mp_scriptingWnd->publishQObject(
    mp_scriptingWnd,
    Q_NULLPTR /* parent QObject */,
    "scriptingWnd",
    QString(),
    "Make available the scripting window under name \"scriptingWnd\".\n",
    "The scripting window is the central place where scripting occurs. The "
    "scripting window provides a number of features available to the scripter.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);

  // data plot relationer

  mp_scriptingWnd->publishQObject(
    &m_dataPlotWidgetRelationer,
    this,
    "plotRelationer",
    QString(),
    "Make available the plot relationer under name \"dataPlotRelation\" with "
    "alias \"relationer\".\n",
    "The data plot widget relationer is a faciity that allows one to trace the "
    "parentship between plot widgets.",
    scriptValue,
    QScriptEngine::QtOwnership,
    QScriptEngine::ExcludeSuperClassContents |
      QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots);
}


//! Make again available a previously used plotting color.
void
MainWindow::recyclePlottingColor(QColor color)
{
  if(!color.isValid())
    return;

  for(int iter = 0; iter < m_usedColorList.size(); ++iter)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "MainWindow::recycleColor with size: " << m_usedColorList.size()
      // << "and iter: " << iter;

      QColor *iterColor = m_usedColorList.at(iter);

      if(*iterColor == color)
        {
          m_colorList.append(iterColor);
          m_usedColorList.takeAt(iter);

          // We must return immediately because we changed the used color list
          // and we cannot go on iteratring in it!!!
          return;
        }
    }
}


//! Tell if \p color is already used to plot data.
bool
MainWindow::isPlottingColorUsed(const QColor &color) const
{
  if(!color.isValid())
    return false;

  for(int iter = 0; iter < m_usedColorList.size(); ++iter)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "MainWindow::recycleColor with size: " << m_usedColorList.size()
      // << "and iter: " << iter;

      QColor *iterColor = m_usedColorList.at(iter);

      if(*iterColor == color)
        return true;
    }

  return false;
}


/*/js/
 * MainWindow.clearPlots()
 *
 * Clear all plots and give back all memory associated to the mass data.
 */

//! Clear all plots. This function frees all the data associated with the plots.
void
MainWindow::clearPlots()
{
  mp_openSpectraDlg->actionComboBoxActivated("Close all");
}


//! Display the open file dialog window for full data load.
void
MainWindow::openFullMassSpectrometryFileDlg()
{
  openMassSpectrometryFile(QString(), false /* isStreamed */);
}


//! Display the open file dialog window for streamed data load.
void
MainWindow::openStreamedMassSpectrometryFileDlg()
{
  openMassSpectrometryFile(QString(), true /* isStreamed */);
}


//! Create a mass spectrum starting from data in the clipboard.
void
MainWindow::createMassSpectrumFromXyText(const QString &text,
                                         const QString &title)
{
  if(text.isEmpty())
    {
      return;
    }

  qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  logConsoleMessage("Starting the parsing of the mass data text.\n");

  qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  MassSpecDataFileLoaderXy loader;

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).
  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", title, false);

  if(loader.loadDataFromString(text, p_massSpecDataSet) < 1)
    {
      statusBarMessage("Failed to load any mass spectrum.");
      delete p_massSpecDataSet;
      return;
    }

  qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() =
    MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_XY;

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  // Each new plot must have a different color than all the others already
  // used, such that we can distinguish the files throughout all the
  // program. We have two lists of colors, the list of available colors
  // (not used currently) and the list of used colors. When a new plot is
  // created, one color is selected from the available colors list and
  // that color is moved to the used colors list so that it cannot be
  // reused. When a graph is closed, its color is migrated from the list
  // of used colors to the list of available colors.
  QColor color;
  QColor *useColor = Q_NULLPTR;

  // Let's see if there is still a color available:

  if(m_colorList.isEmpty())
    {
      // Get a new color, since we do not have any more available.

      color = QColorDialog::getColor(
        Qt::white, this, "Please select a color for the plot");

      if(!color.isValid())
        {
          useColor = new QColor(Qt::black);
        }
      else
        {
          useColor = new QColor(color);
          m_usedColorList.append(useColor);
        }
    }
  else
    {
      useColor = m_colorList.first();
      m_usedColorList.append(useColor);
      m_colorList.takeFirst();
    }

  // qDebug() << __FILE__ << __LINE__ << "Calculating the TIC chromatogram...";
  // Now compute the TIC and plot it. The function returns the plot widget
  // that displays the chromatogram for this mass spec data set.

  qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  statusBarMessage("Calculating the TIC chromatogram...");
  logConsoleMessage("Calculating the TIC chromatogram...\n");

  if(m_isScriptedOperation)
    {
      // This is a non-QThread-based version of the function
      mp_ticChromWnd->jsInitialTic(p_massSpecDataSet, *useColor);
    }
  else
    {
      // This is a QThread-based version of the function
      mp_ticChromWnd->initialTic(p_massSpecDataSet, *useColor);
    }

  // The initialTic function from TicChromWnd will signal
  // "initialTicDoneSignal" to finalize the operation.
}


//! Create a mass spectrum starting from data in the clipboard.
void
MainWindow::createMassSpectrumFromClipboard()
{
  QClipboard *clipboard = QGuiApplication::clipboard();
  QString msData        = clipboard->text();


  return createMassSpectrumFromXyText(
    msData,
    QString("From clipboard @ %1")
      .arg(QTime::currentTime().toString("hh:mm:ss.zzz")));
}


/*/js/
 * MainWindow.jsCreateMassSpectrumFromClipboard()
 *
 * Create a new mass spectrum from data in the clipboard. The data in the
 * clipboard need to be under the format of <number><separator><number>,
 * <separator> being any non-numerical and non-'.' character. The first
 * <number> is the m/z and the second <number> is the intensity.
 *
 * Upon completing the parsing of the textual data in the clipboard, a pseudo
 * TIC chromatogram is created that needs to be integrated to a mass spectrum
 * using:
 *
 * lastTicChromPlotWidget.integrateToMz();
 *
 * Ex:
 * mainWindow.createMassSpectrumFromClipboard();
 * lastTicChromPlotWidget.integrateToMz();
 */
void
MainWindow::jsCreateMassSpectrumFromClipboard()
{
  m_isScriptedOperation = true;
  createMassSpectrumFromClipboard();
  m_isScriptedOperation = false;
}


//! Slot that handles the results after the initialTic function has performed
//! its computation
void
MainWindow::initialTicDone(AbstractPlotWidget *widget,
                           const MassSpecDataSet *massSpecDataSet,
                           QColor color)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "() - enter"
  //<< "with color" << color
  //<< "with integrator:" << integrator
  //<< "and widget:" << widget;

  if(widget == Q_NULLPTR)
    {
      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to compute an initial TIC chromatogram. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      return;
    }

  widget->setFocus();

  // At this point, the mainwindow should show a message telling that the
  // file was successfully loaded, since loading a file is actually two
  // things in sequence:
  // 1. Opening the file and loading the data
  // 2. Calculating a TIC chromatogram on the basis of the loaded data.

  statusBarMessage("Done loading file.");
  QTimer::singleShot(3000, this, [=]() {
    this->updateFeedback("", 0, false /* setVisible */, LOG_TO_STATUS_BAR);
  });

  logConsoleMessage("Done loading file.\n");

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Going to add the tic chrom plot widget to the open spectra dialog.";

  // Add a new item to the open spectrum dialog window widget list such
  // that we'll know which files are open. Note that the function below
  // will create a mapping between the list widget item and the tic chrom plot
  // widget that is set to be the parent of p_massSpecDataSet.
  QListWidgetItem *item =
    mp_openSpectraDlg->addOpenSpectrum(massSpecDataSet, color);

  // Now make sure that the open spectra dialog's list widget item is
  // selected by default.

  mp_openSpectraDlg->selectPlotWidget(widget);

  // The root relation is when the relation has no origin plot widget but
  // has a target plot widget. This is how a relation is recognized to
  // describe a root plot widget.
  m_dataPlotWidgetRelationer.newRelation(massSpecDataSet,
                                         Q_NULLPTR /*origin plot widget */,
                                         widget /*target plot widget */,
                                         item);

  if(massSpecDataSet->isMobilityExperiment())
    {

      //////////////////////////// COLOR MAP WND //////////////////////////////
      //////////////////////////// COLOR MAP WND //////////////////////////////

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Now chaining on to the initial color map stuff";

      // At this time, we now can compute the initial color map, thus generating
      // a new plot widget that we will associate to the tic chrom plot widget.

      statusBarMessage("Calculating the Color map...");
      logConsoleMessage("Calculating the Color map...\n");

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "going to call mp_colorMapWnd->initialColorMap;"
      //<< "with massSpecDataSet:" << massSpecDataSet
      //<< "and filename:" << massSpecDataSet->fileName();

      if(m_isScriptedOperation)
        {
          // This is a non-QThread-based version of the function
          mp_colorMapWnd->jsInitialColorMap(massSpecDataSet, widget, color);
        }
      else
        {
          // This is a QThread-based version of the function
          mp_colorMapWnd->initialColorMap(massSpecDataSet, widget, color);
        }

      // The initialColorMap function from ColorMapWnd will signal
      // initialColorMapDone to finalize the operation.
    }

  // Finally inform the user of the MzIntegrationParams stuff.
  // But only show the informative dialog if there are multiple spectra in the
  // data set. Otherwise it does not make sense.

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "checking if we need to display the mz integ params help dialog window";

  if(massSpecDataSet->size() > 1)
    showMzIntegrationParamsHelp(
      massSpecDataSet->statistics().m_smallestStepMedian);

  // At this point, if the tic data contain a single point, then, start the
  // integration to the masss spectrum right away.

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"hash size: " << massSpecDataSet->rtHash().size();

  if(massSpecDataSet->rtHash().size() < 2)
    static_cast<TicChromPlotWidget *>(widget)->integrateToMz();
}


void
MainWindow::initialColorMapDone(AbstractPlotWidget *widget,
                                AbstractPlotWidget *originWidget,
                                const MassSpecDataSet *massSpecDataSet,
                                QColor color)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "() - enter"
  //<< "current thread id:" << QThread::currentThreadId();

  if(widget == Q_NULLPTR)
    {
      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to compute an initial TIC chromatogram. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      return;
    }

  // Make sure we show it on the fore for the user to see it.
  showColorMapWnd();

  // The colormap is considered to be a descendant from the TIC:

  m_dataPlotWidgetRelationer.newRelation(
    massSpecDataSet,
    originWidget /*origin plot widget */,
    widget /*target plot widget */,
    Q_NULLPTR /* openspectradlglistwidgetitem */);

  // Make sure the origin widget (the matching TicChromPlotWidget) is focused.
  originWidget->setFocus();

  // At this point, the mainwindow should show a message telling that the
  // file was successfully loaded, since loading a file is actually two
  // things in sequence:
  // 1. Opening the file and loading the data
  // 2. Calculating a TIC chromatogram on the basis of the loaded data.

  statusBarMessage("Done loading file.");
  QTimer::singleShot(3000, this, [=]() {
    this->updateFeedback("", 0, false /* setVisible */, LOG_TO_STATUS_BAR);
  });

  logConsoleMessage("Done loading file.\n");

  // Finally inform the user of the MzIntegrationParams stuff.
  // But only show the informative dialog if there are multiple spectra in the
  // data set. Otherwise it does not make sense.
}

//! Open a mass spectrometry file.
/*!

  \param fileName name of the file to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFile(const QString &fileName, bool isStreamed)
{
  if(fileName.isEmpty())
    {
      QFileDialog fileDialog(this);
      fileDialog.setFileMode(QFileDialog::ExistingFiles);
      fileDialog.setViewMode(QFileDialog::Detail);
      fileDialog.setAcceptMode(QFileDialog::AcceptOpen);

      fileDialog.setDirectory(QDir::home());

      QStringList fileNames;

      if(fileDialog.exec())
        fileNames = fileDialog.selectedFiles();

      for(int iter = 0; iter < fileNames.size(); ++iter)
        {
          QString curFileName = fileNames.at(iter);

          openMassSpectrometryFile(curFileName, isStreamed);
        }
    }
  else
    {
      int format = MassSpecDataFileFormatAnalyzer::canLoadData(fileName);

      // qWarning() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "Open file: " << fileName << "format:" << format;

      // The strategy here is to go from more detailed formats to more generic
      // ones, that'll speed the process.

      if(format == MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3)
        {
          openMassSpectrometryFileSqlite3(fileName, isStreamed);
        }
      else if(format == MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZML)
        {
          openMassSpectrometryFilePwiz(fileName, format, isStreamed);
        }
      else if(format ==
              MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZXML)
        {
          openMassSpectrometryFilePwiz(fileName, format, isStreamed);
        }
      else if(format == MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_DX)
        {
          openMassSpectrometryFileDx(fileName, isStreamed);
        }
      else if(format == MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MS1)
        {
          openMassSpectrometryFileMs1(fileName, isStreamed);
        }
      else if(format == MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_XY)
        {
          openMassSpectrometryFileXy(fileName);
        }
      else
        {
          QMessageBox msgBox;
          msgBox.setText("Unknown file format");

          QString msg =
            QString("The requested file (%1) has not a valid format.")
              .arg(fileName);

          msgBox.setInformativeText(msg);
          msgBox.setStandardButtons(QMessageBox::Ok);
          msgBox.setDefaultButton(QMessageBox::Ok);

          msgBox.exec();
        }
    }
}


/*/js/
 * MainWindow.jsOpenMassSpectrometryFile(fileName, streamed)
 *
 * Load mass spectrometry data from fileName.
 *
 * fileName: <String> holding the mass spectrometry data file name
 * streamed: <Boolean> telling if the loading should be in streamed mode.
 *
 * Ex:
 * mainWindow.jsOpenMassSpectrometryFile("/tmp/20161027-cgb-wt-mobility.db",
 * true);
 *
 * reads the data in the file in streamed mode.
 *
 * mainWindow.jsOpenMassSpectrometryFile("/tmp/20161027-cgb-wt-mobility.db",
 * false);
 *
 * reads the data in the file in full mode.
 */
void
MainWindow::jsOpenMassSpectrometryFile(const QString &fileName, bool isStreamed)
{
  m_isScriptedOperation = true;
  openMassSpectrometryFile(fileName, isStreamed);
  m_isScriptedOperation = false;

  return;
}


//! Open the mass spectrometry files listed in \p fileList.
/*!

  \param fileList list of names of the files to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFiles(const QStringList &fileList,
                                      bool isStreamed)
{
  for(int iter = 0; iter < fileList.size(); iter++)
    {
      QString fileName = fileList.at(iter);

      openMassSpectrometryFile(fileName, isStreamed);
    }
  // End of
  // for(int iter = 0; iter < fileList.size(); iter++)
}


/*/js/
 * MainWindow.jsOpenMassSpectrometryFiles(fileNameList, streamed)
 *
 * Load mass spectrometry data from all the files in the fileNameList.
 *
 * fileNameList: <Array> of <String> objects holding the mass spectrometry data
 * file names
 * streamed: <Boolean> telling if the loading should be in streamed mode.
 */
void
MainWindow::jsOpenMassSpectrometryFiles(const QStringList &fileList,
                                        bool isStreamed)
{
  m_isScriptedOperation = true;
  openMassSpectrometryFiles(fileList, isStreamed);
  m_isScriptedOperation = false;
}


//! Open a mass spectrometry file in SQLite3 format.
/*!

  \param fileName name of the file to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFileSqlite3(QString fileName, bool isStreamed)
{
  // Instantiate an object that will help interrogate the file:

  MassSpecDataFileLoaderSqlite3 loader(fileName);

  //! Handler of SQLite3-based mass data.
  // This loader loads data from a SQLite3-based mass spectrometry format and
  // uses the features of the MassSpecSqlite3Handler class that is required
  // to craft SQL-based statements to extract data from the database file.

  MassSpecSqlite3Handler sqlite3Handler;

  // And now, make sure the member database, the source database is fine.

  QString connectionName =
    QString("%1/%2").arg(fileName).arg(msXpS::pointerAsString(this));
  QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
  database.setDatabaseName(fileName);

  // Now make sure that the metadata from the database are read and stored
  // in the sqlite3 handler:
  if(!sqlite3Handler.initializeDbForRead(database, true /* readMetaData */))
    {
      logConsoleMessage("Failed to initialize the database for reading.");
      return;
    }

  makeLoaderConnections(&loader);

  // Allocate a new mass spec data set and provide it to the loadData
  // function.

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).

  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", fileName, isStreamed);

  // Make sure the cancel button is visible.
  mp_statusBarCancelPushButton->setVisible(true);

  statusBarMessage("Loading data for file" + fileName + "...");

  double start = omp_get_wtime();

  int readSpectra =
    loader.loadData(database, sqlite3Handler, p_massSpecDataSet);

  if(!p_massSpecDataSet->isStreamed() && readSpectra <= 0)
    {
      logConsoleMessage("Failed to load any mass spectrum.");

      return;
    }
  // else
  // We cannot test the returned value, because when the data are
  // streamed, we are not allocating mass spectra.

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() =
    MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3;

  statusBarMessage("Loaded data.");

  double end = omp_get_wtime();

  logConsoleMessage(QString("Used %1 s to load %2 spectra\n"
                            "retention time hash has %3 items\n")
                      .arg(end - start)
                      .arg(p_massSpecDataSet->massSpectra().size())
                      .arg(p_massSpecDataSet->rtHash().size()));

  // Before making the initial TIC chromatogram, let's first  ask that
  // the statistics of the mass spectral data be computed. Even if the
  // statistics data are not used for the TIC and COLOR MAP calculations.

  p_massSpecDataSet->consolidateStatistics();

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  seedInitialPlots(p_massSpecDataSet);

  logConsoleMessage("Done loading file.\n");

  concludeFileLoading();
}


//! Open a mass spectrometry file in mzML/mzXML format.
/*!

  \param fileName name of the file to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFilePwiz(QString fileName,
                                         int format,
                                         bool isStreamed)
{
  MassSpecDataFileLoaderPwiz loader(fileName);

  makeLoaderConnections(&loader);

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).
  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", fileName, isStreamed);

  // Make sure the cancel button is visible.
  mp_statusBarCancelPushButton->setVisible(true);

  statusBarMessage("Loading data for file" + fileName + "...");

  double start = omp_get_wtime();

  loader.loadDataToMassSpecDataSet(p_massSpecDataSet);

  // We do store mass spectra in the mass spec data set only if we are not
  // streaming or if we are streaming AND we are loading ion mobility mass
  // data.

  if(!isStreamed && p_massSpecDataSet->massSpectra().size() == 0)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "File loading failed.";

      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to load any mass data. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      delete p_massSpecDataSet;

      return;
    }

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() = format;

  statusBarMessage("Loaded data.");

  double end = omp_get_wtime();

  logConsoleMessage(QString("Used %1 s to load %2 spectra\n"
                            "retention time hash has %3 items\n")
                      .arg(end - start)
                      .arg(p_massSpecDataSet->massSpectra().size())
                      .arg(p_massSpecDataSet->rtHash().size()));

  // At this point, ask that the statistics of the mass data be made

  // Before making the initial TIC chromatogram, let's first  ask that
  // the statistics of the mass spectral data be computed. Even if the
  // statistics data are not used for the TIC and COLOR MAP calculations.

  p_massSpecDataSet->consolidateStatistics();

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  seedInitialPlots(p_massSpecDataSet);

  logConsoleMessage("Done loading file.\n");

  concludeFileLoading();
}


//! Open a mass spectrometry file in DX format.
/*!

  \param fileName name of the file to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFileDx(QString fileName, bool isStreamed)
{
  logConsoleMessage("Starting the parsing of the mass data dx file.\n");

  MassSpecDataFileLoaderDx loader(fileName);

  makeLoaderConnections(&loader);

  int spectrumCount = loader.readSpectrumCount();
  QString msg;
  QMessageBox msgBox;

  msg = QString("Info: this file contains %1 spectra.").arg(spectrumCount);

  msgBox.setText(msg);
  msgBox.setInformativeText("Do you want to continue?");
  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  msgBox.setDefaultButton(QMessageBox::No);
  int ret = msgBox.exec();

  if(ret != QMessageBox::Yes)
    return;

  // At this point, the user wants to do the data load.

  // Set up the progress dialog, for this we need the number of real spectra
  // in the file:

  // qDebug() << __FILE__ << __LINE__
  // << "there are " << spectrumCount << "spectra";

  msg = QString("Loading %1 spectra from dx file.\n").arg(spectrumCount);

  logConsoleMessage(msg);

  QApplication::processEvents();

  double start = omp_get_wtime();

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = spectrumCount;

  updateFeedback(msg,
                 1,
                 true /* setVisible */,
                 LogType::LOG_TO_BOTH,
                 m_progressFeedbackStartValue,
                 m_progressFeedbackEndValue);

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).
  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", fileName, isStreamed);

  loader.loadData(p_massSpecDataSet);

  if(p_massSpecDataSet->massSpectra().size() == 0)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "File loading failed.";

      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to load any mass data. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      delete p_massSpecDataSet;

      return;
    }

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() =
    MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_DX;

  statusBarMessage("Loaded data.");

  double end = omp_get_wtime();

  logConsoleMessage(QString("Used %1 s to load %2 spectra\n"
                            "retention time hash has %3 items\n")
                      .arg(end - start)
                      .arg(p_massSpecDataSet->massSpectra().size())
                      .arg(p_massSpecDataSet->rtHash().size()));

  // At this point, ask that the statistics of the mass data be made

  // Before making the initial TIC chromatogram, let's first  ask that
  // the statistics of the mass spectral data be computed. Even if the
  // statistics data are not used for the TIC and COLOR MAP calculations.

  p_massSpecDataSet->consolidateStatistics();

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  seedInitialPlots(p_massSpecDataSet);

  logConsoleMessage("Done loading file.\n");

  concludeFileLoading();
}


//! Open a mass spectrometry file in MS1 format.
/*!

  \param fileName name of the file to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFileMs1(QString fileName, bool isStreamed)
{
  logConsoleMessage("Starting the parsing of the mass data dx file.\n");

  MassSpecDataFileLoaderMs1 loader(fileName);

  makeLoaderConnections(&loader);

  int spectrumCount = loader.readSpectrumCount();
  QString msg;
  QMessageBox msgBox;

  msg = QString("Info: this file contains %1 spectra.").arg(spectrumCount);

  msgBox.setText(msg);
  msgBox.setInformativeText("Do you want to continue?");
  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  msgBox.setDefaultButton(QMessageBox::No);
  int ret = msgBox.exec();

  if(ret != QMessageBox::Yes)
    return;

  // At this point, the user wants to do the data load.

  // Set up the progress dialog, for this we need the number of real spectra
  // in the file:

  // qDebug() << __FILE__ << __LINE__
  // << "there are " << spectrumCount << "spectra";

  msg = QString("Loading %1 spectra from dx file.\n").arg(spectrumCount);

  logConsoleMessage(msg);

  QApplication::processEvents();

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = spectrumCount;

  updateFeedback(msg,
                 1,
                 true /* setVisible */,
                 LogType::LOG_TO_BOTH,
                 m_progressFeedbackStartValue,
                 m_progressFeedbackEndValue);

  double start = omp_get_wtime();

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).
  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", fileName, isStreamed);

  loader.loadData(p_massSpecDataSet);

  if(p_massSpecDataSet->massSpectra().size() == 0)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "File loading failed.";

      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to load any mass data. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      delete p_massSpecDataSet;

      return;
    }

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() =
    MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MS1;

  statusBarMessage("Loaded data.");

  double end = omp_get_wtime();

  logConsoleMessage(QString("Used %1 s to load %2 spectra\n"
                            "retention time hash has %3 items\n")
                      .arg(end - start)
                      .arg(p_massSpecDataSet->massSpectra().size())
                      .arg(p_massSpecDataSet->rtHash().size()));

  // At this point, ask that the statistics of the mass data be made

  // Before making the initial TIC chromatogram, let's first  ask that
  // the statistics of the mass spectral data be computed. Even if the
  // statistics data are not used for the TIC and COLOR MAP calculations.

  p_massSpecDataSet->consolidateStatistics();

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  seedInitialPlots(p_massSpecDataSet);

  logConsoleMessage("Done loading file.\n");

  concludeFileLoading();
}


//! Open a mass spectrometry file in Bruker XY format.
/*!

  \param fileName name of the file to open.

  \param isStreamed tell if the data loading process must be streamed or not.

*/
void
MainWindow::openMassSpectrometryFileBrukerXy(QString fileName, bool isStreamed)
{
  logConsoleMessage("Starting the parsing of the mass data bruker xy file.\n");

  MassSpecDataFileLoaderBrukerXy loader(fileName);

  makeLoaderConnections(&loader);

  int spectrumCount = loader.readSpectrumCount();
  QString msg;
  QMessageBox msgBox;

  msg = QString("Info: this file contains %1 spectra.").arg(spectrumCount);

  msgBox.setText(msg);
  msgBox.setInformativeText("Do you want to continue?");
  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  msgBox.setDefaultButton(QMessageBox::No);
  int ret = msgBox.exec();

  if(ret != QMessageBox::Yes)
    return;

  // At this point, the user wants to do the data load.

  // Set up the progress dialog, for this we need the number of real spectra
  // in the file:

  // qDebug() << __FILE__ << __LINE__
  // << "there are " << spectrumCount << "spectra";

  msg = QString("Loading %1 spectra from dx file.\n").arg(spectrumCount);

  logConsoleMessage(msg);

  QApplication::processEvents();

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = spectrumCount;

  updateFeedback(msg,
                 1,
                 true /* setVisible */,
                 LogType::LOG_TO_BOTH,
                 m_progressFeedbackStartValue,
                 m_progressFeedbackEndValue);

  double start = omp_get_wtime();

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).
  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", fileName, isStreamed);

  loader.loadData(p_massSpecDataSet);

  if(p_massSpecDataSet->massSpectra().size() == 0)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "File loading failed.";

      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to load any mass data. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      delete p_massSpecDataSet;

      return;
    }

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() =
    MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_BRUKER_XY;

  statusBarMessage("Loaded data.");

  double end = omp_get_wtime();

  logConsoleMessage(QString("Used %1 s to load %2 spectra\n"
                            "retention time hash has %3 items\n")
                      .arg(end - start)
                      .arg(p_massSpecDataSet->massSpectra().size())
                      .arg(p_massSpecDataSet->rtHash().size()));

  // At this point, ask that the statistics of the mass data be made

  // Before making the initial TIC chromatogram, let's first  ask that
  // the statistics of the mass spectral data be computed. Even if the
  // statistics data are not used for the TIC and COLOR MAP calculations.

  p_massSpecDataSet->consolidateStatistics();

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  seedInitialPlots(p_massSpecDataSet);

  logConsoleMessage("Done loading file.\n");

  concludeFileLoading();
}


//! Open a mass spectrometry file in simple (x,y) format.
/*!

  \param fileName name of the file to open.

*/
void
MainWindow::openMassSpectrometryFileXy(QString fileName)
{
  // The file format is an easy one : two double values separated by either
  // ',' or '[space]' or '[tab]' or ';'.

  logConsoleMessage("Starting the parsing of the mass data text file.\n");

  MassSpecDataFileLoaderXy loader(fileName);

  // This data set will be reparented when we provide it to the handling
  // widget (window or plot widget or whatever QObject).
  MassSpecDataSet *p_massSpecDataSet =
    new MassSpecDataSet(this, "NOT_SET", fileName, false);

  double start = omp_get_wtime();

  loader.loadData(p_massSpecDataSet);

  if(p_massSpecDataSet->massSpectra().size() == 0)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "File loading failed.";

      QMessageBox msgBox;
      msgBox.setText(
        "The program failed to load any mass data. Please,"
        " ensure that the file being loaded is correct.");

      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();

      delete p_massSpecDataSet;

      return;
    }

  // We can set some more data to the p_massSpecDataSet:
  p_massSpecDataSet->rfileFormat() =
    MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_XY;

  statusBarMessage("Loaded data.");

  double end = omp_get_wtime();

  logConsoleMessage(QString("Used %1 s to load %2 spectra\n"
                            "retention time hash has %3 items\n")
                      .arg(end - start)
                      .arg(p_massSpecDataSet->massSpectra().size())
                      .arg(p_massSpecDataSet->rtHash().size()));

  // Even if there is a single mass spectrum, we need to seed the statistics
  // for the various minimum mz ...

  p_massSpecDataSet->consolidateStatistics();

  // When loading a new file, we want to first generate a TIC chromatogram
  // for the user to start the investigation on its data.
  //
  // This is actually the entry point to the handling of the data because
  // ownership of the data will be transferred (using the QObject
  // parentship mechanis) to the widget that will display the TIC.

  seedInitialPlots(p_massSpecDataSet);

  logConsoleMessage("Done loading file.\n");

  concludeFileLoading();
}


//! Make feedback connections.
/*!

  When a mass data file is loaded, and if the loading process is long, the
  user is provided with data loading feedback messages, like telling how many
  spectra were read from the file, for example.

  The feedback messages are displayed under the responsibility of \c this
  MainWindow instance, but they actually originate in the data file loader
  class (MassSpecDataFileLoader). The connections loader/main window are
  performed by this function.

*/
void
MainWindow::makeLoaderConnections(MassSpecDataFileLoader *loader)
{
  if(loader == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Connect the loader to this window so that we can display the reader log
  // messages:

  connect(loader,
          &MassSpecDataFileLoader::updateFeedbackSignal,
          this,
          &MainWindow::updateFeedback);

  connect(this,
          &MainWindow::cancelOperationSignal,
          loader,
          &MassSpecDataFileLoader::cancelOperation);
}


void
MainWindow::isoSpecDlg()
{
  IsoSpecDlg *iso_spec_dlg_p =
    new IsoSpecDlg(dynamic_cast<QWidget *>(this), m_moduleName);
  iso_spec_dlg_p->show();
  iso_spec_dlg_p->raise();
  iso_spec_dlg_p->activateWindow();
}


void
MainWindow::peakShaperDlg()
{
  PeakShaperDlg *peak_shaper_dlg_p =
    new PeakShaperDlg(dynamic_cast<QWidget *>(this), m_moduleName);
  peak_shaper_dlg_p->show();
  peak_shaper_dlg_p->raise();
  peak_shaper_dlg_p->activateWindow();
}


//! Open the analysis preferences dialog window to let the user configure the
//! options.
void
MainWindow::analysisPrefDlg()
{
  if(!mp_analysisPrefDlg)
    {
      mp_analysisPrefDlg =
        new AnalysisPreferencesDlg(dynamic_cast<QWidget *>(this), m_moduleName);
    }

  int res = mp_analysisPrefDlg->exec();

  if(res == QDialog::Accepted)
    {
      // Copy the preferences from the dialog window to *this.
      mpa_analysisPreferences =
        new AnalysisPreferences(mp_analysisPrefDlg->analysisPreferences());

      // Let's check if the recording should go a file, in which case we test
      // that we can open it properly.

      if((mpa_analysisPreferences->m_recordTarget &
          RecordTarget::RECORD_TO_FILE) == RecordTarget::RECORD_TO_FILE)
        {
          // Now check that the analysis file could be opened.
          m_analysisFile.setFileName(mpa_analysisPreferences->m_fileName);

          bool res = false;

          if(mpa_analysisPreferences->m_fileOpenMode == QIODevice::Truncate)
            // Interestingly, only truncate did not work. Since writeonly
            // implies
            // truncate, that's fine, and it works.
            res = m_analysisFile.open(QIODevice::WriteOnly);
          else
            res = m_analysisFile.open(QIODevice::Append);

          if(!res)
            {
              QMessageBox msgBox;
              msgBox.setText(
                "Failed to open the analysis file, please fix "
                "the analysis preferences.");
              msgBox.exec();

              return;
            }
          else
            {
              // Close the file, which will be opened each time it is
              // necessary.
              m_analysisFile.close();

              // We can effectively start the work. For that we need to inform
              // the
              // various plot widget windows that the analysis file where to
              // store
              // the analysis data is available (the pointer won't be 0
              // anymore).

              mp_ticChromWnd->setAnalysisFile(&m_analysisFile);
              mp_massSpecWnd->setAnalysisFile(&m_analysisFile);
              mp_driftSpecWnd->setAnalysisFile(&m_analysisFile);
            }
        }

      // Finally do not forget to set the gotten analyses to the mass spec
      // window.
      mp_massSpecWnd->setAnalysisPreferences(mpa_analysisPreferences);
      mp_driftSpecWnd->setAnalysisPreferences(mpa_analysisPreferences);
      mp_ticChromWnd->setAnalysisPreferences(mpa_analysisPreferences);
    }
  else // if(res == QDialog::Accepted)
    {
      // The dialog returned a rejected result. We cannot do anything.
    }
}


const OpenSpectraDlg *
MainWindow::openSpectraDlg() const
{
  return mp_openSpectraDlg;
}


void
MainWindow::createMenusAndActions()
{
  // File menu
  m_fileMenu = menuBar()->addMenu("&File");

  m_openFullMsFileAct = new QAction(tr("Open &full mass spectrum file(s)"),
                                    dynamic_cast<QObject *>(this));
  m_openFullMsFileAct->setShortcut(QKeySequence("Ctrl+O, F"));
  m_openFullMsFileAct->setStatusTip(
    tr("Open file(s) and store data in memory"));
  connect(m_openFullMsFileAct,
          &QAction::triggered,
          this,
          &MainWindow::openFullMassSpectrometryFileDlg);
  m_fileMenu->addAction(m_openFullMsFileAct);

  m_openStreamedMsFileAct = new QAction(
    tr("Open &streamed mass spectrum file(s)"), dynamic_cast<QObject *>(this));
  m_openStreamedMsFileAct->setShortcut(QKeySequence("Ctrl+O, S"));
  m_openStreamedMsFileAct->setStatusTip(
    tr("Open files(s) leaving data on disk"));
  connect(m_openStreamedMsFileAct,
          &QAction::triggered,
          this,
          &MainWindow::openStreamedMassSpectrometryFileDlg);
  m_fileMenu->addAction(m_openStreamedMsFileAct);

  m_openMsClipboardAct = new QAction(tr("&Mass spectrum from clipboard"),
                                     dynamic_cast<QObject *>(this));
  m_openMsClipboardAct->setShortcut(QKeySequence("Ctrl+O, C"));
  m_openMsClipboardAct->setStatusTip(
    tr("Create mass spectrum from clipboard data"));
  connect(m_openMsClipboardAct,
          &QAction::triggered,
          this,
          &MainWindow::createMassSpectrumFromClipboard);
  m_fileMenu->addAction(m_openMsClipboardAct);

  m_fileMenu->addSeparator();

  m_analysisPrefsAct =
    new QAction(tr("&Analysis preferences"), dynamic_cast<QObject *>(this));
  m_analysisPrefsAct->setShortcut(tr("Ctrl+P"));
  m_analysisPrefsAct->setStatusTip(tr("Set the analysis preferences"));
  connect(m_analysisPrefsAct,
          &QAction::triggered,
          this,
          &MainWindow::analysisPrefDlg);
  m_fileMenu->addAction(m_analysisPrefsAct);

  m_fileMenu->addSeparator();

  m_quitAct = new QAction(tr("&Quit"), dynamic_cast<QObject *>(this));
  m_quitAct->setShortcut(tr("Ctrl+Q"));
  m_quitAct->setStatusTip(tr("Exit mineXpert"));
  connect(m_quitAct, &QAction::triggered, this, &MainWindow::close);
  m_fileMenu->addAction(m_quitAct);


  // Plot menu
  m_plotMenu = menuBar()->addMenu("&Plot");

  m_clearPlotsAct = new QAction("&Clear plots", dynamic_cast<QObject *>(this));
  connect(m_clearPlotsAct, &QAction::triggered, this, &MainWindow::clearPlots);

  m_plotMenu->addAction(m_clearPlotsAct);


  // Utilities menu
  m_utilitiesMenu = menuBar()->addMenu("&Utilities");

  m_isoSpecAct =
    new QAction("Isotopic cluster calculator", dynamic_cast<QObject *>(this));
  m_isoSpecAct->setShortcut(QKeySequence("Ctrl+U, I"));
  m_isoSpecAct->setStatusTip(
    tr("Calculate the isotopic cluster distribution for a chemical formula"));
  connect(m_isoSpecAct, &QAction::triggered, this, &MainWindow::isoSpecDlg);
  m_utilitiesMenu->addAction(m_isoSpecAct);


  m_peakShaperAct =
    new QAction("Peak centroid shaper", dynamic_cast<QObject *>(this));
  m_peakShaperAct->setShortcut(QKeySequence("Ctrl+U, S"));
  m_peakShaperAct->setStatusTip(
    tr("Shape peak centroid(s) into gaussian/lorentzian fully shaped peak(s)"));
  connect(
    m_peakShaperAct, &QAction::triggered, this, &MainWindow::peakShaperDlg);
  m_utilitiesMenu->addAction(m_peakShaperAct);


  // Windows menu
  m_windowsMenu = menuBar()->addMenu("&Windows");

  m_showTicChromWndAct =
    new QAction("Show &TIC window", dynamic_cast<QObject *>(this));
  m_showTicChromWndAct->setStatusTip(tr("Show the TIC chromatogram window"));
  connect(m_showTicChromWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showTicChromWnd);
  m_windowsMenu->addAction(m_showTicChromWndAct);

  m_showMassSpecWndAct =
    new QAction("Show &Mass spectrum window", dynamic_cast<QObject *>(this));
  m_showMassSpecWndAct->setStatusTip(tr("Show the mass spectrum window"));
  connect(m_showMassSpecWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showMassSpecWnd);
  m_windowsMenu->addAction(m_showMassSpecWndAct);

  m_showDriftSpecWndAct =
    new QAction("Show &Drift spectrum window", dynamic_cast<QObject *>(this));
  m_showDriftSpecWndAct->setStatusTip(tr("Show the drift spectrum window"));
  connect(m_showDriftSpecWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showDriftSpecWnd);
  m_windowsMenu->addAction(m_showDriftSpecWndAct);

  m_showColorMapWndAct =
    new QAction("Show &Color map window", dynamic_cast<QObject *>(this));
  m_showColorMapWndAct->setStatusTip(tr("Show the color map window"));
  connect(m_showColorMapWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showColorMapWnd);
  m_windowsMenu->addAction(m_showColorMapWndAct);

  m_windowsMenu->addSeparator();

  m_showOpenSpectraDlgAct = new QAction("Show &Open mass spectra dialog",
                                        dynamic_cast<QObject *>(this));
  m_showOpenSpectraDlgAct->setStatusTip(
    tr("Show the open mass spectra dialog"));
  connect(m_showOpenSpectraDlgAct,
          &QAction::triggered,
          this,
          &MainWindow::showOpenMassSpectraDlg);
  m_windowsMenu->addAction(m_showOpenSpectraDlgAct);

  m_showConsoleWndAct =
    new QAction("Show &Console window", dynamic_cast<QObject *>(this));
  m_showConsoleWndAct->setStatusTip(tr("Show the console window"));
  connect(m_showConsoleWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showConsoleWnd);
  m_windowsMenu->addAction(m_showConsoleWndAct);

  m_showScriptingWndAct =
    new QAction("Show &Scripting window", dynamic_cast<QObject *>(this));
  m_showScriptingWndAct->setStatusTip(tr("Show the scripting window"));
  connect(m_showScriptingWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showScriptingWnd);
  m_windowsMenu->addAction(m_showScriptingWndAct);

  m_showXicExtractionWndAct =
    new QAction("Show &XIC extraction window", dynamic_cast<QObject *>(this));
  m_showXicExtractionWndAct->setStatusTip(tr("Show the XIC extraction window"));
  connect(m_showXicExtractionWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showXicExtractionWnd);
  m_windowsMenu->addAction(m_showXicExtractionWndAct);

  m_showMzIntegrationParamsWndAct = new QAction(
    "Show &MZ integration params window", dynamic_cast<QObject *>(this));
  m_showMzIntegrationParamsWndAct->setStatusTip(
    tr("Show the m/z integration params window"));
  connect(m_showMzIntegrationParamsWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showMzIntegrationParamsWnd);
  m_windowsMenu->addAction(m_showMzIntegrationParamsWndAct);

  m_showSqlMassDataSlicerWndAct =
    new QAction("Show &data slicer window", dynamic_cast<QObject *>(this));
  m_showSqlMassDataSlicerWndAct->setStatusTip(
    tr("Show the data slicer window"));
  connect(m_showSqlMassDataSlicerWndAct,
          &QAction::triggered,
          this,
          &MainWindow::showSqlMassDataSlicerWnd);
  m_windowsMenu->addAction(m_showSqlMassDataSlicerWndAct);

  m_windowsMenu->addSeparator();

  m_showAllWindowsAct =
    new QAction("Show all windows", dynamic_cast<QObject *>(this));
  m_showAllWindowsAct->setStatusTip(tr("Show all windows"));
  connect(m_showAllWindowsAct,
          &QAction::triggered,
          this,
          &MainWindow::showAllWindows);
  m_windowsMenu->addAction(m_showAllWindowsAct);

  m_saveWorkspaceAct =
    new QAction("Save workspace", dynamic_cast<QObject *>(this));
  m_saveWorkspaceAct->setStatusTip(tr("Save workspace"));
  connect(
    m_saveWorkspaceAct, &QAction::triggered, this, &MainWindow::saveWorkspace);
  m_windowsMenu->addAction(m_saveWorkspaceAct);

  // Help menu
  m_helpMenu = menuBar()->addMenu("&Help");

  m_aboutAct = new QAction(tr("&About"), dynamic_cast<QObject *>(this));
  m_aboutAct->setShortcut(tr("Ctrl+H"));
  m_aboutAct->setStatusTip(tr("Show the application's About box"));
  connect(m_aboutAct, &QAction::triggered, this, &MainWindow::about);
  m_helpMenu->addAction(m_aboutAct);

  m_aboutQtAct = new QAction(tr("About &Qt"), dynamic_cast<QObject *>(this));
  m_aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
  connect(m_aboutQtAct, &QAction::triggered, this, &Application::aboutQt);
  m_helpMenu->addAction(m_aboutQtAct);
}


void
MainWindow::showMzIntegrationParamsHelp(double smallestStepMedian)
{
  QSettings settings;

  bool noShowHelp =
    settings.value("MainWindow_nomoreShowMzIntegrationParamsHelp", false)
      .toBool();

  if(noShowHelp)
    return;

  QDialog *explanationDlg = new QDialog;
  QVBoxLayout *verticalLayout;
  QTextEdit *textEdit;
  QCheckBox *doNotShowAgainCheckBox;
  QPushButton *okPushButton;
  if(explanationDlg->objectName().isEmpty())
    explanationDlg->setObjectName(QStringLiteral("explanationDlg"));
  explanationDlg->resize(319, 300);
  verticalLayout = new QVBoxLayout(explanationDlg);
  verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
  textEdit = new QTextEdit(explanationDlg);
  textEdit->setObjectName(QStringLiteral("textEdit"));
  verticalLayout->addWidget(textEdit);

  QString helpMsg =
    QString(
      "When mass spectral data are loaded from file,\n"
      "statistics are computed and are used to configure\n"
      "the mass spectral integrations to a mass spectrum.\n"
      "These statistics are shown in the console window. \n"
      "\n"
      "The last file loaded had a median value for the\n"
      "smallest m/z delta of %1 m/z. This value is used \n"
      "to configure the bin size. Please, check the m/z \n"
      "integration parameters window. Note that mass spectral\n"
      "integrations to a mass spectrum sometimes need some tweaking\n"
      "of the integration parameters.\n\n"
      "Please, refer to the user manual for a detailed explanation.")
      .arg(smallestStepMedian, 0, 'f', 3);

  textEdit->setPlainText(helpMsg);

  doNotShowAgainCheckBox = new QCheckBox(explanationDlg);
  doNotShowAgainCheckBox->setObjectName(
    QStringLiteral("doNotShowAgainCheckBox"));

  verticalLayout->addWidget(doNotShowAgainCheckBox);

  okPushButton = new QPushButton(explanationDlg);
  okPushButton->setObjectName(QStringLiteral("okPushButton"));

  verticalLayout->addWidget(okPushButton);

  explanationDlg->setWindowTitle(
    QString("%1 - m/z integration parameters help").arg(m_moduleName));
  okPushButton->setText("Ok");

  connect(okPushButton, &QPushButton::clicked, explanationDlg, &QDialog::close);

  QMetaObject::connectSlotsByName(explanationDlg);
  doNotShowAgainCheckBox->setText("Got it, do not show again");

  connect(
    doNotShowAgainCheckBox, &QCheckBox::clicked, [=](const bool &isChecked) {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
      QSettings settings;
      settings.setValue("MainWindow_nomoreShowMzIntegrationParamsHelp",
                        isChecked);
    });

  // Make sure the m/z integration parameters window is visible.
  showMzIntegrationParamsWnd();

  QTimer::singleShot(30000, explanationDlg, &QDialog::close);

  explanationDlg->exec();
}


/*/js/
 * MainWindow.about()
 *
 * Open an About dialog window.
 *
 * Ex:
 * mainWindow.about();
 */

//! Open a module-specific About dialog window.
/*!

  \sa AboutMineXpertDlg.

*/
void
MainWindow::about()
{
  // The application name will be set automatically by default parameter
  // value.
  AboutMineXpertDlg *dlg = new AboutMineXpertDlg(this);

  dlg->show();
}

//! Write the window settings to be able to restore the window positions.
void
MainWindow::writeSettings()
{
  QSettings settings;
  settings.setValue("MainWindow_geometry", saveGeometry());
  settings.setValue("MainWindow_windowState", saveState());
}


//! Read the window settings to be able to restore the window positions.
void
MainWindow::readSettings()
{
  QSettings settings;
  restoreGeometry(settings.value("MainWindow_geometry").toByteArray());
  restoreState(settings.value("MainWindow_windowState").toByteArray());
}


//! Log \p msg to the console message.
/*!

  \param msg string message to display.

  \param color color to use for the printout.

  \param overwrite if true, then the new message will erase the last message.

*/
void
MainWindow::logConsoleMessage(QString msg, const QColor &color, bool overwrite)
{
  mp_consoleWnd->logMessage(msg, color, overwrite);

  // Process the app events, to refresh the GUI.
  QApplication::processEvents();
}


//! Update the progress feedback using the \p msg string and the \p currentValue
//! value.
void
MainWindow::updateFeedback(QString msg,
                           int currentValue,
                           bool setVisible,
                           int logType,
                           int startValue,
                           int endValue)
{
  // qDebug() << __FILE__ << __LINE__
  //<< "Entering" << __FUNCTION__
  //<< "with message: " << msg
  //<< "currentValue:" << currentValue
  //<< "set visible:" << setVisible
  //<< "log type:" << logType
  //<< "startValue:" << startValue
  //<< "endValue:" << endValue;


  if(logType & LogType::LOG_TO_CONSOLE)
    logConsoleMessage(msg);

  if(logType & LogType::LOG_TO_STATUS_BAR)
    {
      mp_statusBarLabel->setVisible(setVisible);
      mp_statusBarCancelPushButton->setVisible(setVisible);
      mp_statusBarProgressBar->setVisible(setVisible);

      if(startValue != -1)
        mp_statusBarProgressBar->setMinimum(startValue);
      if(endValue != -1)
        mp_statusBarProgressBar->setMaximum(endValue);

      mp_statusBarProgressBar->setValue(currentValue);

      mp_statusBarLabel->setText(msg);
    }
}


//! Update the status bar message with the \p msg string.
void
MainWindow::updateStatusBar(QString msg)
{
  statusBar()->showMessage(msg, 5000);
}


//! Log a message to various places specified with \p logType.
void
MainWindow::dataLoadMessage(QString message, int logType)
{
  if(logType & LOG_TO_CONSOLE)
    logConsoleMessage(message);
  else if(logType & LOG_TO_CONSOLE_OVERWRITE)
    logConsoleMessage(message, QColor(), true);

  if(logType & LOG_TO_STATUS_BAR)
    statusBarMessage(message);

  // qDebug() << __FILE__ << __LINE__
  // << "dataLoadMessage:" << message;
}


//! Update the progress bar located in the status bar with \p value.
void
MainWindow::statusBarProgress(int value)
{
  // Created invisible, thus make it visible first.
  mp_statusBarProgressBar->setVisible(true);
  mp_statusBarProgressBar->setValue(value);

  // Process the app events, to refresh the GUI.
  QApplication::processEvents();
}


//! Update the status bar label with \p msg.
void
MainWindow::statusBarMessage(QString msg)
{
  // Created invisible, thus make it visible first.
  mp_statusBarLabel->setVisible(true);
  mp_statusBarLabel->setText(msg);

  // Process the app events, to refresh the GUI.
  QApplication::processEvents();
}


//! Make sure the TIC chromatogram window is visible.
void
MainWindow::showTicChromWnd()
{
  mp_ticChromWnd->activateWindow();
  mp_ticChromWnd->raise();
  mp_ticChromWnd->show();
}


//! Make sure the mass spectrum window is visible.
void
MainWindow::showMassSpecWnd()
{
  mp_massSpecWnd->activateWindow();
  mp_massSpecWnd->raise();
  mp_massSpecWnd->show();
}


//! Make sure the drift spectrum window is visible.
void
MainWindow::showDriftSpecWnd()
{
  mp_driftSpecWnd->activateWindow();
  mp_driftSpecWnd->raise();
  mp_driftSpecWnd->show();
}


//! Make sure the color map window is visible.
void
MainWindow::showColorMapWnd()
{
  mp_colorMapWnd->activateWindow();
  mp_colorMapWnd->raise();
  mp_colorMapWnd->show();
}


//! Open the dialog window that lists all the opened mass data files.
void
MainWindow::showOpenMassSpectraDlg()
{
  mp_openSpectraDlg->activateWindow();
  mp_openSpectraDlg->raise();
  mp_openSpectraDlg->show();
}


//! Make sure the console window is visible.
void
MainWindow::showConsoleWnd()
{
  mp_consoleWnd->activateWindow();
  mp_consoleWnd->raise();
  mp_consoleWnd->show();
}


//! Make sure the scripting window is visible.
void
MainWindow::showScriptingWnd()
{
  mp_scriptingWnd->activateWindow();
  mp_scriptingWnd->raise();
  mp_scriptingWnd->show();
}

//! Make sure the xicExtraction window is visible.
void
MainWindow::showXicExtractionWnd()
{
  mp_xicExtractionWnd->activateWindow();
  mp_xicExtractionWnd->raise();
  mp_xicExtractionWnd->show();
}


//! Make sure the m/z integration parameters window is visible.
void
MainWindow::showMzIntegrationParamsWnd()
{
  mp_mzIntegrationParamsWnd->activateWindow();
  mp_mzIntegrationParamsWnd->raise();
  mp_mzIntegrationParamsWnd->show();
}


//! Make sure the m/z integration parameters window is visible.
void
MainWindow::showSqlMassDataSlicerWnd()
{
  mp_sqlMassDataSlicerWnd->activateWindow();
  mp_sqlMassDataSlicerWnd->raise();
  mp_sqlMassDataSlicerWnd->show();
}


//! Make sure all the main windows of the program are visible.
void
MainWindow::showAllWindows()
{
  mp_ticChromWnd->show();
  mp_massSpecWnd->show();
  mp_driftSpecWnd->show();
  mp_colorMapWnd->show();
  mp_openSpectraDlg->show();
  mp_consoleWnd->show();
  mp_scriptingWnd->show();
  mp_xicExtractionWnd->show();
  mp_mzIntegrationParamsWnd->show();
  mp_sqlMassDataSlicerWnd->show();
}


//! Save the settings of all the main windows of the program.
void
MainWindow::saveWorkspace()
{
  mp_ticChromWnd->writeSettings();
  mp_massSpecWnd->writeSettings();
  mp_driftSpecWnd->writeSettings();
  mp_colorMapWnd->writeSettings();
  mp_openSpectraDlg->writeSettings();
  mp_consoleWnd->writeSettings();
  mp_scriptingWnd->writeSettings();
  mp_xicExtractionWnd->writeSettings();
  mp_mzIntegrationParamsWnd->writeSettings();
  mp_sqlMassDataSlicerWnd->writeSettings();
}


//! Destroy all the plot widgets that have been created in \p originPlotWidget.
/*!

  The program records, thanks to DataPlotWidgetRelation objects and thanks to
  the DataPlotWidgetRelationer manager, all the relationships between plot
  widgets. For example, when an integration is performed inside a TIC
  chromatogram window, and that as a result of that integration, a mass
  spectrum plot widget is created to display the mass spectrum result, the a
  relationship is stored between both plot widgets. In this example, the TIC
  chromatogram plot widget is the origin plot widget and the mass spectrum
  plot widget is the target plot widget. Now, if the user deletes the TIC
  chromatogram plot widget, the mass spectrum plot widget needs to be
  destroyed also. This function does that: given an origin plot widget, it
  destroys \e recursively all the plot widgets that were created as target of
  this origin plot widget.

*/
void
MainWindow::destroyAlsoAllTargetPlotWidgets(
  AbstractPlotWidget *originPlotWidget)
{

  // We are getting a pointer to a plot widget that is being destroyed.
  // That plot widget is not necessarily a root plot widget (but could be,
  // so we need to be watching). What we want to do is trace down all the
  // descendants of originPlotWidget (that is, all the targets of
  // originPlotWidget).

  if(originPlotWidget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QString originPlotWidgetString = QString("0x%1").arg(
    (quintptr)originPlotWidget, QT_POINTER_SIZE * 2, 16, QChar('0'));

  // qDebug()
  //<< __FILE__ << __LINE__
  //<< "Entering MainWindow::destroyAlsoAllTargetPlotWidgets with param:"
  //<< originPlotWidgetString << originPlotWidget << originPlotWidget->desc()
  //<< "undergoing destruction. There are currently "
  //<< m_dataPlotWidgetRelationer.m_relationList.size()
  //<< "relations in total.";

  // First off get the root plot widget and the root relation.

  DataPlotWidgetRelation *p_rootRelation = Q_NULLPTR;

  const AbstractPlotWidget *rootPlotWidget =
    m_dataPlotWidgetRelationer.rootPlotWidget(originPlotWidget,
                                              &p_rootRelation);

  // Sanity checks.

  if(rootPlotWidget == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "It is not possible that a plot widget has no root plot widget."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(p_rootRelation == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "It is not possible that no plot relation be found for a plot widget."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Get all the relations that have originPlotWidget as origin plot widget:
  // This means actually that we are gathering all the relations making
  // originPlotWidget the parent of all the descendants. We want to get a
  // handle to the descendants by looking at the target plot widget member
  // of all the returned relations.

  QList<DataPlotWidgetRelation *> matchingOriginList;
  m_dataPlotWidgetRelationer.getRelationsByOrigin(originPlotWidget,
                                                  &matchingOriginList);

  // qDebug() << __FILE__ << __LINE__ << "The plot widget being destroyed has "
  //<< matchingOriginList.size() << "target plot widgets (that is, siblings).\n"
  //<< "Going to iterate in the list of target plot widgets";

  // Now, for each relation get the *target* widget and make sure we ask
  // it to destroy itself.

  for(int iter = 0; iter < matchingOriginList.size(); ++iter)
    {
      DataPlotWidgetRelation *relation = matchingOriginList.at(iter);

      // qDebug() << __FILE__ << __LINE__ << "Iteration " << iter
      //<< "relation: " << relation;

      // Sanity check.
      // It is never possible that a relation has a target plot widget that is
      // Q_NULLPTR. Conversely, it is possible that a relation has an origin
      // plot widget that is Q_NULLPTR: when the plot widget is the very first
      // plot widget to be created to display the TIC chromatogram data that
      // are computed immediately after loading data from a file.

      if(relation->targetPlotWidget() == Q_NULLPTR)
        {
          qFatal(
            "Fatal error at %s@%d -- %s. "
            "It cannot be possible that a plot widget relation has a "
            "target plot widget member that is Q_NULLPTR."
            "Program aborted.",
            __FILE__,
            __LINE__,
            __FUNCTION__);
        }

      // qDebug() << __FILE__ << __LINE__ << "Iteration " << iter
      //<< "relation: " << relation
      //<< "target plot widget:" << relation->targetPlotWidget()
      //<< "with name:" << relation->targetPlotWidget()->name();

      // Upon running the below function, the destroyed widget will become the
      // originPlotWidget of a new function call to this function. This is
      // recursive execution of the function until all the target widgets have
      // lost their target widgets, and thus become origin plot widgets...

      // qDebug() << __FILE__ << __LINE__
      //<< "Going to call "
      //"relation->targetPlotWidget()->shouldDestroyPlotWidget"
      //<< "on" << relation->targetPlotWidget();

      const_cast<AbstractPlotWidget *>(relation->targetPlotWidget())
        ->shouldDestroyPlotWidget();
    }
  // End of
  // for (int iter = 0; iter < matchingOriginList.size(); ++iter)

  // qDebug() << __FILE__ << __LINE__ << "Finished iterating in the list of "
  //"relations that match the origin plot " "widget.";

  // At this point we have recursively destroyed all the descending plot
  // widgets (siblings and downward siblings). What if the plot widget
  // passed as parameter was actually a root plot widget ? In that specific
  // case we should be acting more thoroughly. We had initially gotten the
  // pointers to the root plot widget and to the root relation.

  if(rootPlotWidget == originPlotWidget)
    {
      // Yes, the user is actually destroying the root plot widget.

      // qDebug() << __FILE__ << __LINE__
      //<< "Now handling the origin root plot widget case."
      //<< "Deleting the root plot widget and root relation:"
      //<< rootPlotWidget << rootPlotWidget->name() << "/"
      //<< p_rootRelation;

      QColor color = rootPlotWidget->color();

      // The root plot widget must have the open spectra dialog list
      // widget item member datum set. Use it so that we can delete that item.

      delete const_cast<QListWidgetItem *>(
        p_rootRelation->openSpectraListWidgetItem());

      m_dataPlotWidgetRelationer.clearRelation(p_rootRelation);
      delete rootPlotWidget;
      rootPlotWidget = Q_NULLPTR;

      // Finally, we can recycle the plotting color.
      recyclePlottingColor(color);

      return;
    }

  // At this point, we should be able to delete the original plot widget, the
  // one that started the cascade. We discovered that the original plot
  // widget was not the root plot widget. So we still need to do that work.

  // We want to clear the relation for this plot widget before deleting
  // it. To get its relation, we need to ask for the relations that have
  // their target matching this plot widget:
  QList<DataPlotWidgetRelation *> matchingTargetList;
  m_dataPlotWidgetRelationer.getRelationsByTarget(originPlotWidget,
                                                  &matchingTargetList);

  if(matchingTargetList.size() == 1)
    {
      // Indeed, this widget is the last leaf of a branch, delete it and
      // also clear its relation:

      m_dataPlotWidgetRelationer.clearRelation(matchingTargetList.first());

      // qDebug() << __FILE__ << __LINE__
      //<< "Now deleting the last leaf plot widget" << originPlotWidget
      //<< originPlotWidget->name() << originPlotWidget->desc();

      delete originPlotWidget;
      originPlotWidget = Q_NULLPTR;
    }
  else if(matchingTargetList.size() == 0)
    {
      // qDebug() << __FILE__ << __LINE__ << "No matching target list, the "
      //"origin root plot widget was "
      //"deleted already.";
    }
}


//! Toggle the visibility of the matching graph in the multigraph plot widget.
/*!

  Each time a new tic chromatogram or mass spectrum or drift spectrum is
  created, a matching graph is added in the multigraph plot widget. The
  matching graph can be shown/hidden using this function.

  \param widget plot widget for which the matching graph in the multigraph
  plot widget needs to be toggled.

  \param recursive tells if the toggle operation needs to be propagated to all
  the target widgets recursively.

*/
void
MainWindow::toggleMultiGraph(AbstractPlotWidget *widget, bool recursive)
{
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QList<AbstractPlotWidget *> plotWidgetList;

  // Whatever the recursive value, we want to handle the widget:
  plotWidgetList.append(widget);

  if(recursive)
    {

      // The user has asked that the param widget and all its targets have
      // their multigraph graph shown if it was hidden or hidden if it was
      // shown.

      m_dataPlotWidgetRelationer.targetsRecursive(widget, &plotWidgetList);
    }

  // Now that we have seeded the plot wiget list, just work on it.
  for(int iter = 0; iter < plotWidgetList.size(); ++iter)
    plotWidgetList.at(iter)->toggleMultiGraph();
}


//! Set \c m_isOperationCancelled to true and emit a signal.
/*!

  The signal is emitted so that any listening data load/computation operation
  might be cancelled.

*/
void
MainWindow::cancelPushButtonClicked()
{
  m_isOperationCancelled = true;
  emit cancelOperationSignal();
}


//! Boilerplate code to finish the data file load process.
void
MainWindow::concludeFileLoading()
{
  // Hide the cancel button.
  mp_statusBarCancelPushButton->setVisible(false);

  // We may have gotten here because the operation was cancelled. In any case,
  // reset the boolean value.
  m_isOperationCancelled = false;

  // At this point, the mainwindow should show a message telling that the
  // file was successfully loaded, since loading a file is actually two
  // things in sequence:
  // 1. Opening the file and loading the data
  // 2. Calculating a TIC chromatogram on the basis of the loaded data.

  statusBarMessage("Done loading file.");
  QTimer::singleShot(3000, this, [=]() {
    this->updateFeedback("", 0);
    this->mp_statusBarProgressBar->setVisible(false);
  });
}


//! Create initial TIC chromatogram and color map widgets.
/*!

  When a new mass data file is loaded in full, the very first computation that
  is automatically performed with the data stored in \p p_massSpecDataSet is the
  calculation of the TIC chromatogram. If the file contains ion mobility data,
  then a mz=f(dt) color map is also automatically computed.

  New widgets are allocated to display the TIC chromatogram and (optionally)
  the color map.

  Finally, relations are created to seed the relation tree. Note that the
  color map plot widget is registered as a target of the TIC chromatogram plot
  widget, which in turn had been registered as the root plot widget.

*/
void
MainWindow::seedInitialPlots(MassSpecDataSet *p_massSpecDataSet)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "() - enter";

  // Each new plot must have a different color than all the others already
  // used, such that we can distinguish the files throughout all the
  // program. We have two lists of colors, the list of available colors
  // (not used currently) and the list of used colors. When a new plot is
  // created, one color is selected from the available colors list and
  // that color is moved to the used colors list so that it cannot be
  // reused. When a graph is closed, its color is migrated from the list
  // of used colors to the list of available colors.

  QColor color;
  QColor *useColor = Q_NULLPTR;

  // Let's see if there is still a color available:

  if(m_colorList.isEmpty())
    {
      // Get a new color, since we do not have any more available.

      color = QColorDialog::getColor(
        Qt::white, this, "Please select a color for the plot");

      if(!color.isValid())
        {
          useColor = new QColor(Qt::black);
        }
      else
        {
          useColor = new QColor(color);
          m_usedColorList.append(useColor);
        }
    }
  else
    {
      useColor = m_colorList.first();
      m_usedColorList.append(useColor);
      m_colorList.takeFirst();
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "setting wished color:" << *useColor;

  // qDebug() << __FILE__ << __LINE__ << "Calculating the TIC chromatogram...";
  // Now compute the TIC and plot it. The function returns the plot widget
  // that displays the chromatogram for this mass spec data set.

  // connect(mp_ticChromWnd,
  //&TicChromWnd::initialTicDoneSignal,
  // this,
  //&MainWindow::initialTicDone);

  connect(mp_colorMapWnd,
          &ColorMapWnd::initialColorMapDoneSignal,
          this,
          &MainWindow::initialColorMapDone);

  statusBarMessage("Calculating the TIC chromatogram...");
  logConsoleMessage("Calculating the TIC chromatogram...\n");

  if(m_isScriptedOperation)
    {
      // This is a non-QThread-based version of the function
      mp_ticChromWnd->jsInitialTic(p_massSpecDataSet, *useColor);
    }
  else
    {
      // This is a QThread-based version of the function
      mp_ticChromWnd->initialTic(p_massSpecDataSet, *useColor);
    }

  // The initialTic function from TicChromWnd will emit the
  // initialTicDoneSignal. From that signal slot function, the process of
  // calculating the color map is started.
}


} // namespace msXpSmineXpert
