/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QDebug>
#include <QMenu>
#include <QMenuBar>
#include <QSettings>
#include <QStyleOptionSlider>

/////////////////////// pappsomspp includes


/////////////////////// libXpertMassCore includes


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/WindowGeometryManager.hpp>


/////////////////////// Local includes
#include "../nongui/MassDataIntegratorTask.hpp"
#include "Application.hpp"
#include "BasePlotWnd.hpp"
#include "MsFragmentationSpecDlg.hpp"
#include "MzIntegrationParamsDlg.hpp"
#include "PlotAreaOverlay.hpp"
#include "ProgramWindow.hpp"
#include "SaveToGraphicsFileDlg.hpp"
#include "ui_BasePlotCompositeWidget.h"
#include "ui_BasePlotWnd.h"

namespace MsXpS
{
namespace MineXpert
{


//! Construct an BasePlotWnd instance.
BasePlotWnd::BasePlotWnd(QWidget *parent,
                         const QString &title,
                         const QString &settingsTitle,
                         const QString &description)
  : QMainWindow(parent),
    mp_programWindow(static_cast<ProgramWindow *>(parent)),
    m_title(title),
    m_settingsTitle(settingsTitle),
    m_description(description),
    m_ui(new ::Ui::BasePlotWnd)
{
  if(parent == Q_NULLPTR)
    qFatal() << "Programming error.";

  m_ui->setupUi(this);

  if(!initialize())
    qFatal() << "Programming error.";
}

//! Destruct \c this BasePlotWnd instance.
BasePlotWnd::~BasePlotWnd()
{
  // qDebug();
  writeSettings();
}

//! Write the settings to as to restore the window geometry later.
void
BasePlotWnd::writeSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  // qDebug() << "Saving window geometry.";

  libXpertMassGui::WindowGeometryManager::save(
    dynamic_cast<const QWidget *>(this), m_settingsTitle, settings);

  settings.beginGroup(m_settingsTitle);

  settings.setValue("isVisible", isVisible());

  settings.endGroup();
}

//! Read the settings to as to restore the window geometry.
void
BasePlotWnd::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  qDebug() << "Restoring window geometry.";

  libXpertMassGui::WindowGeometryManager::restore(
    this, m_settingsTitle, settings);

  settings.beginGroup(m_settingsTitle);

  setVisible(settings.value("isVisible").toBool());

  settings.endGroup();
}

//! Handle the close event.
void
BasePlotWnd::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  // qDebug();
  writeSettings();
  hide();
}

ProgramWindow *
BasePlotWnd::getProgramWindow()
{
  return mp_programWindow;
}

//! Initialize the window.
bool
BasePlotWnd::initialize()
{
  setWindowTitle(QString("MineXpert3 - %1").arg(m_title));

  // We do not want to destroy on close !!!
  // setAttribute(Qt::WA_DeleteOnClose);

  // The default window icon.
  QString pixmap_file_name = ":/images/icons/32x32/minexpert3.png";
  QPixmap icon_pixmap(pixmap_file_name);
  QIcon icon(icon_pixmap);
  setWindowIcon(icon);

  // This will take care of the main menu that sits in the first button of the
  // tool bar.
  setupToolBar();

  /****************** The widgetry layouting ******************/

  mp_centralWidget = new QWidget(this);
  mp_centralWidget->setObjectName(QStringLiteral("mp_centralWidget"));

  this->setCentralWidget(mp_centralWidget);

  // Passing the widget as parent to the layout constructor is the same as
  // calling widget->setLayout(layout).
  mp_centralWidgetGridLayout = new QGridLayout(mp_centralWidget);
  mp_centralWidgetGridLayout->setObjectName(
    QStringLiteral("mp_centralWidgetGridLayout"));

  // Now that the main layout is set to the central widget, let's pack the
  // scroll area in it. Upon adding the area to the layout, that area is
  // reparented to the layout that is responsible for its destruction.
  mp_scrollArea = new QScrollArea(nullptr);
  mp_scrollArea->setObjectName(QStringLiteral("mp_scrollArea"));
  mp_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  mp_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

  // We accept that the plots are resized, but we set a minimum size when we
  // create them.
  mp_scrollArea->setWidgetResizable(true);
  mp_scrollArea->setAlignment(Qt::AlignCenter);

  mp_scrollAreaWidgetContents = new QWidget(nullptr);
  mp_scrollAreaWidgetContents->setObjectName(
    QStringLiteral("mp_scrollAreaWidgetContents"));
  mp_scrollAreaWidgetContents->setGeometry(QRect(0, 0, 766, 525));

  mp_scrollAreaVBoxLayout = new QVBoxLayout(mp_scrollAreaWidgetContents);
  mp_scrollAreaVBoxLayout->setObjectName(
    QStringLiteral("mp_scrollAreaVBoxLayout"));

  mp_splitter = new QSplitter(Qt::Vertical, mp_scrollAreaWidgetContents);
  mp_splitter->setHandleWidth(3);

  // We want the host of all the plot widget to be this QSplitter instance
  // because this way, the user has some leeway to resize the plot widgets
  // inside the scroll area.
  mp_scrollAreaVBoxLayout->addWidget(mp_splitter);

  mp_scrollArea->setWidget(mp_scrollAreaWidgetContents);

  // We want the scroll area to lie just below the vertical rule slider that
  // we'll handle later. So set the row to 1 and the column to 0.
  mp_centralWidgetGridLayout->addWidget(mp_scrollArea, 1, 0);

  // At this point, make sure we allocate the transparent overlay widget in
  // which we'll draw items that need to be drawn over all the widgets.

  mpa_plotAreaOverlay = new PlotAreaOverlay(mp_centralWidget);
  mpa_plotAreaOverlay->setObjectName("mpa_plotAreaOverlay");
  mpa_plotAreaOverlay->raise();
  mpa_plotAreaOverlay->show();

  // At this point that the overlay plot has been created, we can create the
  // slider that will govern the position of the vertical rule that span the
  // whole height of the scroll area.
  mp_vertRuleSlider = new VerticalRuleSlider(mp_centralWidget);
  mp_vertRuleSlider->setSingleStep(1);
  mp_vertRuleSlider->setPageStep(1);
  mp_vertRuleSlider->setMinimum(1);
  mp_vertRuleSlider->setMaximum(mp_centralWidget->size().width());

#if 0
// We want to be certain that there is no gap between the left side of
// the rectanble of these three widgets, because we need to make some
// calculations for the slider handle position.

  if(mp_scrollArea->rect().left() != mp_vertRuleSlider->rect().left())
    qDebug() << "The difference is:"
             << mp_vertRuleSlider->rect().left() - mp_scrollArea->rect().left();
  else
    qDebug() << "There is no difference:"
             << mp_vertRuleSlider->rect().left() - mp_scrollArea->rect().left();


  if(mpa_plotAreaOverlay->rect().left() != mp_vertRuleSlider->rect().left())
    qDebug() << "The difference is:"
             << mp_vertRuleSlider->rect().left() -
                  mpa_plotAreaOverlay->rect().left();
  else
    qDebug() << "There is no difference:"
             << mp_vertRuleSlider->rect().left() -
                  mpa_plotAreaOverlay->rect().left();

  if(mpa_plotAreaOverlay->rect().width() != mp_vertRuleSlider->rect().width())
    qFatal() << "Not the same:"
             << mpa_plotAreaOverlay->rect().width() -
                  mp_vertRuleSlider->rect().width();
#endif

  // Start hidden.
  mp_vertRuleSlider->setVisible(m_isLockedYMarker);

  // We add the ruler as the first widget from top (row 0, column 0)
  mp_centralWidgetGridLayout->addWidget(mp_vertRuleSlider, 0, 0);

  connect(mp_vertRuleSlider,
          &VerticalRuleSlider::sliderHandleCenterPositionXChangedSignal,
          this,
          &BasePlotWnd::verticalRuleSliderHandleRectCenterXValueChanged);

  readSettings();

  return true;
}

void
BasePlotWnd::verticalRuleSliderHandleRectCenterXValueChanged(int x_position)
{

  // qDebug()
  //   << "New vertical rule slider handle center point x-axis value (pixels):"
  //   << x_position;

  // The user has modified the position of the slider.
  // The slider itself has computed the position of the center point of the
  // slider handle and emitted a signal with the x() of that center point. This
  // is what we get here, as a pixel value within the referential of the whole
  // slider rectangle (that is, starting from x=0 being the left side of that
  // rectangle).

  // Draw the line at this handle center x position.
  mpa_plotAreaOverlay->setLastHorSliderValue(x_position);

  // There are two situations:
  // 1. The user has not locked the plot widgets along the x-axis. In this case,
  // we do only draw the vertical rule at the new position.

  if(!m_isLockedXRange)
    {
      mp_vertRuleSlider->setXposLabelVisible(false);
      mpa_plotAreaOverlay->update();
      return;
    }

  // 2. The user has locked the plot widgets along the x-axis. In this case,
  // we also need to draw a label next to the slider handle to display the
  // x-axis position of the vertical rule. The x-axis value that we want to
  // print in the label needs to be computed starting from the pixel x position
  // of the center point of the rectangle of the slider handle:

  /*
  |------------0
  */

  // This is the slider, with the dot in the O being the center of the handle
  // of which we get the x_position.
  // That x_position is relative to the left side of the rectangle in which
  // the whole slider is packed.

  // Because the slider is packed in this plot window along with the
  // custom plot widgets, we need to see if these two widgets are aligned
  // on their left side. To check this, get the left rectangle of each
  // widget, get the left side x value in screen coordinates and compare.
  // In fact, we know they are not aligned, because the slider widget has its
  // enclosing rectangle left side right at the left border of this window,
  // while the c

  // The slider coordinates
  QRect global_slider_rect(mp_vertRuleSlider->mapToGlobal(QPoint(0, 0)),
                           mp_vertRuleSlider->size());
  // qDebug() << "mp_vertRuleSlider rect" << mp_vertRuleSlider->rect()
  //          << "global:" << global_slider_rect;
  // ======> mp_vertRuleSlider rect QRect(0,0 1232x20) global: QRect(2574,818
  // 1232x20)

  // Get the list of plot composite widgets that are in this window.
  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  // If there are not plot composite widgets, there are not plottables
  // (traces) there, and thus not x-axis value. Return.
  if(!widget_list.size())
    return;

  // The composite plot widget coordinates
  BasePlotCompositeWidget *composite_widget_p = widget_list.first();
  Q_ASSERT(composite_widget_p != nullptr);
  QRect global_composite_plot_widget_rect(
    composite_widget_p->mapToGlobal(QPoint(0, 0)), composite_widget_p->size());
  // qDebug() << "composite_widget_p rect" << composite_widget_p->rect()
  //          << "global:" << global_composite_plot_widget_rect;
  // composite_widget_p rect QRect(0,0 1195x472) global: QRect(2582,852
  // 1195x472)

  // The QCustomplot widget coordinates
  QRect global_qcustomplot_widget_rect(
    composite_widget_p->getPlotWidget()->mapToGlobal(QPoint(0, 0)),
    composite_widget_p->getPlotWidget()->size());
  // qDebug() << "qcustomplot widget rect"
  //          << composite_widget_p->getPlotWidget()->axisRect()->rect()
  //          << "global:" << global_qcustomplot_widget_rect;
  // qcustomplot widget rect QRect(33,15 1012x363) global: QRect(2658,875
  // 1060x424)

  // See what offset there is between the left side of the rectangles that
  // contain each one of the three widgets above:

  // In screen coordinates, the qcustomplot widget' left side is right of the
  // the slider widget's left side: so the offset is positive here, all we will
  // remove it from the position used for converting pixels to x-axis values
  // below.
  double x_axis_offset =
    global_qcustomplot_widget_rect.x() - global_slider_rect.x();
  // qDebug() << qSetRealNumberPrecision(5) << "The offset is:" <<
  // x_axis_offset;

  // So we see that there is an x_axis_offset of 84 px difference between the
  // left side of the slider rectangle and the left side of the qcustomplot
  // widget.

  double x_axis_coordinate =
    composite_widget_p->getPlotWidget()->xAxis->pixelToCoord(x_position -
                                                             x_axis_offset);
  // qDebug() << qSetRealNumberPrecision(5)
  //          << "Plot widget xAxis x:" << x_axis_coordinate;

#if 0
  // This produces the same result as the above call.
  QCPAbstractPlottable *plottable_p =
    composite_widget_p->plottableToBeUsedAsIntegrationSource();
  double coord_x = 0;
  double coord_y = 0;
  plottable_p->pixelsToCoords(x_position - x_axis_offset, 0, coord_x, coord_y);
  qDebug() << qSetRealNumberPrecision(5) << "Plottable x:" << coord_x;
#endif

  mp_vertRuleSlider->updatePosLabelXaxisValue(x_axis_coordinate);

  // Now that the new position at which the rule needs to be drawn, we can
  // update the widget so that it repaints itself, thus drawing the vertical
  // line at the right position.
  mpa_plotAreaOverlay->update();
}

void
BasePlotWnd::resizeEvent([[maybe_unused]] QResizeEvent *event)
{
  // qDebug();

  // We way call this function without event.
  if(event != nullptr)
    {
      QMainWindow::resizeEvent(event);
    }

  // We need to update the size of the vertical rule slider.
  mp_vertRuleSlider->setMaximum(size().width());
  // qDebug() << "The new slider max value:" << mp_vertRuleSlider->maximum();

  // We need to update the size of the transparent overlay plot widget so that
  // we can draw over the right surface: the surface of the scroll area.

  // We do not want that the size of the overlay plot widget encompasses the
  // scroll bars, so we craft a QSize object.

  QSize reduced_size(mp_scrollArea->size().width() -
                       mp_scrollArea->verticalScrollBar()->size().width(),
                     mp_scrollArea->size().height() -
                       mp_scrollArea->horizontalScrollBar()->size().height());

  mpa_plotAreaOverlay->resize(reduced_size);

  // Now move that plot overlay widget to he position of the scroll area.
  mpa_plotAreaOverlay->move(mp_scrollArea->pos());

  // At this point we need to update the vertical ruler position.
  mpa_plotAreaOverlay->update();
}

void
BasePlotWnd::setupToolBar()
{
  /****************** The button tool bar ******************/
  /****************** The button tool bar ******************/

  mp_toolBar = addToolBar("");
  mp_toolBar->setObjectName(QStringLiteral("mp_toolBar"));

  // Make sure a right mouse button click does not elicit the context menu
  // that when clicked made the toolbar disappear totally.
  mp_toolBar->setContextMenuPolicy(Qt::PreventContextMenu);

  // Create the main menu that sits into the first button of the toolbar.
  createMainMenu();

  // The tool bar non-menu buttons

  const QIcon xRangeIcon = QIcon(":/images/svg/lock-x-range.svg");
  mp_lockXRangeAct =
    new QAction(xRangeIcon, tr("Lock X range (Ctrl+L, X)"), this);
  mp_lockXRangeAct->setCheckable(true);
  mp_lockXRangeAct->setShortcut(QKeySequence("Ctrl+L, X"));
  mp_lockXRangeAct->setStatusTip(tr("Lock X range"));
  connect(
    mp_lockXRangeAct, &QAction::toggled, this, &BasePlotWnd::lockXRangeToggle);
  mp_toolBar->addAction(mp_lockXRangeAct);

  const QIcon yRangeIcon = QIcon(":/images/svg/lock-y-range.svg");
  mp_lockYRangeAct =
    new QAction(yRangeIcon, tr("Lock Y range (Ctrl+L, Y)"), this);
  mp_lockYRangeAct->setCheckable(true);
  mp_lockYRangeAct->setShortcut(QKeySequence("Ctrl+L, Y"));
  mp_lockYRangeAct->setStatusTip(tr("Lock Y range"));
  connect(
    mp_lockYRangeAct, &QAction::toggled, this, &BasePlotWnd::lockYRangeToggle);

  mp_toolBar->addAction(mp_lockYRangeAct);

  const QIcon yMarkerIcon = QIcon(":/images/svg/lock-y-marker.svg");
  mp_lockYMarkerAct       = new QAction(
    yMarkerIcon, tr("Lock multi-widget Y marker (Ctrl+L, W)"), this);
  mp_lockYMarkerAct->setCheckable(true);
  mp_lockYMarkerAct->setShortcut(QKeySequence("Ctrl+L, W"));
  mp_lockYMarkerAct->setStatusTip(tr("Lock multi-widget Y marker"));
  connect(mp_lockYMarkerAct,
          &QAction::toggled,
          this,
          &BasePlotWnd::lockYMarkerToggle);

  mp_toolBar->addAction(mp_lockYMarkerAct);
}

void
BasePlotWnd::createMainMenu()
{
  // The toolbar menu button

  ProgramWindow *program_window_p =
    static_cast<Application *>(qApp)->getProgramWindow();
  QString action_label;
  libXpertMassGui::ActionId action_id;
  QKeySequence key_sequence;
  QAction *action_p = nullptr;

  // The main menu in the form of a button with an icon like thoses found for
  // the menus in mobile phone applications.
  mp_mainMenu = new QMenu(this);

  const QIcon main_menu_icon =
    QIcon(":/images/svg/mobile-phone-like-menu-button.svg");

  mp_mainMenuToolButton = new QToolButton(this);
  mp_mainMenuToolButton->setIcon(main_menu_icon);
  mp_mainMenuToolButton->setMenu(mp_mainMenu);

  mp_toolBar->addWidget(mp_mainMenuToolButton);

  key_sequence = QKeySequence("Ctrl+W, M");
  action_label = QString("Plot window menu");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  mp_mainMenuToolButton->setToolTip(action_p->text());
  // When the main menu push button is clicked, the menu shows up.
  connect(action_p, &QAction::triggered, this, [&]() {
    if(isActiveWindow())
      {
        // qDebug() << "Trigger." << this->windowTitle();

        // Show the menu aligned to the bottom of the button, otherwise the menu
        // appears at the top left corner of the screen.
        mp_mainMenu->exec(mp_mainMenuToolButton->mapToGlobal(
          QPoint(0, mp_mainMenuToolButton->height())));
        // mp_mainMenu->show();
      }
  });

  ////////////// MZ INTEG PARAMS and MS FRAG SPEC ////////////////////
  ////////////// MZ INTEG PARAMS and MS FRAG SPEC ////////////////////

  // We want to be able to set the mz integ params for all the plot widgets in
  // the window in one go.

  key_sequence = QKeySequence("Ctrl+I, P");
  action_label =
    QString("Set m/z integration params for all pinned-down widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(isActiveWindow())
      setMzIntegrationParamsForAllPlotWidgets();
  });
  mp_mainMenu->addAction(action_p);

  // We want to be able to set the ms fragmentation spec for all the plot
  // widgets in the window in one go.

  key_sequence = QKeySequence("Ctrl+F, P");
  action_label =
    QString("Set MS fragmentation spec for all pinned-down widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(isActiveWindow())
      setMsFragmentationSpecForAllPlotWidgets();
  });
  mp_mainMenu->addAction(action_p);

  ////////////// END MZ INTEG PARAMS and MS FRAG SPEC ////////////////////
  ////////////// END MZ INTEG PARAMS and MS FRAG SPEC ////////////////////


  // Separate the last added item from the rest.
  mp_beforePlotWidgetManagementSeparatorAction = mp_mainMenu->addSeparator();


  ////////////// PLOT WIDGET MANAGEMENT ////////////////////
  ////////////// PLOT WIDGET MANAGEMENT ////////////////////

  mp_plotWidgetManagementMenu = new QMenu("Plot widget management", this);
  mp_mainMenu->addMenu(mp_plotWidgetManagementMenu);

  // An action to allow resizing all the plot widget to the mean size. Useful
  // when the user has resized to naught some plot widget and they want to get
  // back to their initial size.

  // Old version, remove when tested.
  // mp_resetPlotSizesAct =
  //   new QAction(tr("&Reset all the plot widget sizes"), this);
  // mp_resetPlotSizesAct->setStatusTip(tr("Reset all the plot widget sizes"));
  // mp_resetPlotSizesAct->setShortcut(QKeySequence("Ctrl+R, S"));

  key_sequence = QKeySequence("Ctrl+R, S");
  action_label = QString("Reset all the plot widget sizes");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);

  // Lambda function to compute the right size for each plot widget in the
  // splitter.
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    // First off, if there are not widgets, do nothing, division by zero !!!

    if(!mp_splitter->count())
      return;
    QList<int> sizes = mp_splitter->sizes();
    int size_sum     = 0;
    foreach(int size, sizes)
      size_sum += size;

    int mean_size = size_sum / sizes.size();

    QList<int> new_sizes;

    for(int iter = 0; iter < sizes.size(); ++iter)
      new_sizes.append(mean_size);

    mp_splitter->setSizes(new_sizes);
  });
  // mp_plotWidgetManagementMenu->addAction(mp_resetPlotSizesAct);
  mp_plotWidgetManagementMenu->addAction(action_p);

  QMenu *pin_unpin_plot_widgets_menu_p =
    new QMenu("Pin-down/Unpin plot widgets...", this);
  mp_plotWidgetManagementMenu->addMenu(pin_unpin_plot_widgets_menu_p);

  // An action to push-pin all the widgets.
  key_sequence = QKeySequence("Ctrl+P, Ctrl+A");
  action_label = QString("Pin-down all the widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    std::vector<BasePlotCompositeWidget *> widget_list = allWidgets();

    for(auto &&widget_p : widget_list)
      widget_p->pinDown(true);
  });
  pin_unpin_plot_widgets_menu_p->addAction(action_p);

  // An action to unpush-pin all the widgets.
  key_sequence = QKeySequence("Ctrl+U, Ctrl+A");
  action_label = QString("Un-pin all the widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    std::vector<BasePlotCompositeWidget *> widget_list = allWidgets();

    for(auto &&widget_p : widget_list)
      widget_p->pinDown(false);
  });
  pin_unpin_plot_widgets_menu_p->addAction(action_p);

  mp_plotWidgetManagementMenu->addSeparator();

  QMenu *remove_plot_widgets_menu_p = new QMenu("Remove plot widgets...", this);
  mp_plotWidgetManagementMenu->addMenu(remove_plot_widgets_menu_p);

  // An action to remove all the pinned-down widgets.
  key_sequence = QKeySequence("Ctrl+R, Ctrl+P");
  action_label = QString("Remove pinned-down plot widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

    for(auto &&widget_p : widget_list)
      plotCompositeWidgetDestructionRequested(widget_p);
  });
  remove_plot_widgets_menu_p->addAction(action_p);

  // An action to remove all the pinned-down widgets.
  key_sequence = QKeySequence("Ctrl+R, Ctrl+D");
  action_label = QString("Remove pinned-down plot widgets and descendants");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

    for(auto &&widget_p : widget_list)
      plotCompositeWidgetDestructionRequested(widget_p);
  });
  remove_plot_widgets_menu_p->addAction(action_p);

  remove_plot_widgets_menu_p->addSeparator();

  // An action to remove all the empty plot widgets.
  key_sequence = QKeySequence("Ctrl+R, Ctrl+E");
  action_label = QString("Remove all the empty plot widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    BasePlotWnd::removeEmptyPlotWidgets();
  });
  remove_plot_widgets_menu_p->addAction(action_p);

  // An action to remove all the plot widgets.
  key_sequence = QKeySequence("Ctrl+R, Ctrl+A");
  action_label = QString("Remove all the plot widgets");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  Q_ASSERT(action_p != nullptr);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    if(!isActiveWindow())
      return;

    QList<BasePlotCompositeWidget *> widget_list =
      findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

    while(widget_list.size())
      plotCompositeWidgetDestructionRequested(widget_list.takeLast());
  });
  remove_plot_widgets_menu_p->addAction(action_p);

#if 0
  // It looks like this feature does not work.
  // An action to remove all the plot widgets and descendants.
  key_sequence = QKeySequence("Ctrl+Shift+R, Ctrl+A");
  action_label = QString("Remove all the plot widgets and descendants");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger.";

    if(!isActiveWindow())
      return;

    QList<BasePlotCompositeWidget *> widget_list =
      findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

    while(widget_list.size())
      // We want the removal to recursively remove all descendants
      plotCompositeWidgetDestructionRequested(widget_list.takeLast());
  });
  remove_plot_widgets_menu_p->addAction(action_p);
#endif

  ////////////// END PLOT WIDGET MANAGEMENT ////////////////////
  ////////////// END PLOT WIDGET MANAGEMENT ////////////////////

  mp_mainMenu->addSeparator();

  // An action to save all the pinned plot(s) to a graphics file.
  key_sequence = QKeySequence("Ctrl+S, Ctrl+P");
  action_label = QString("Save pinned plot(s) to graphics file");
  action_id.initialize("Global", "Menus", "PlotWindow", action_label);
  action_p = program_window_p->getActionManager()->installAction(action_id,
                                                                 key_sequence);
  this->addAction(action_p);
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger.";

    if(!isActiveWindow())
      return;

    BasePlotWnd::savePinnedPlotsToGraphicsFile();
  });
  mp_mainMenu->addAction(action_p);
}

void
BasePlotWnd::setMzIntegrationParamsForAllPlotWidgets()
{
  // qDebug();

  pappso::MzIntegrationParams mz_integration_params;

  MzIntegrationParamsDlg *mz_integration_params_dlg_p =
    new MzIntegrationParamsDlg(this, mz_integration_params, Qt::black);

  connect(mz_integration_params_dlg_p,
          &MzIntegrationParamsDlg::mzIntegrationParamsChangedSignal,
          this,
          &BasePlotWnd::mzIntegrationParamsChanged);

  connect(
    mz_integration_params_dlg_p,
    &MzIntegrationParamsDlg::mzIntegrationParamsDlgShouldBeDestroyedSignal,
    this,
    [&]() {
      // qDebug();
      delete QObject::sender();
    });

  mz_integration_params_dlg_p->show();
}

void
BasePlotWnd::mzIntegrationParamsChanged(
  pappso::MzIntegrationParams &mz_integration_params)
{
  // We now know what are the mz integ params. We need to propagate them to
  // all the pinned-down widgets.

  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  for(auto &&widget_p : widget_list)
    {
      QList<QCPAbstractPlottable *> destination_plottables =
        widget_p->plottablesToBeUsedAsIntegrationDestination();

      for(int iter = 0; iter < destination_plottables.size(); ++iter)
        {
          const QCPAbstractPlottable *iter_plottable_p =
            destination_plottables.at(iter);

          // Now get the ProcessingFlow for the plottable:

          ProcessingFlow *processing_flow_p =
            widget_p->getProcessingFlowPtrForPlottable(iter_plottable_p);

          // Now get the mz integration params out of it, because we need to
          // know some params like the smallest/greatest m/z bin, for example.
          // Then, we'll update it with the new params set here.

          pappso::MzIntegrationParams *iter_mz_integration_params_p = nullptr;

          iter_mz_integration_params_p =
            processing_flow_p->getDefaultMzIntegrationParams();

          if(iter_mz_integration_params_p == nullptr)
            {
              // Heap allocated
              iter_mz_integration_params_p =
                processing_flow_p->getSensibleMzIntegrationParams();
              processing_flow_p->setDefaultMzIntegrationParams(
                *iter_mz_integration_params_p);
              delete iter_mz_integration_params_p;
              iter_mz_integration_params_p =
                processing_flow_p->getDefaultMzIntegrationParams();
            }

          // Now modify the values in place according to the new settings:

          iter_mz_integration_params_p->setBinningType(
            mz_integration_params.getBinningType());
          iter_mz_integration_params_p->setBinSizeModel(
            mz_integration_params.getBinSizeModel());
          iter_mz_integration_params_p->setDecimalPlaces(
            mz_integration_params.getDecimalPlaces());
          iter_mz_integration_params_p->setRemoveZeroValDataPoints(
            mz_integration_params.isRemoveZeroValDataPoints());
        }
    }
}

pappso::MzIntegrationParams::InitializationResult
BasePlotWnd::getSavedMzIntegrationParams(
  pappso::MzIntegrationParams &mz_integration_params)
{
  pappso::MzIntegrationParams::InitializationResult result =
    static_cast<Application *>(QCoreApplication::instance())
      ->getMzIntegrationParamsFromSavedSettings(mz_integration_params);

  // qDebug() << "Got saved MzIntegrationParams:"
  //          << mz_integration_params.toString();

  return result;
}

void
BasePlotWnd::setMsFragmentationSpecForAllPlotWidgets()
{
  // qDebug();

  // Set the ms fragmentation spec for all the selected/single plottables in
  // each pinned-down plot widget (see analog function for mz integ. params).

  // Start with a fresh default spec.

  MsFragmentationSpec ms_fragmentation_spec;

  MsFragmentationSpecDlg *ms_fragmentation_spec_dlg_p =
    new MsFragmentationSpecDlg(this, ms_fragmentation_spec, Qt::black);

  connect(
    ms_fragmentation_spec_dlg_p,
    &MsFragmentationSpecDlg::msFragmentationSpecDlgShouldBeDestroyedSignal,
    this,
    [&]() {
      // qDebug();
      delete QObject::sender();
    });

  connect(ms_fragmentation_spec_dlg_p,
          &MsFragmentationSpecDlg::msFragmentationSpecChangedSignal,
          this,
          &BasePlotWnd::msFragmentationSpecChanged);

  ms_fragmentation_spec_dlg_p->show();
}

void
BasePlotWnd::msFragmentationSpecChanged(
  MsFragmentationSpec ms_fragmentation_spec)
{
  // We now know what is the ms fragmentation spec. We need to propagate it to
  // all the pinned-down widgets.

  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  for(auto &&widget_p : widget_list)
    {
      QList<QCPAbstractPlottable *> destination_plottables =
        widget_p->plottablesToBeUsedAsIntegrationDestination();

      for(int iter = 0; iter < destination_plottables.size(); ++iter)
        {
          const QCPAbstractPlottable *iter_plottable_p =
            destination_plottables.at(iter);

          // Now get the ProcessingFlow for the plottable:

          ProcessingFlow *processing_flow_p =
            widget_p->getProcessingFlowPtrForPlottable(iter_plottable_p);

          // And finally set the default ms frag pec.
          processing_flow_p->setDefaultMsFragmentationSpec(
            ms_fragmentation_spec);
        }
    }
}

void
BasePlotWnd::show()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup(m_settingsTitle);

  restoreGeometry(settings.value(m_settingsTitle + "_geometry").toByteArray());

  settings.endGroup();

  QMainWindow::show();
}

void
BasePlotWnd::savePinnedPlotsToGraphicsFile()
{
  // Get a list of pinned down plot composite widgets.

  std::vector<BasePlotCompositeWidget *> pinned_down_widgets =
    pinnedDownWidgets();

  for(auto &&composite_widget_p : pinned_down_widgets)
    {

      QColor color = Qt::black;

      QCPAbstractPlottable *plottable_p = composite_widget_p->firstPlottable();

      if(plottable_p != nullptr)
        color = plottable_p->pen().color();

      QString sample_name = composite_widget_p->m_ui->sampleNameLabel->text();
      SaveToGraphicsFileDlg dlg(
        this, composite_widget_p, "mineXpert", sample_name, color);

      dlg.exec();
    }
}


#if 0

// Old version
void
BasePlotWnd::integrateToTicIntensity(QCPAbstractPlottable *parent_plottable_p,
                                     const ProcessingFlow &processing_flow)
{
  // qDebug().noquote() << "Integrating to rt with processing flow:"
  //<< processing_flow.toString();

  // Get the ms run data set that for the graph we are going to base the
  // integration on.
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  // Pass the integrator the flow we got as param and that describes in its
  // most recent step the integration that it should perform.
  MsRunDataSetTreeMassDataIntegratorToTicInt *mass_data_integrator_p =
    new MsRunDataSetTreeMassDataIntegratorToTicInt(ms_run_data_set_csp,
                                                   processing_flow);

  // Ensure the mass data integrator messages are used.

  connect(mass_data_integrator_p,
          &MassDataIntegrator::logTextToConsoleSignal,
          mp_programWindow,
          &ProgramWindow::logTextToConsole);

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a mass data integrator to integrate the data.

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(
    this,
    // SIGNAL(integrateToTicIntensitySignal(MsRunDataSetTreeMassDataIntegratorToTicInt
    // *)),
    static_cast<void (BasePlotWnd::*)(
      MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
      &BasePlotWnd::integrateToTicIntensitySignal),
    mass_data_integrator_task_p,
    // SLOT(integrateToTicIntensity(MsRunDataSetTreeMassDataIntegratorToTicInt
    // *)),
    static_cast<void (MassDataIntegratorTask::*)(
      MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
      &MassDataIntegratorTask::integrateToTicIntensity),
    // Fundamental for signals that travel across QThread instances...
    Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // When the read task finishes, it sends a signal that we trap to go on with
  // the plot widget creation stuff.

  // Since we allocated the QThread dynamically we need to be able to destroy
  // it later, so make the connection.
  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          this,
          [this,
           thread_p,
           mass_data_integrator_p,
           parent_plottable_p,
           mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the MassDataIntegratorTask
            // allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
            this->finishedIntegratingToTicIntensity(mass_data_integrator_p,
                                                    parent_plottable_p);
          });


  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Integrating to mass spectrum");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the integrator task instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &MassDataIntegrator::cancelOperation);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped though signals.

  qRegisterMetaType<std::size_t>("std::size_t");

  // Now make all the connections that will allow the integrator to provide
  // dynamic feedback to the user via the task monitor widget.
  task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
    mass_data_integrator_p);

  // qDebug() << "going to emit integrateToTicIntensitySignal with mass "
  //"data integrator:"
  //<< mass_data_integrator_p;

  emit integrateToTicIntensitySignal(mass_data_integrator_p);

  // We do not want to make signal/slot calls more than once. This is because
  // one user might well trigger more than one integration from this window to
  // a mass spectrum. Thus we do not want that *this window be still connected
  // to the specific mass_data_integrator_task_p when a new integration is
  // triggered. We want the signal/slot pairs to be contained to specific
  // objects. Each MassSpecTracePlotWnd::integrateToMz() call must be
  // contained to a this/mass_data_integrator_task_p specific signal/slot
  // pair.
  disconnect(this,
             // SIGNAL(integrateToTicIntensitySignal(
             // MsRunDataSetTreeMassDataIntegratorToTicInt *)),
             static_cast<void (BasePlotWnd::*)(
               MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
               &BasePlotWnd::integrateToTicIntensitySignal),
             mass_data_integrator_task_p,
             // SLOT(integrateToTicIntensity(
             // MsRunDataSetTreeMassDataIntegratorToTicInt *)));
             static_cast<void (MassDataIntegratorTask::*)(
               MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
               &MassDataIntegratorTask::integrateToTicIntensity));
}
#endif


void
BasePlotWnd::integrateToTicIntensity(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    qualified_mass_spectra_vector_sp,
  const ProcessingFlow &processing_flow)
{
  qDebug().noquote() << "Integrating to mz with processing flow:"
                     << processing_flow.toString();

  // Get the ms run data set that for the graph we are going to base the
  // integration on.
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  // First prepare a vector of QualifiedMassSpectrumCstSPtr

  std::size_t qualified_mass_spectra_count = fillInQualifiedMassSpectraVector(
    ms_run_data_set_csp, qualified_mass_spectra_vector_sp, processing_flow);

  if(!qualified_mass_spectra_count)
    return;

  // qDebug() << "The number of remaining mass spectra:"
  //<< qualified_mass_spectra_count;

  // Now start the actual integration work.

  // Allocate a mass data integrator to integrate the data.

  QualifiedMassSpectrumVectorMassDataIntegratorToTicInt
    *mass_data_integrator_p =
      new QualifiedMassSpectrumVectorMassDataIntegratorToTicInt(
        ms_run_data_set_csp, processing_flow, qualified_mass_spectra_vector_sp);

  mass_data_integrator_p->setMaxThreadUseCount(
    mp_programWindow->getMaxThreadUseCount());

  // Ensure the mass data integrator messages are used.

  connect(
    mass_data_integrator_p,
    &QualifiedMassSpectrumVectorMassDataIntegrator::logTextToConsoleSignal,
    mp_programWindow,
    &ProgramWindow::logTextToConsole);

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(this,

          static_cast<void (BasePlotWnd::*)(
            QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
            &BasePlotWnd::integrateToTicIntensitySignal),

          mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
            &MassDataIntegratorTask::integrateToTicIntensity),

          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // Since we allocated the QThread dynamically we need to be able to
  // destroy it later, so make the connection.

  connect(mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),

          this,

          [this,
           thread_p,
           mass_data_integrator_p,
           parent_plottable_p,
           mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the
            // MassDataIntegratorTask allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
            this->finishedIntegratingToTicIntensity(mass_data_integrator_p,
                                                    parent_plottable_p);
          });


  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->mp_taskMonitorWnd->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Integrating to TIC intensity from the table view");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the MsRunReadTask instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),

          task_monitor_composite_widget_p,

          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::cancelOperation);

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mass_data_integrator_p);

  // task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
  //   mass_data_integrator_p);

  emit integrateToTicIntensitySignal(mass_data_integrator_p);

  // Because the emitter is the same ProgramWindow object, we need to
  // disconnect the signal so that at next opening of mass data file, it will
  // not be called twice.
  disconnect(this,

             static_cast<void (BasePlotWnd::*)(
               QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
               &BasePlotWnd::integrateToTicIntensitySignal),

             mass_data_integrator_task_p,

             static_cast<void (MassDataIntegratorTask::*)(
               QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
               &MassDataIntegratorTask::integrateToTicIntensity));
}

void
BasePlotWnd::finishedIntegratingToTicIntensity(
  QualifiedMassSpectrumVectorMassDataIntegrator *mass_data_integrator_p,
  QCPAbstractPlottable *parent_plottable_p)
{
  // This function is actually a slot that is called when the integration
  // is terminated in a QThread.

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // We get back the integrator that was made to work in another thread. Now
  // get the obtained data and work with them.

  // Store the MsRunDataSetSPtr for later use before deleting the integrattor.

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    mass_data_integrator_p->getProcessingFlow()->getMsRunDataSetCstSPtr();

  double tic_intensity =
    static_cast<QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *>(
      mass_data_integrator_p)
      ->getTicIntensity();

  // We can delete the integrator now that the calculation is finished and
  // that we have made a copy of the ProcessingFlow.
  delete mass_data_integrator_p;
  mass_data_integrator_p = nullptr;

  // At this point, we need to get a color for the plot. That color needs to
  // be that of the parent.

  QColor plot_color;

  // There are two situations: either the integration started from a plot
  // widget and the color is certainly obtainable via the pen of the plot.
  if(parent_plottable_p != nullptr)
    plot_color = parent_plottable_p->pen().color();
  else
    // Or the integration started at the MsRunDataSetTableView and the color
    // can only be obtained via the MsRunDataSet in the OpenMsRunDataSetsDlg.
    plot_color = mp_programWindow->getColorForMsRunDataSet(ms_run_data_set_csp);

  // Now print the sample name and the tic intensity.

  QString sample_name = ms_run_data_set_csp->getMsRunId()->getSampleName();

  QString msg =
    QString("%1: TIC intensity: %2\n").arg(sample_name).arg(tic_intensity);

  mp_programWindow->logColoredTextToConsole(msg, plot_color);

  // FIXME would be cool to have the right color for the intensity
  // output in the composite plot widget, depending on the plottable
  // for which the intensity was calculated.
  mp_programWindow->getPlotWidget(ms_run_data_set_csp, parent_plottable_p)
    ->newTicIntensityCalculated(tic_intensity, plot_color);
}

//! Set the x-axis range lock between all the plot widgets.
void
BasePlotWnd::lockXRangeToggle(bool checked)
{
  m_isLockedXRange = checked;
  // qDebug() << "Is the lockXRange check button checked?:" << m_isLockedXRange;
  mp_vertRuleSlider->setXposLabelVisible(m_isLockedXRange);
}

//! Set the y-axis range lock between all the plot widgets.
void
BasePlotWnd::lockYRangeToggle(bool checked)
{
  m_isLockedYRange = checked;
  // qDebug() << "Is the lockYRange check button checked?:" << m_isLockedXRange;
}

void
BasePlotWnd::lockYMarkerToggle(bool checked)
{
  m_isLockedYMarker = checked;
  mpa_plotAreaOverlay->setVertRuleVisible(m_isLockedYMarker);
  mp_vertRuleSlider->setVisible(m_isLockedYMarker);
  // Only show the x axis value in the label if we are in locked X range mode.
  if(m_isLockedXRange)
    mp_vertRuleSlider->updatePosLabelXaxisValue();

  if(m_isLockedYMarker)
    {
      // Make sure the overlay widget has the same size as that of the scroll
      // area. This call will handle all the details.
      resizeEvent(nullptr);
    }
}

void
BasePlotWnd::plotWidgetGotFocus(BasePlotCompositeWidget *composite_widget_p)
{
  // qDebug() << "Plot widget:" << composite_widget_p << "got focus.";

  // Get a list of all the composite plot widgets.

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  // qDebug() << "Number of trace plot composite widgets:" <<
  // widget_list.size();

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *iter_composite_widget_p = widget_list.at(iter);

      if(composite_widget_p == iter_composite_widget_p)
        {
          iter_composite_widget_p->redrawBackground(true /*has_focus*/);

          // qDebug() << "Asking focused background redraw for plot widget:"
          //<< iter_composite_widget_p;
        }
      else
        {
          iter_composite_widget_p->redrawBackground(false /*has_focus*/);

          // qDebug() << "Asking unfocused background redraw for plot widget:"
          //<< iter_composite_widget_p;
        }
    }
}

void
BasePlotWnd::plotCompositeWidgetDestructionRequested(
  BasePlotCompositeWidget *composite_widget_p)
{
  // qDebug() << "Starting the destruction process of the composite widget:"
  //<< composite_widget_p << "recursively:" << recursively;

  // We want to remove the graphs that sit in this plot widget. But we only go
  // down their children if recursively is true.

  mp_programWindow->plotCompositeWidgetDestructionRequested(composite_widget_p);

  // qDebug() << "Done deleting the composite widget.";
}

std::vector<BasePlotCompositeWidget *>
BasePlotWnd::allWidgets() const
{
  // qDebug();

  QList<BasePlotCompositeWidget *> widget_q_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  std::vector<BasePlotCompositeWidget *> widget_std_vector;

  for(int iter = 0; iter < widget_q_list.size(); ++iter)
    {
      BasePlotCompositeWidget *widget_p = widget_q_list.at(iter);
      widget_std_vector.push_back(widget_p);
    }

  return widget_std_vector;
}

std::vector<BasePlotCompositeWidget *>
BasePlotWnd::pinnedDownWidgets() const
{
  // qDebug();

  // Iterate in the list of composite widgets currently in this window, and
  // check if they are pinned-down. If so add them to the vector.

  QList<BasePlotCompositeWidget *> widget_q_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  std::vector<BasePlotCompositeWidget *> pinned_down_widgets;

  for(int iter = 0; iter < widget_q_list.size(); ++iter)
    {
      BasePlotCompositeWidget *widget_p = widget_q_list.at(iter);

      if(widget_p->isPinnedDown())
        pinned_down_widgets.push_back(widget_p);
    }

  return pinned_down_widgets;
}

BasePlotCompositeWidget *
BasePlotWnd::plotCompositeWidgetForPlottable(QCPAbstractPlottable *plottable_p)
{
  // We need to find the QCustomPlot first:

  QCustomPlot *q_custom_plot_p = plottable_p->parentPlot();

  // Now iterate in all the children of this window and search for each
  // composite widget, which one has a member matching q_custom_plot_p.

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *composite_widget_p = widget_list.at(iter);

      if(composite_widget_p->mp_plotWidget == q_custom_plot_p)
        return composite_widget_p;
    }

  return nullptr;
}

void
BasePlotWnd::plotRangesChanged(QMouseEvent *event,
                               const pappso::BasePlotContext &context)
{
  Q_UNUSED(event)

  // qDebug() << "With context:" << context.toString();

  // Iterate in the list of composite widgets currently in this window, and
  // ask them to replot using the range if necessary.

  int which_plot_axis = 0;

  if(m_isLockedXRange)
    {
      which_plot_axis |= static_cast<int>(pappso::Enums::Axis::x);
      // qDebug() << "Locked range:" << (int)which_plot_axis;
    }

  if(m_isLockedYRange)
    {
      which_plot_axis |= static_cast<int>(pappso::Enums::Axis::y);
      // qDebug() << "Locked range:" << (int)which_plot_axis;
    }

  if(!which_plot_axis)
    {
      // qDebug() << "Apparently no locked range.";
      return;
    }

  QCPRange x_axis_range(context.m_xRange.lower, context.m_xRange.upper);
  QCPRange y_axis_range(context.m_yRange.lower, context.m_yRange.upper);

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");
  std::vector<BaseTracePlotCompositeWidget *> pinned_down_widgets;

  // qDebug() << "The list of widget has size:" << widget_list.size();

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      // qDebug() << "Iterating in widget iter: " << iter;

      BasePlotCompositeWidget *widget_p = widget_list.at(iter);

      if(QObject::sender() == dynamic_cast<QObject *>(widget_p))
        {
          // We do not want to ask that the emitter replots itself, which
          // would be nonsense.

          // qDebug() << "This one is the sender.";
          continue;
        }

      // Ask a replotting with a record of the new axes ranges into the history.
      widget_p->mp_plotWidget->replotWithAxesRanges(
        x_axis_range,
        y_axis_range,
        static_cast<pappso::Enums::Axis>(which_plot_axis));

      // qDebug() << "x_axis_range:" << x_axis_range.lower << "-"
      //<< x_axis_range.upper << "y_axis_range:" << y_axis_range.lower
      //<< "-" << y_axis_range.upper;

      // We also need to update the context so that when we focus another plot
      // widget, there is no jump in the plot, the plot was moved and has
      // recorded the new ranges in the context and thus "start from there" when
      // the trace is somehow panned or whatever. This fixes an issue reported
      // by Kevin Hooijschuur.
      widget_p->mp_plotWidget->updateContextXandYAxisRanges();
    }

  // qDebug() << "Now returning from the function.";
}

QFile *
BasePlotWnd::getAnalysisFilePtr()
{
  return mp_programWindow->getAnalysisFilePtr();
}

QSharedPointer<DiscoveriesPreferences>
BasePlotWnd::getDiscoveriesPreferences()
{
  return mp_programWindow->getDiscoveriesPreferences();
}

void
BasePlotWnd::recordAnalysisStanza(QString stanza, const QColor &color)
{
  mp_programWindow->recordAnalysisStanza(stanza, color);
}

void
BasePlotWnd::showWindow()
{
  activateWindow();
  raise();
  show();
}

QPoint
BasePlotWnd::firstCompositeWidgetPosition() const
{
  std::vector<BasePlotCompositeWidget *> plot_widget_list = allWidgets();

  if(plot_widget_list.size())
    {
      return plot_widget_list[0]->pos();
    }
  return pos();
}

QSize
BasePlotWnd::firstCompositeWidgetSize() const
{
  std::vector<BasePlotCompositeWidget *> plot_widget_list = allWidgets();

  if(plot_widget_list.size())
    {
      return plot_widget_list[0]->size();
    }

  return size();
}

std::size_t
BasePlotWnd::fillInQualifiedMassSpectraVector(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    &qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  // If the pointer to the vector is nullptr, we need to create the vector.

  if(qualified_mass_spectra_sp == nullptr)
    {
      qualified_mass_spectra_sp =
        std::make_shared<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>();
    }

  // If the vector is not empty, then we return its size without modification.

  if(qualified_mass_spectra_sp->size())
    {
      // qDebug()
      //   << "The vector of qualified spectra is not empty. Returning "
      //      "with no modification.";

      return qualified_mass_spectra_sp->size();
    }

  // Now, we know we can fill in the vector. But try to limit as much as
  // possible the number of spectra to add. We do that by only pushing into
  // the vector the spectra that have rt (and dt, if mobility data) values
  // matching the innermost rt (and dt) ranges.

  std::pair<double, double> range_pair;

  // We choose ANY_RT that matches in the widest possible manner all the
  // previous integrations that started with a rt-involving selection (both
  // mono- and two-dimensional, either from the ms run table view or any
  // trace/color map plot).

  bool integration = processing_flow.innermostRange("ANY_RT", range_pair);

  if(!integration)
    {
      // It is imaginable that the data come not from a previous RT step,
      // that is, that the data are indeed MZ data not coming from at least
      // one integration from TIC chromatogram if the mass spectrum (MZ)
      // comes from the net (source processing type is SOCKET_MZ).

      std::vector<ProcessingStepCstSPtr> steps =
        processing_flow.stepsMatchingSrcTypeX("SOCKET_MZ");

      if(!steps.size())
        qFatal(
          "Programming error: the data have a problematic ProcessingFlow.");
    }

  // qDebug() << "Filling-in mass spectra for RT range: "
  //          << range_pair.first << "-" << range_pair.second;

  std::size_t added_rt_mass_spectra;

  // Perform this lengthy computation in another thread.
  QPromise<int> promise;
  QFuture<int> future = promise.future();

  QScopedPointer<QThread> thread(QThread::create(
    [&](QPromise<int> promise) {
      promise.start(); // notifies QFuture that the computation is started

      added_rt_mass_spectra = ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
                                ->addDataSetQualMassSpectraInsideDtOrRtRange(
                                  range_pair.first,
                                  range_pair.second,
                                  *qualified_mass_spectra_sp,
                                  pappso::Enums::DataKind::rt);


      promise.addResult(added_rt_mass_spectra);
      promise.finish(); // notifies QFuture that the computation is finished
    },
    std::move(promise)));

  thread->start();
  future.waitForFinished(); // blocks until QPromise::finish is called
  added_rt_mass_spectra = future.result(); // returns added_rt_mass_spectra

  // Original version in the main thread
  // added_rt_mass_spectra =
  //   ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
  //     ->addDataSetQualMassSpectraInsideDtRtRange(range_pair.first,
  //                                                range_pair.second,
  //                                                *qualified_mass_spectra_sp,
  //                                                pappso::Enums::DataKind::rt);

  // qDebug() << "Added RT mass spectra:" << added_rt_mass_spectra;

  // Now see if we can remove some mass spectra on the basis of DT values.

  // We choose ANY_DT that matches in the widest possible manner all the
  // previous integrations that started with a dt-involving selection (both
  // mono- and two-dimensional).
  integration = processing_flow.innermostRange("ANY_DT", range_pair);

  // Here, it is not an error not to have any DT integration because not all
  // the mass spectral acquisitions involved ion mobility.
  if(integration)
    {
      // qDebug() << "Detected DT integration, removing mass spectra outside DT
      // "
      //             "range:"
      //          << range_pair.first << "-" << range_pair.second;

      std::size_t removed_mass_spectra;

      // Perform this lengthy computation in another thread.
      QPromise<int> new_promise;
      QFuture<int> new_future = new_promise.future();

      QScopedPointer<QThread> new_thread(QThread::create(
        [&](QPromise<int> new_promise) {
          new_promise
            .start(); // notifies QFuture that the computation is started

          removed_mass_spectra =
            ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
              ->removeDataSetQualMassSpectraOutsideDtOrRtRange(
                range_pair.first,
                range_pair.second,
                *qualified_mass_spectra_sp,
                pappso::Enums::DataKind::dt);


          new_promise.addResult(removed_mass_spectra);
          new_promise
            .finish(); // notifies QFuture that the computation is finished
        },
        std::move(new_promise)));

      new_thread->start();
      new_future.waitForFinished(); // blocks until QPromise::finish is called
      removed_mass_spectra =
        new_future.result(); // returns removed_mass_spectra

      // Original version in the main thread
      // removed_mass_spectra =
      // ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
      //                          ->removeDataSetQualMassSpectraOutsideDtRtRange(
      //                            range_pair.first,
      //                            range_pair.second,
      //                            *qualified_mass_spectra_sp,
      //                            pappso::Enums::DataKind::dt);

      // qDebug() << "Removed DT mass spectra:" << removed_mass_spectra;
    }

  // At this point we need to really check which of the remaining spectra are
  // inside of the selection polygon.

  // qDebug() << "Finally returning" << qualified_mass_spectra_sp->size()
  //          << "spectra.";

  return qualified_mass_spectra_sp->size();
}

void
BasePlotWnd::removeEmptyPlotWidgets()
{
  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  int widget_list_size = widget_list.size();

  if(!widget_list_size)
    return;

  int index = widget_list_size - 1;

  while(index > -1)
    {
      BasePlotCompositeWidget *iter_widget_p = widget_list.at(index);

      int plottable_count = iter_widget_p->getPlotWidget()->plottableCount();

      if(!plottable_count)
        plotCompositeWidgetDestructionRequested(iter_widget_p);

      --index;
    }
}


} // namespace MineXpert

} // namespace MsXpS
