/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 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 <QMessageBox>
#include <QWidgetAction>

/////////////////////// QCustomPlot


/////////////////////// pappsomspp includes
#include <pappsomspp/core/processing/combiners/traceminuscombiner.h>
#include <pappsomspp/core/processing/combiners/tracepluscombiner.h>

#include <pappsomspp/core/processing/filters/savgolfilter.h>
#include <pappsomspp/core/processing/filters/filterlowintensitysignalremoval.h>
#include <pappsomspp/core/processing/filters/filterpass.h>


/////////////////////// libXpertMass includes


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/LabelledNumberLineEditAction.hpp>
#include <MsXpS/libXpertMassGui/LabelledCheckBoxAction.hpp>


/////////////////////// Local includes
#include "BaseTracePlotCompositeWidget.hpp"
#include "BaseTracePlotWnd.hpp"
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>
#include "ProgramWindow.hpp"
#include "Application.hpp"
#include "ui_BasePlotCompositeWidget.h"

namespace MsXpS
{
namespace MineXpert
{


// If one peak has been labelled already at a given x-axis value +-
// label_x_axis_tolerance / 2, shift the new label for that peak to the
// top.
float label_x_axis_tolerance = 0.1;


BaseTracePlotCompositeWidget::BaseTracePlotCompositeWidget(
  QWidget *parent, const QString &x_axis_label, const QString &y_axis_label)
  : BasePlotCompositeWidget(parent, x_axis_label, y_axis_label)
{
  // We need to add some menus / menu items.

  createMainMenu();
  configureOutIntegrationCheckButtons();

  // Handling of the user-defined peak labels
  // Handling of the user-defined peak labels
  createLabellingSessionMenu();

#if 0
// This code needs to be in the derived class that might use the feature.
  // Create the list of peak labels
  LabelListQSPtr peak_labels_qsp  = QSharedPointer<LabelList>::create();
  peak_labels_qsp->plot           = mp_plotWidget;
  peak_labels_qsp->name           = "PeakLabelList";
  peak_labels_qsp->layoutHandling = false;

  m_nameLabelListMap[peak_labels_qsp->name] = peak_labels_qsp;
#endif

  // The trace combined plot widget receives traces from integrations. It is
  // possible to perform various actions with the traces inside a composite plot
  // widget and the selection of the action to perform is done using check
  // buttons or one combobox.

  // The replace trace(s) with new one action.

  m_ui->eraseTraceCreateNewPushButton->setShortcut(QKeySequence("Ctrl+T, E"));
  connect(m_ui->eraseTraceCreateNewPushButton, &QPushButton::clicked, [this]() {
    m_newTraceHandlingMethod = NewTraceHandlingMethod::REPLACE;
  });

  // The overlay action.

  m_ui->keepTraceCreateNewPushButton->setShortcut(QKeySequence("Ctrl+T, O"));
  connect(m_ui->keepTraceCreateNewPushButton, &QPushButton::clicked, [this]() {
    m_newTraceHandlingMethod = NewTraceHandlingMethod::OVERLAY;
  });

  // The combine (plus or minus) action.

  // The input logic, that is based on the set of icons in the left column. Some
  // icons are checkable buttons and one is a combobox. This is the one that is
  // tricky to handle because it is not possible to know if it is checed or not.

  const QIcon plus_combine_icon =
    QIcon(":/images/svg/keep-trace-and-plus-combine-new-one.svg");

  const QIcon minus_combine_icon =
    QIcon(":/images/svg/keep-trace-and-minus-combine-new-one.svg");

  m_ui->combinationsComboBox->insertItem(
    -1, minus_combine_icon, "", static_cast<int>(TraceCombinationSign::MINUS));
  m_ui->combinationsComboBox->insertItem(
    10, plus_combine_icon, "", static_cast<int>(TraceCombinationSign::PLUS));

  connect(m_ui->combinationsComboBox,
          QOverload<int>::of(&QComboBox::activated),
          [=]([[maybe_unused]] int index) {
            bool ok = false;

            int item_data =
              m_ui->combinationsComboBox->currentData().toInt(&ok);

            if(static_cast<TraceCombinationSign>(item_data) ==
               TraceCombinationSign::PLUS)
              {
                // qDebug() << "TraceCombinationSign::PLUS";

                m_newTraceHandlingMethod = NewTraceHandlingMethod::PLUS_COMBINE;
              }
            else if(static_cast<TraceCombinationSign>(item_data) ==
                    TraceCombinationSign::MINUS)
              {
                // qDebug() << "TraceCombinationSign::MINUS";

                m_newTraceHandlingMethod =
                  NewTraceHandlingMethod::MINUS_COMBINE;
              }
            else
              qFatal() << "Programming error.";
          });
}

BaseTracePlotCompositeWidget::~BaseTracePlotCompositeWidget()
{
  // qDebug();
}

void
BaseTracePlotCompositeWidget::createMainMenu()
{
  // qDebug() << "BEGIN extending the main menu:" << mp_mainMenu;

  // Complete the base class menu with the trace specific menu item.

  QIcon the_icon = m_ui->mainMenuPushButton->icon();
  QSize size = m_ui->mainMenuPushButton->iconSize(); // typically what you want
  QPixmap pixmap = the_icon.pixmap(size);
  pixmap.save("/tmp/basetraceploticon.png");

  ProgramWindow *program_window_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  QString action_label;
  libXpertMassGui::ActionId action_id;
  QKeySequence key_sequence;
  // Action to be gottent for action_id.
  QAction *action_p              = nullptr;
  // Store for later if an action by action_id was already registered.
  QAction *pre_existing_action_p = nullptr;


  /////////////// Labelling ///////////////

  QMenu *labelling_menu_p = mp_mainMenu->addMenu("Labelling");

  mp_labelListNamesComboBox     = new QComboBox();
  mp_labelListNamesWidgetAction = new QWidgetAction(labelling_menu_p);
  mp_labelListNamesWidgetAction->setDefaultWidget(mp_labelListNamesComboBox);
  labelling_menu_p->addAction(mp_labelListNamesWidgetAction);

  connect(labelling_menu_p, &QMenu::aboutToShow, this, [this]() {
    QStringList label_names = m_nameLabelListMap.keys();

    mp_labelListNamesComboBox->clear();
    mp_labelListNamesComboBox->addItems(label_names);
    mp_labelListNamesComboBox->setEnabled(!label_names.isEmpty());

    if(!label_names.isEmpty())
      mp_labelListNamesComboBox->setCurrentIndex(0);
  });

  action_label = "Show labels";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Labelling", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+S, L"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Show labels for the selected label list");
  labelling_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        qDebug() << "Show labels for the selected label list - in the lambda.";

        QString current_label_list_name =
          mp_labelListNamesComboBox->currentText();

        TracePlotLabelListQSPtr label_list_qsp =
          m_nameLabelListMap.value(current_label_list_name);

        if(label_list_qsp == nullptr)
          return;

        label_list_qsp->showLabels();
        mp_plotWidget->replot();

        qDebug() << "Shown labels for the selected label list - in the lambda:"
                 << label_list_qsp->getName();
      });
    }

  action_label = "Hide labels";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Labelling", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+H, L"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Hide labels for the selected label list");
  labelling_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        QString current_label_list_name =
          mp_labelListNamesComboBox->currentText();

        TracePlotLabelListQSPtr label_list_qsp =
          m_nameLabelListMap.value(current_label_list_name);

        if(label_list_qsp == nullptr)
          return;

        label_list_qsp->hideLabels();
        mp_plotWidget->replot();

        qDebug() << "Hidden labels for the selected label list - in the lambda:"
                 << label_list_qsp->getName();
      });
    }


  action_label = "Remove labels";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Labelling", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+R, L"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Remove labels for the selected label list");
  labelling_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        QString current_label_list_name =
          mp_labelListNamesComboBox->currentText();

        TracePlotLabelListQSPtr label_list_qsp =
          m_nameLabelListMap.value(current_label_list_name);

        if(label_list_qsp == nullptr)
          return;

        label_list_qsp->eraseLabels();
        mp_plotWidget->replot();

        qDebug() << "Erased labels for the selected label list - in the lambda:"
                 << label_list_qsp->getName();
      });
    }

  labelling_menu_p->addSeparator();

  action_label = "Export label list to clipboard";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Labelling", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+E, C"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Export the selected label list to clipboard");
  labelling_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        QString current_label_list_name =
          mp_labelListNamesComboBox->currentText();

        TracePlotLabelListQSPtr label_list_qsp =
          m_nameLabelListMap.value(current_label_list_name);

        if(label_list_qsp == nullptr)
          return;

        QString text = QString("%1:\n").arg(current_label_list_name);

        for(const TracePlotLabelQSPtr &trace_plot_label_qsp :
            label_list_qsp->getLabelsCstRef())
          {
            text += QString("%1\n").arg(trace_plot_label_qsp->getText());
          }

        mp_parentWnd->getProgramWindow()->logTextToConsole(text);

        QClipboard *clipboard_p = QApplication::clipboard();
        clipboard_p->setText(text, QClipboard::Clipboard);
      });
    }


  labelling_menu_p->addSeparator();

  action_label = "Eliminate label list";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Labelling", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+E, L"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Eliminate the selected label list");
  labelling_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        QString current_label_list_name =
          mp_labelListNamesComboBox->currentText();

        TracePlotLabelListQSPtr label_list_qsp =
          m_nameLabelListMap.value(current_label_list_name);

        if(label_list_qsp == nullptr)
          return;

        // The label list will be deleted along with all the labels in it
        // when the ref count decreases to 0.
        m_nameLabelListMap.remove(current_label_list_name);
      });
    }


  /////////////// Peak list saving to clipboard or file ///////////////

  // Save peak list sub menu to hold check box option and the relevant menu
  // items.
  QMenu *save_peak_list_menu_p = mp_mainMenu->addMenu("Save peak list");

  QWidgetAction *peak_list_save_mode_action_p =
    new libXpertMassGui::LabelledCheckBoxAction(
      "Check to only save visible data range",
      Qt::CheckState::Unchecked,
      "" /*no text here*/,
      dynamic_cast<QObject *>(this));
  save_peak_list_menu_p->addAction(peak_list_save_mode_action_p);
  connect(static_cast<libXpertMassGui::LabelledCheckBoxAction *>(
            peak_list_save_mode_action_p),
          &libXpertMassGui::LabelledCheckBoxAction::checkBoxStateToggledSignal,
          this,
          &BaseTracePlotCompositeWidget::
            savePeakListOnlyForVisibleRangeCheckBoxStateChanged);

  action_label = "Save peak list to clipboard";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Analysis", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+P, C"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Save peak list to clipboard");
  save_peak_list_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        qDebug()
          << "Save peak list to clipboard - in the lambda  with only visible:"
          << this->m_isSavePeakListOnlyForVisibleRange;
        BaseTracePlotCompositeWidget::savePeakListToClipboard(
          this->m_isSavePeakListOnlyForVisibleRange /*only_in_current_range*/);
      });
    }

  action_label = "Save peak list to file";
  action_id.initialize(
    "Plot1D", "CompositePlotWidget", "Analysis", action_label);
  // Store for later if an action by action_id was already registered.
  pre_existing_action_p =
    program_window_p->getActionManager()->getAction(action_id);
  // If an action by action_id preexisted, its pointer would be returned
  // without a new allocation. Thus, if more than one base trace
  // composite plot widgets are created, they would have a connection
  // below multiple times against the very same QAction. This is not possible,
  // which is why we check for a potentially existing QACtion by action_id.
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+P, F"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  action_p->setToolTip("Save peak list to file");
  save_peak_list_menu_p->addAction(action_p);
  // We may call this code a large number of times: each time a new
  // trace plot widget is called, an we do not want that the same QAction *
  // be connected innumerous times, because the corresponding menu action
  // when triggered will be repeated as many times.
  // Only connect if no action by action_id has been allocated before.
  if(pre_existing_action_p != nullptr)
    {
      connect(action_p, &QAction::triggered, this, [this]() {
        qDebug() << "Save peak list to file - in the lambda  with only visible:"
                 << this->m_isSavePeakListOnlyForVisibleRange;
        BaseTracePlotCompositeWidget::savePeakListToFile(
          this->m_isSavePeakListOnlyForVisibleRange /*only_in_current_range*/);
      });
    }

  mp_mainMenu->addSeparator();

  /////////////// Integrations ///////////////

  QMenu *integrations_menu_p = mp_mainMenu->addMenu("&Integrations");

  QAction *single_point_integration_mode_action_p = new QAction(
    "Perform single graph &point integrations", dynamic_cast<QObject *>(this));
  single_point_integration_mode_action_p->setStatusTip(
    "Perform single graph point integrations");
  single_point_integration_mode_action_p->setCheckable(true);
  single_point_integration_mode_action_p->setShortcut(
    QKeySequence("Ctrl+P, I"));

  connect(single_point_integration_mode_action_p,
          &QAction::triggered,
          this,
          [this, single_point_integration_mode_action_p]() {
            m_isSinglePointIntegrationMode =
              single_point_integration_mode_action_p->isChecked();
          });

  integrations_menu_p->addAction(single_point_integration_mode_action_p);

  mp_mainMenu->addSeparator();

  /////////////// Filters ///////////////

  QMenu *filters_menu_p = mp_mainMenu->addMenu("&Filters");

  // We want to provide the user with the ability to define a noise
  // sample. They would create a new graph by integrating to that new
  // graph with combination the various regions they feel are noise.
  // They would get a noise sample trace. Then they'd call the menu
  // below to store that trace for further use.

  QMenu *noise_handling_menu_p = filters_menu_p->addMenu("Noise handling");

  QAction *store_noise_sample_trace_action_p = new QAction(
    "Store trace as noise sample trace", dynamic_cast<QObject *>(this));
  store_noise_sample_trace_action_p->setStatusTip(
    "Set the (selected) trace as a noise sample trace");
  connect(store_noise_sample_trace_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::setNoiseSampleTrace);
  noise_handling_menu_p->addAction(store_noise_sample_trace_action_p);

  QAction *reset_noise_sample_trace_action_p =
    new QAction("Reset the noise sample trace", dynamic_cast<QObject *>(this));
  reset_noise_sample_trace_action_p->setStatusTip(
    "Reset the noise sample trace");
  connect(reset_noise_sample_trace_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::resetNoiseSampleTrace);
  noise_handling_menu_p->addAction(reset_noise_sample_trace_action_p);

  filters_menu_p->addSeparator();

  // Savitzky-Golay filtering: two submenus: one to parameterize and the
  // other to actually run the filter.

  QMenu *savgol_filter_menu_p = filters_menu_p->addMenu("&Savitzky-Golay");

  QAction *savitzky_golay_config_filter_action_p = new QAction(
    "Configure Savitzky-Golay smoothing filter", dynamic_cast<QObject *>(this));
  savitzky_golay_config_filter_action_p->setStatusTip(
    "Configure Savitzky-Golay smoothing filter");
  savitzky_golay_config_filter_action_p->setShortcut(QKeySequence("Ctrl+S, C"));

  connect(
    savitzky_golay_config_filter_action_p, &QAction::triggered, this, [this]() {
      showSavitzkyGolayFilterParamsDlg();
    });

  savgol_filter_menu_p->addAction(savitzky_golay_config_filter_action_p);

  QAction *savitzky_golay_run_filter_action_p = new QAction(
    "Run Savitzky-Golay smoothing filter", dynamic_cast<QObject *>(this));
  savitzky_golay_run_filter_action_p->setStatusTip(
    "Run Savitzky-Golay smoothing filter");
  savitzky_golay_run_filter_action_p->setShortcut(QKeySequence("Ctrl+S, R"));

  connect(
    savitzky_golay_run_filter_action_p, &QAction::triggered, this, [this]() {
      runSavitzkyGolayFilter();
    });

  savgol_filter_menu_p->addAction(savitzky_golay_run_filter_action_p);

  // Now add a new menu for the Low intensity signal removel filter.

  QWidgetAction *widget_action_p =
    new libXpertMassGui::LabelledNumberLineEditAction(
      "Low intensity threshold removal",
      10000,
      0,
      10e100,
      2,
      true,
      dynamic_cast<QObject *>(this));
  widget_action_p->setStatusTip("Remove signal below the provided threshold");
  connect(
    widget_action_p, &QAction::triggered, this, [this, widget_action_p]() {
      runLowIntensityThresholdRemovalFilter(widget_action_p);
    });
  filters_menu_p->addAction(widget_action_p);


  QAction *filter_high_pass_0_action_p = new QAction(
    "Remove less than 0 intensity data points", dynamic_cast<QObject *>(this));
  filter_high_pass_0_action_p->setStatusTip(
    "Remove less than 0 intensity data points");
  connect(filter_high_pass_0_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::removeZeroIntensityDataPointsFilter);
  filters_menu_p->addAction(filter_high_pass_0_action_p);

  // qDebug() << "END extending the main menu:" << mp_mainMenu;
}

void
BaseTracePlotCompositeWidget::configureOutIntegrationCheckButtons()
{
  // The right hand side strip of buttons, which act as check buttons
  // configure the destination of the integrations.

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

  action_label = "Duplicate trace";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+D, T"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->duplicateTracePushButton->addAction(action_p);
  m_ui->duplicateTracePushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    // This one is a true button, not a button that acts like a check button!
    m_ui->duplicateTracePushButton->click();
  });

  action_label = "Integrate to an ion mobility spectrum";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, D"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToDtPushButton->addAction(action_p);
  m_ui->integrateToDtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    // m_ui->integrateToDtPushButton->click();
    m_ui->integrateToDtPushButton->setChecked(true);
  });

  action_label = "Integrate to a mass spectrum";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, M"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToMzPushButton->addAction(action_p);
  m_ui->integrateToMzPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    // m_ui->integrateToMzPushButton->click();
    m_ui->integrateToMzPushButton->setChecked(true);
  });

  action_label = "Integrate to a TIC/XIC chromatogram";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, R"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToRtPushButton->addAction(action_p);
  m_ui->integrateToRtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToRtPushButton->setChecked(true);
  });

  action_label = "Integrate to a TIC intensity value";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, I"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToIntPushButton->addAction(action_p);
  m_ui->integrateToIntPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToIntPushButton->setChecked(true);
  });

  action_label = "Integrate to an ion mobility / mass spectrum color map";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, Ctrl+D"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToDtMzPushButton->addAction(action_p);
  m_ui->integrateToDtMzPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToDtMzPushButton->setChecked(true);
  });

  action_label = "Integrate to an ion mobility / TIC chromatogram color map";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, Ctrl+R"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToDtRtPushButton->addAction(action_p);
  m_ui->integrateToDtRtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToDtRtPushButton->setChecked(true);
  });

  action_label = "Integrate to a mass spectrum / TIC chromatogram color map";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, Ctrl+M"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToMzRtPushButton->addAction(action_p);
  m_ui->integrateToMzRtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    // qDebug() << "Trigger" << static_cast<QAction
    // *>(QObject::sender())->text(); This action is set to may different
    // buttons all over the windows, we do not want that the action be triggered
    // for any other window than this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToMzRtPushButton->setChecked(true);
  });
}

QCPGraph *
BaseTracePlotCompositeWidget::addTrace(const pappso::Trace &trace,
                                       QCPAbstractPlottable *parent_plottable_p,
                                       MsRunDataSetCstSPtr ms_run_data_set_csp,
                                       const QString &sample_name,
                                       const ProcessingFlow &processing_flow,
                                       const QColor &color)
{
  // qDebug();

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // If color is not valid, we need to create one.

  QColor local_color(color);

  // Get color from the available colors, or if none is available, create one
  // randomly without requesting the user to select one from QColorDialog.
  if(!local_color.isValid())
    local_color = libXpertMassGui::ColorSelector::getColor(true);

  // We have to verify if there is a destination policy for this composite plot
  // widget. There are different situations.

  // 1. The erase trace and create new push button is checked: the user want to
  // remove all the selected traces and replace them with the trace passed as
  // parameter.

  // 2. The keep traces and combine push button is checked: the user want to
  // combine all the selected traces individually to the new trace passed as
  // parameters.

  // 3. If no destination policy is defined, simply add the new trace without
  // touching any other trace that might be already plotted there.

  // We'll need this to factorize code.
  bool must_create_new_graph = false;
  QCPGraph *graph_p          = nullptr;
  QList<QCPAbstractPlottable *> dest_graph_list =
    plottablesToBeUsedAsIntegrationDestination();

  pappso::TracePlusCombiner trace_combiner;

  if(m_newTraceHandlingMethod == NewTraceHandlingMethod::OVERLAY)
    {
      // Keep the traces there and simply add a new one.
      must_create_new_graph = true;
    }
  else if(m_newTraceHandlingMethod == NewTraceHandlingMethod::MINUS_COMBINE ||
          m_newTraceHandlingMethod == NewTraceHandlingMethod::PLUS_COMBINE)
    {
      // qDebug() << "The added trace will be combined.";

      // Combination is requested. There are some situations:

      // Now check what is the text:

      // 1. If there is no single trace in the widget, then simply add a new
      // trace.
      //
      // 2. If there is a single trace, then combine with that, selected or not.
      //
      // 3. If there are multiple traces, then only combine with those.

      if(!dest_graph_list.size())
        must_create_new_graph = true;
      else
        {
          // qDebug() << "There is indeed at least one graph that will be
          // handled.";

          // // The integration that is performed here is granted to be in
          // integration to either TIC or DT because for MZ there is an
          // overriden function in the MassSpecTracePlotCompositeWidget class.
          // So go for Trace combinations and not Mass Spectrum combinations.

          pappso::TraceMinusCombiner trace_minus_combiner;
          pappso::TracePlusCombiner trace_plus_combiner;

          for(int iter = 0; iter < dest_graph_list.size(); ++iter)
            {
              QCPGraph *iter_graph_p =
                static_cast<QCPGraph *>(dest_graph_list.at(iter));

              pappso::Trace iter_trace =
                static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                  ->toTrace(iter_graph_p);

              // Initialize the receiving map trace with the trace we are
              // iterating into.
              pappso::MapTrace map_trace(iter_trace);

              // And now combine the trace we got as parameter.

              if(m_newTraceHandlingMethod ==
                 NewTraceHandlingMethod::MINUS_COMBINE)
                trace_minus_combiner.combine(map_trace, trace);
              else if(m_newTraceHandlingMethod ==
                      NewTraceHandlingMethod::PLUS_COMBINE)
                trace_plus_combiner.combine(map_trace, trace);

              // Now replace the graph's data. Note that the data are
              // inherently sorted (true below).

              QVector<double> key_vector;
              QVector<double> value_vector;

              for(auto &value : map_trace.xValues())
                key_vector.push_back(value);

              for(auto &value : map_trace.yValues())
                value_vector.push_back(value);

              iter_graph_p->setData(key_vector, value_vector, true);

              // Note how we do not change neither the sample name text nor its
              // color. He we just increment/decrement a trace. We do not set a
              // new trace.
            }
        }
      // End of
      // else of if(!graph_count), that if there are graphs in the plot widget.
    }
  // End of if(m_ui->keepTraceCombineNewPushButton->isChecked())
  else if(m_newTraceHandlingMethod == NewTraceHandlingMethod::REPLACE)
    {
      // qDebug() << "The traces currently selected will be replaced.";

      // Start by erasing all the traces that are selected.

      for(int iter = 0; iter < dest_graph_list.size(); ++iter)
        {
          QCPGraph *iter_graph_p =
            static_cast<QCPGraph *>(dest_graph_list.at(iter));

          // Destroy the graph , but not recursively (false).
          mp_parentWnd->getProgramWindow()->plottableDestructionRequested(
            this, iter_graph_p, false);
        }

      must_create_new_graph = true;
    }
  else
    {
      // None of the button were checked, we simply create a new graph.
      must_create_new_graph = true;
    }

  if(must_create_new_graph)
    {
      // qDebug() << "We will be creating a new graph.";

      // At this point we know we need to create a new trace.

      graph_p = static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                  ->addTrace(trace, local_color);

      // Each time a new trace is added anywhere, it needs to be documented
      // in the main program window's graph nodes tree! This is what we call
      // the ms run data plot graph filiation documentation.

      mp_parentWnd->getProgramWindow()->documentMsRunDataPlottableFiliation(
        ms_run_data_set_csp, graph_p, parent_plottable_p, this);

      // We need to allocate a new ProcessingFlow instance with the graph
      // as its parent.
      ProcessingFlow *processing_flow_p = processing_flow.clone(graph_p);

      // Map the graph_p to the processing flow that we got to document
      // how the trace was obtained in the first place.
      m_plottableProcessingFlowMap[graph_p] = processing_flow_p;

      // Note how we do  change the sample name text and its color.
      // He we do set a new trace.
      m_ui->sampleNameLabel->setText(sample_name);
      setSampleNameColor(color);
    }

  // qDebug() << "Finished adding trace, asking for the plot widget to replot.";

  mp_plotWidget->replot();

  // The setFocus() call below will trigger the mp_plotWidget to send a signal
  // that will be caught by the parent window that will iterate in all the plot
  // widget and set their background to focused/unfocused color according to
  // their focus status.
  // mp_plotWidget->setFocus();

  // graph_p might be nullptr.
  return graph_p;
}

void
BaseTracePlotCompositeWidget::showSavitzkyGolayFilterParamsDlg()
{
  // The SavitzkyGolay parameters are saved in the ProcessingFlow that belong
  // to any given trace. If the settings have been defined already, we can get
  // them back from the ProcessingFlow via the function filter() below.
  //
  // If the settings were not set already, then we can test if they we used at
  // some previous occasion in the past (by looking into the QSettings).
  //
  // If the settings were not found in the QSettings, then we need to ask the
  // user to actually set them in this specific dialog window.

  // First we get the source plottable that might be of interest to the user.

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to set the m/z integration parameters, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  // qDebug() << "The source plottable:" << source_plottable_p;

  // Set aside SavGolParams that we'll need later.
  QString parameters;
  pappso::SavGolParams sav_gol_params;

  // Now see if we can get the filter from the ProcessingFlow. If it is not
  // there, not a problem, since the idea is precisely that we are setting one
  // up.

  pappso::FilterNameInterfaceCstSPtr filter_csp =
    filter("Savitzky-Golay", source_plottable_p);

  bool parameters_set = false;

  if(filter_csp != nullptr)
    {
      // qDebug() << "Could extract the parameters from the ProcessingFlow:"
      //          << filter_csp->toString();

      // Make sure we actually have a pappso::FilterSavitzkyGolay filter.

      const pappso::FilterSavitzkyGolay *filter_p =
        static_cast<const pappso::FilterSavitzkyGolay *>(filter_csp.get());

      if(filter_p != nullptr)
        {
          sav_gol_params = filter_p->getParameters();
          parameters_set = true;

          // qDebug() << "Got parameters from found filter:"
          //          << sav_gol_params.toString();
        }
    }
  else
    {
      // qDebug() << "Could not extract the parameters from the
      // ProcessingFlow.";
    }

  if(!parameters_set)
    {
      // We still have a chance to get parameters by looking into the settings.
      loadFilterParamsFromSettings("Savitzky-Golay", parameters);

      // qDebug() << "The parameters loaded from the settings:" << parameters;

      if(!parameters.isEmpty())
        {
          sav_gol_params.initialize(parameters);
          parameters_set = true;
        }
    }

  // Whatever happens, the SavGolParams will have values because the constructor
  // of this class provides default values
  // (see struct PMSPP_LIB_DECL pappso::SavGolParams)

  if(mp_savitzkyGolayFilterParamsDlg == nullptr)
    {
      // qDebug();

      mp_savitzkyGolayFilterParamsDlg = new SavitzkyGolayFilterParamsDlg(
        this, sav_gol_params, source_plottable_p->pen().color());

      connect(mp_savitzkyGolayFilterParamsDlg,
              &SavitzkyGolayFilterParamsDlg::
                savitzkyGolayFilterParamsDlgShouldBeDestroyedSignal,
              [this]() {
                // qDebug();
                delete QObject::sender();
                mp_savitzkyGolayFilterParamsDlg = nullptr;
              });

      connect(
        mp_savitzkyGolayFilterParamsDlg,
        &SavitzkyGolayFilterParamsDlg::savitzkyGolayFilterParamsChangedSignal,
        this,
        &BaseTracePlotCompositeWidget::savitzkyGolayFilterParamsChanged);
    }

  mp_savitzkyGolayFilterParamsDlg->show();
}

void
BaseTracePlotCompositeWidget::loadFilterParamsFromSettings(
  const QString &filter_name, QString &filter_parameters)
{
  // qDebug();

  // Try to get the params from the settings, if there are any.

  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup("FilterParameters");

  filter_parameters = settings.value(filter_name).toString();

  settings.endGroup();
}

void
BaseTracePlotCompositeWidget::writeFilterParamsToSettings(
  const QString &filter_name, const QString &filter_parameters)
{
  // qDebug();

  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup("FilterParameters");

  settings.setValue(filter_name, filter_parameters);

  settings.endGroup();

  // #if 0

  QString parameters;
  loadFilterParamsFromSettings(filter_name, parameters);

  // qDebug() << "The parameters" << filter_name << "just written:" <<
  // parameters;

  // #endif
}

void
BaseTracePlotCompositeWidget::savitzkyGolayFilterParamsChanged(
  pappso::SavGolParams sav_gol_params)
{
  // qDebug();

  // First we get the source plottable that might be of interest to the user.

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  // qDebug() << "The source plottable:" << source_plottable_p;

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to set the Savitzky-Golay filter parameters, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  // Store the new params to the Settings. These params will be read
  // when the filter is to be applied.
  writeFilterParamsToSettings("Savitzky-Golay", sav_gol_params.toString());
}

void
BaseTracePlotCompositeWidget::runFilter(const QString &filter_name)
{
  qDebug();

  // We want to run a filter by its name. The filter is going to be applied to
  // the trace that is either unique in the widget or selected (if there are
  // more than one). There are different situations:
  //
  // 1. The settings have parameters for a filter by that name. Then, create a
  // new filter and set it to the ProcessingFlow of the generated filtered trace
  // if the application of the filter was successful.
  //
  // 2. There were no params for a filter by that name. No way, we need to let
  // the user enter the parameters for the filter.

  // First get the plottable for which to apply the filter.

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  // qDebug() << "The source plottable:" << source_plottable_p;

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to perform a filtering step, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  QString filter_parameters;
  loadFilterParamsFromSettings(filter_name, filter_parameters);

  if(filter_parameters.isEmpty())
    {
      // No, there were no filter parameters in the settings. We
      // cannot go on. If the filter is a SavitzkyGolay filter, then, we can
      // immeditaly open a dialog window for the user to enter the
      // parameters.

      if(filter_name == "Savitzky-Golay")
        {
          showSavitzkyGolayFilterParamsDlg();
          return;
        }

      return;
    }

  qDebug() << "Could load parameters:" << filter_parameters;

  // We could find the parameters in the settings. Just use them
  // to craft a filter.
  pappso::FilterNameInterfaceCstSPtr filter_csp = nullptr;

  if(filter_name == "Savitzky-Golay")
    {
      filter_csp = std::make_shared<const pappso::FilterSavitzkyGolay>(
        QString("%1|%2").arg(filter_name, filter_parameters));

      if(filter_csp == nullptr)
        {
          // qDebug() << "Could not create filter using these parameters.";

          showSavitzkyGolayFilterParamsDlg();
          return;
        }
    }
  else if(filter_name == "FilterLowIntensitySignalRemoval")
    {
      filter_csp =
        std::make_shared<const pappso::FilterLowIntensitySignalRemoval>(
          QString("%1|%2").arg(filter_name, filter_parameters));

      if(filter_csp == nullptr)
        {
          qDebug() << "Could not create filter using these parameters:"
                   << filter_parameters;

          return;
        }
    }

  qDebug() << "Running filter by name:" << filter_name
           << "with string representation : " << filter_csp->toString();

  pappso::Trace data_trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(static_cast<QCPGraph *>(source_plottable_p));

  pappso::Trace filtered_trace = filter_csp->filter(data_trace);

  // At this point, add the trace ! But only if it has points in it.

  if(!filtered_trace.size())
    {
      // qDebug() << "The trace produced by the filter is empty.";

      m_ui->statusLineEdit->setText(
        QString("The filter %1 produced an empty trace. Plotting nothing.")
          .arg(filter_name));

      return;
    }

  // Make a copy of the ProcessingFlow, without parentship, because we'll
  // pass that instance by reference to downstream code that will allocate
  // an instance with proper parentship. Just delete the temporary pointer
  // after that call.
  ProcessingFlow *temp_processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p)->clone(nullptr);

  temp_processing_flow_p->addFilter(filter_csp);

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(source_plottable_p);

  addTrace(filtered_trace,
           source_plottable_p,
           ms_run_data_set_csp,
           ms_run_data_set_csp->getMsRunId()->getSampleName(),
           *temp_processing_flow_p,
           source_plottable_p->pen().color());

  delete temp_processing_flow_p;
}

void
BaseTracePlotCompositeWidget::runSavitzkyGolayFilter()
{
  // qDebug() << "Asked that the Savitzky-Golay filter be run.";

  runFilter("Savitzky-Golay");
}

void
BaseTracePlotCompositeWidget::runLowIntensityThresholdRemovalFilter(
  QWidgetAction *widget_action_p)
{
  qDebug();

  bool ok = false;

  double threshold =
    static_cast<libXpertMassGui::LabelledNumberLineEditAction *>(
      widget_action_p)
      ->getValue()
      .toDouble(&ok);

  if(!ok)
    qFatal() << "Failed to convert QVariant to double";

  qDebug() << "Triggered with threshold value:" << threshold;

  // Get the source plottable that might be of interest to the user.
  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  // qDebug() << "The source plottable:" << source_plottable_p;

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(this,
                               "Please select *one* trace",
                               "In order to run the low intensity threshold "
                               "removal filter, the source "
                               "graph needs to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return;
    }

  std::size_t point_count;
  double mean    = 0;
  double std_dev = 0;

  // If there is no noise sample trace, then provide 0-valued variables. The
  // user will see nothing.
  bool res = static_cast<BaseTracePlotWnd *>(mp_parentWnd)
               ->getNoiseSampleStats(point_count, mean, std_dev);

  if(!res)
    {
      mean    = 0;
      std_dev = 0;
    }

  // Create the new filter.

  qDebug() << "Now allocating FilterLowIntensitySignalRemoval";

  pappso::FilterLowIntensitySignalRemovalCstSPtr new_filter_csp =
    std::make_shared<pappso::FilterLowIntensitySignalRemoval>(
      pappso::FilterLowIntensitySignalRemoval(mean, std_dev, threshold));

  qDebug() << "Allocated:" << new_filter_csp->toString();

  // Get the processing flow for that specific graph.
  ProcessingFlow *processing_flow_p =
    getProcessingFlowPtrForPlottable(source_plottable_p);

  // Add the filter to the processing flow for the proper plottable.
  processing_flow_p->addFilter(new_filter_csp);

  // Store the new params to the Settings. These params will be read
  // when the filter is to be applied.
  QString parameters = new_filter_csp->toString().split('|').last();

  writeFilterParamsToSettings("FilterLowIntensitySignalRemoval", parameters);

  // Finally, since the action was triggered, run the filter.

  runFilter(new_filter_csp->name());
}

void
BaseTracePlotCompositeWidget::removeZeroIntensityDataPointsFilter()
{
  qDebug();
  // We want to remove all the points that are 0-intensity, that is,
  // that have y=0.

  // Get the source plottable that might be of interest to the user.
  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  // qDebug() << "The source plottable:" << source_plottable_p;

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(this,
                               "Please select *one* trace",
                               "In order to run the low intensity threshold "
                               "removal filter, the source "
                               "graph needs to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return;
    }

  pappso::Trace data_trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(static_cast<QCPGraph *>(source_plottable_p));

  pappso::FilterHighPass filter_high_pass(0);

  data_trace = filter_high_pass.filter(data_trace);

  // At this point, add the trace ! But only if it has points in it.

  if(!data_trace.size())
    {
      // qDebug() << "The trace produced by the filter is empty.";

      m_ui->statusLineEdit->setText(
        QString("The filter %1 produced an empty trace. Plotting nothing.")
          .arg("High pass (0)"));

      return;
    }

  // Make a copy of the ProcessingFlow, without parentship, because we'll
  // pass that instance by reference to downstream code that will allocate
  // an instance with proper parentship. Just delete the temporary pointer
  // after that call.
  ProcessingFlow *temp_processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p)->clone(nullptr);

  // temp_processing_flow_p->addFilter(filter_csp);

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(source_plottable_p);

  addTrace(data_trace,
           source_plottable_p,
           ms_run_data_set_csp,
           ms_run_data_set_csp->getMsRunId()->getSampleName(),
           *temp_processing_flow_p,
           source_plottable_p->pen().color());

  delete temp_processing_flow_p;
}

void
BaseTracePlotCompositeWidget::plotWidgetKeyPressEvent(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "ENTER with base plot context:" << context.toString();

  // Call the base class that will fill-in various generic data, like the
  // file/sample name, the x and y ranges...

  BasePlotCompositeWidget::plotWidgetKeyPressEvent(context);

  m_plotWidgetPressedKeyCode = context.m_pressedKeyCode;

  // qDebug() << "The context" << context.toString();

  if(context.m_pressedKeyCode == Qt::Key_Space)
    {
      // At the moment, in this class we have nothing very specific to do. But
      // we could reparse the m_analysisStanza string and fill-in any
      // remaining
      // %<char> that might have been remained unfilled by the base class
      // function call.
    }

  if(context.m_pressedKeyCode == Qt::Key_Delete)
    {
      qDebug() << "Qt::Key_Delete";
    }

  // We do not call the stanza recording function because that will be
  // performed by the derived classes when all the chances of having filled-in
  // the fields have been met.
}

void
BaseTracePlotCompositeWidget::plotWidgetKeyReleaseEvent(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "m_plotWidgetReleasedKeyCode:" << m_plotWidgetReleasedKeyCode;

  BasePlotCompositeWidget::plotWidgetKeyReleaseEvent(context);

  // The F3 key triggers the displacement of the mouse cursor to the next
  // graph point. If integrations are requested, then an integration for that
  // single point is performed (see m_isSinglePointIntegrationMode).

  if(m_plotWidgetReleasedKeyCode == Qt::Key_F3)
    {
      // qDebug() << "F3 key was released.";
      moveMouseCursorToNextGraphPoint(context);
    }

  if(m_plotWidgetPressedKeyCode == Qt::Key_Escape)
    {
      // The user wants to nullify any ongoing integration using the right
      // mouse button.

      // The simplest way is to simulate a left click mouse button.

      if(mp_plotWidget)
        {
          QPointF mouseCursorPos(
            mp_plotWidget->xAxis->coordToPixel(context.m_currentDragPoint.x()),
            mp_plotWidget->xAxis->coordToPixel(context.m_currentDragPoint.y()));

          QMouseEvent *mouse_event_p = new QMouseEvent(QEvent::MouseButtonPress,
                                                       mouseCursorPos,
                                                       mouseCursorPos,
                                                       Qt::LeftButton,
                                                       Qt::LeftButton,
                                                       Qt::NoModifier);

          QCoreApplication::sendEvent(QWidget::focusWidget(), mouse_event_p);

          mouse_event_p = new QMouseEvent(QEvent::MouseButtonRelease,
                                          mouseCursorPos,
                                          mouseCursorPos,
                                          Qt::LeftButton,
                                          Qt::LeftButton,
                                          Qt::NoModifier);

          QCoreApplication::sendEvent(QWidget::focusWidget(), mouse_event_p);
        }

      // qDebug() << "Escape key released.";
    }
}

void
BaseTracePlotCompositeWidget::plotWidgetMousePressEvent(
  [[maybe_unused]] const pappso::BasePlotContext &context)
{
  // qDebug() << "The BasePlotContext:" << context.toString();
  BasePlotCompositeWidget::plotWidgetMousePressEvent(context);
}

void
BaseTracePlotCompositeWidget::plotWidgetMouseReleaseEvent(
  [[maybe_unused]] const pappso::BasePlotContext &context)
{
  BasePlotCompositeWidget::plotWidgetMouseReleaseEvent(context);

  // If the user clicks on a trace plot widget while maintaining the 'X' or 'Y'
  // keys pressed, and if m_isPeakApexPinPointing is true, then the user is
  // capturing the x axis (or y axis) value in the console.

  if(m_isPinPointingPeakApex)
    {
      // qDebug() << "Peak apex pin pointing.";

      double x_value = context.m_lastCursorHoveredPoint.x();
      double y_value = context.m_lastCursorHoveredPoint.y();

      QString text;

      if(context.m_pressedKeyCode == Qt::Key_X)
        {
          text = QString("%1").arg(x_value, 0, 'f', 4);
        }
      else if(context.m_pressedKeyCode == Qt::Key_Y)
        {
          text = QString("%1").arg(y_value, 0, 'f', 4);
        }
      else if(context.m_pressedKeyCode == Qt::Key_C)
        {
          // (X,Y)
          text =
            QString("(%1,%2)").arg(x_value, 0, 'f', 4).arg(y_value, 0, 'f', 4);
        }

      if(!text.isEmpty())
        {
          mp_parentWnd->getProgramWindow()->logTextToConsole(text);

          TracePlotLabelListQSPtr trace_plot_label_list_qsp =
            m_nameLabelListMap.value("PeakLabelList");

          if(trace_plot_label_list_qsp == nullptr)
            qFatal()
              << "Programming error. The peak list title \"PeakLabelList\" "
                 "does not exist.";

          trace_plot_label_list_qsp->newLabel(
            text, QPointF(x_value, y_value), true /*should_replot*/);
        }
    }
}

void
BaseTracePlotCompositeWidget::moveMouseCursorToNextGraphPoint(
  const pappso::BasePlotContext &context)
{
  Q_UNUSED(context);

  // We want to reach the next valid graph point and, if integration is
  // requested, perform an integration around that single point, without
  // including any other point.

  // The idea is that when we find the next data point, we try to find a range
  // that encloses it. If x is the found *next* data point, w is prev and y is
  // next, then this is the situation.
  // ------- w ----a--- x ----b---y ---------
  //
  // Ideally we want an integration range like [ a - b ], that is half w-x to
  // half x-y.

  // qDebug();

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());
  if(source_graph_p == nullptr)
    return;

  QSharedPointer<QCPGraphDataContainer> graph_data_container_sp =
    source_graph_p->data();

  if(!graph_data_container_sp->size())
    {
      // qDebug() << "Graph is empty.Returning.";
      return;
    }

  // This is the secret of the whole process. In order to be able to use the
  // findEnd() function from QCustomPlot, we need to virtually displace the
  // current cursor point one pixel to the right. In this manner findEnd()
  // will not return the same iterator position as for the previous function
  // call.
  //
  // From the docs:
  // findEnd() returns an iterator to the element after the data point with a
  // (sort-)key that is equal to, just above or just below sortKey. If
  // expandedRange is true, the data point just above sortKey will be
  // considered, otherwise the one just below.

  QPointF incremented_cursor_point =
    mp_plotWidget->horizontalGetGraphCoordNewPointCountPixels(1);

  // qDebug() << "incremented_incremented_cursor_point:"
  //<< incremented_cursor_point;

  QCPGraphDataContainer::const_iterator next_data_point_iterator =
    graph_data_container_sp->findEnd(incremented_cursor_point.x(),
                                     // allow for upper position
                                     false);

  QCPGraphDataContainer::const_iterator find_end_data_iterator =
    graph_data_container_sp->findEnd(incremented_cursor_point.x(),
                                     // allow for upper position
                                     false);

  // We need to check that it does not point to end().

  if(find_end_data_iterator == graph_data_container_sp->end())
    {
      // qDebug() << "find_end_data_iterator is equal to end(), setting "
      //"next_data_point_iterator to begin().";

      next_data_point_iterator = graph_data_container_sp->begin();
    }
  else
    {
      // qDebug() << "find_end_data_iterator:" << find_end_data_iterator->key
      //<< "-" << find_end_data_iterator->value;
    }

  // qDebug() << "Now next_data_point_iterator:" <<
  // next_data_point_iterator->key
  //<< "-" << next_data_point_iterator->value;

  // Finally, we know where we want to land.

  mp_plotWidget->moveMouseCursorGraphCoordToGlobal(
    QPointF(next_data_point_iterator->key, next_data_point_iterator->value));

  // qDebug() << "Moved cursor to next point.";

  if(m_isSinglePointIntegrationMode)
    {
      // qDebug();

      // Go on with the range integration determination and integration.
      // Now compute the range of interest

      // ------- w ----a--- x ----b---y ---------

      //
      // To recap:
      //
      // next_data_point_iterator points to x


      if(next_data_point_iterator == graph_data_container_sp->begin())
        {
          // qDebug() << "next_data_point_iterator is begin(). Subtracting 10
          // to " "the begin() key.";

          m_integrationRange.lower = graph_data_container_sp->begin()->key - 10;
        }
      else
        {
          // Get the previous point.

          QPointF previous_point(std::prev(next_data_point_iterator)->key,
                                 std::prev(next_data_point_iterator)->value);

          // qDebug() << "Previous point:" << previous_point;

          // Compute the midvalue a (see diagram above).

          m_integrationRange.lower =
            next_data_point_iterator->key -
            (next_data_point_iterator->key - previous_point.x()) / 2;
        }

      // At this point, we have the lower member of the m_integrationRange.

      // Now compute the b value.

      if(next_data_point_iterator == std::prev(graph_data_container_sp->end()))
        {
          // We are at the last graph point, simply go to the right of it.

          m_integrationRange.upper = next_data_point_iterator->key + 10;
        }
      else
        {
          // Get the next point.

          QPointF next_point(std::next(next_data_point_iterator)->key,
                             std::next(next_data_point_iterator)->value);

          // Compute the midvalue b (see diagram above).

          m_integrationRange.upper =
            (next_data_point_iterator->key +
             (next_point.x() - next_data_point_iterator->key) / 2);
        }

      // At this point, we should have reliable m_integrationRange.

      // qDebug() << "Integration range:" << m_integrationRange.lower << "-"
      //<< m_integrationRange.upper;

      // qDebug() << "The context from the plot widget, now that we have moved
      // "
      //"the cursor:"
      //<< mp_plotWidget->getContext().toString();
    }
}

void
BaseTracePlotCompositeWidget::setNoiseSampleTrace()
{

  // First we get the source plottable that might be of interest to the user.

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to set the noise sample trace, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  pappso::Trace trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(source_graph_p);

  static_cast<BaseTracePlotWnd *>(mp_parentWnd)->setNoiseSampleTrace(trace);
}

void
BaseTracePlotCompositeWidget::resetNoiseSampleTrace()
{

  // First we get the source plottable that might be of interest to the user.

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to reset the noise sample trace, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  static_cast<BaseTracePlotWnd *>(mp_parentWnd)->resetNoiseSampleTrace();
}

void
BaseTracePlotCompositeWidget::pinPointPeakApexCheckBoxStateChanged(
  Qt::CheckState state)
{
  // When entering the pin point peak apex data analysis mode,
  // we do not want to select traces.
  // By default, pappso::BasePlotWidget is set with:
  // setInteractions(QCP::iRangeZoom | QCP::iSelectPlottables |
  // QCP::iMultiSelect);

  if(state == Qt::Checked)
    {
      // qDebug() << "The pin point peak apex button is checked.";
      m_isPinPointingPeakApex = true;
      mp_plotWidget->setInteractions(QCP::iRangeZoom);
    }
  else if(state == Qt::Unchecked || state == Qt::PartiallyChecked)
    {
      // qDebug() << "The pin point peak apex button is not checked.";
      m_isPinPointingPeakApex = false;
      mp_plotWidget->setInteractions(QCP::iRangeZoom | QCP::iSelectPlottables |
                                     QCP::iMultiSelect);
    }
}

void
BaseTracePlotCompositeWidget::
  savePeakListOnlyForVisibleRangeCheckBoxStateChanged(Qt::CheckState state)
{
  // qDebug() << "with check state:" << state;

  if(state == Qt::Checked)
    {
      m_isSavePeakListOnlyForVisibleRange = true;
    }
  else if(state == Qt::Unchecked || state == Qt::PartiallyChecked)
    {
      m_isSavePeakListOnlyForVisibleRange = false;
    }
}

void
BaseTracePlotCompositeWidget::beforeReplot()
{
  // qDebug() << "";
}

void
BaseTracePlotCompositeWidget::afterLayout()
{
  // qDebug() << "Now relayouting all the analyte labels in the"
  //          << m_nameLabelListMap.size() << "label lists";

  for(TracePlotLabelListQSPtr trace_plot_label_list_qsp : m_nameLabelListMap)
    {
      Q_ASSERT(trace_plot_label_list_qsp != nullptr);

      // Will skip label lists that do not handle layouting.
      // Will replot anyway.
      trace_plot_label_list_qsp->writeToUpdatedDrawPoint(
        false /*should_replot*/);
    }
}

void
BaseTracePlotCompositeWidget::afterReplot()
{
  // qDebug() << "";
}


void
BaseTracePlotCompositeWidget::createLabellingSessionMenu()
{
  // qDebug() << "ENTER";

  if(mp_labelSessionMenu != nullptr)
    return;

  // Create the menu.
  mp_labelSessionMenu = new QMenu(this);
  mp_labelSessionMenu->setToolTip(
    "To label a feature with the x axis value, press the keyboard X key and\n"
    "click the point where the label should be printed.\n"
    "Likewise with the keyboard Y key and the y axis value."
    "To print both the x and y axes values, press the keyboard C key.");

  // Create the menu push button.
  const QIcon menu_icon = QIcon(":/images/svg/label-session-menu-button.svg");
  mp_labelSessionMenu->setIcon(menu_icon);
  mp_labelSessionMenu->setToolTip("Labelling session menu (Ctrl+L, S)");

  // Finally set the push button to be the menu...
  m_ui->labellingSessionMenuPushButton->setMenu(mp_labelSessionMenu);

  QAction *pick_peaks_only_on_visible_maxima_p =
    mp_labelSessionMenu->addAction("Pick peaks only on visible maxima");

  connect(pick_peaks_only_on_visible_maxima_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::pickPeaksOnlyOnVisibleMaxima);

  mp_labelSessionMenu->addSeparator();

  QAction *start_stop_labelling_session_p =
    mp_labelSessionMenu->addAction("Start/Stop labelling session");

  start_stop_labelling_session_p->setCheckable(true);
  start_stop_labelling_session_p->setShortcut(QKeySequence("Ctrl+L, S"));

  connect(
    start_stop_labelling_session_p, &QAction::toggled, [this](bool checked) {
      m_isPinPointingPeakApex = checked;
      // qDebug() << "Labelling session open?" << checked;
    });

  mp_labelSessionMenu->addSeparator();

  QAction *toggle_all_labels_visibility_action_p =
    mp_labelSessionMenu->addAction("Toggle all peak labels visibility");

  toggle_all_labels_visibility_action_p->setShortcut(QKeySequence("Ctrl+T, V"));

  connect(toggle_all_labels_visibility_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::toggleAllPeakLabelsVisibility);

  mp_labelSessionMenu->addSeparator();

  QAction *remove_first_label_action_p =
    mp_labelSessionMenu->addAction("Remove first peak label");

  remove_first_label_action_p->setShortcut(QKeySequence("Ctrl+R, F"));

  connect(remove_first_label_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::removeFirstPeakLabel);

  QAction *remove_last_label_action_p =
    mp_labelSessionMenu->addAction("Remove last peak label");

  remove_last_label_action_p->setShortcut(QKeySequence("Ctrl+R, L"));

  connect(remove_last_label_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::removeLastPeakLabel);

  mp_labelSessionMenu->addSeparator();

  QAction *remove_all_labels_action_p =
    mp_labelSessionMenu->addAction("Remove all peak labels");

  remove_all_labels_action_p->setShortcut(QKeySequence("Ctrl+R, A"));

  connect(remove_all_labels_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::removeAllPeakLabels);

  mp_labelSessionMenu->addSeparator();

  QAction *export_labels_to_clipboard_action_p =
    mp_labelSessionMenu->addAction("Export peak labels to clipboard");

  export_labels_to_clipboard_action_p->setShortcut(QKeySequence("Ctrl+E, L"));

  connect(export_labels_to_clipboard_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::exportPeakLabelsToClipboard);
}

void
BaseTracePlotCompositeWidget::labellingSessionMenuPushButtonClicked()
{
  createLabellingSessionMenu();
  m_ui->mainMenuPushButton->showMenu();
}

void
BaseTracePlotCompositeWidget::labelPeak(const QString &text,
                                        const QPointF &data_point,
                                        bool should_replot)
{
  TracePlotLabelListQSPtr trace_plot_label_list_qsp =
    m_nameLabelListMap.value("PeakLabelList");

  trace_plot_label_list_qsp->newLabel(text, data_point, should_replot);
}


void
BaseTracePlotCompositeWidget::labelPeaks(const pappso::Trace &trace)
{
  QString text;

  for(const pappso::DataPoint &dp : trace)
    {
      text = QString("(%1,%2)").arg(dp.x, 0, 'f', 4).arg(dp.y, 0, 'f', 4);
      labelPeak(text, QPointF(dp.x, dp.y), false /*shouldReplot*/);
    }

  mp_plotWidget->replot();
}

void
BaseTracePlotCompositeWidget::toggleAllPeakLabelsVisibility()
{
  // qDebug();

  TracePlotLabelListQSPtr trace_plot_label_list_qsp =
    m_nameLabelListMap.value("PeakLabelList");

  if(!trace_plot_label_list_qsp->size())
    return;

  trace_plot_label_list_qsp->toggleLabelsVisibility();

  mp_plotWidget->replot();
}

void
BaseTracePlotCompositeWidget::removeFirstPeakLabel()
{
  // qDebug();

  TracePlotLabelListQSPtr trace_plot_label_list_qsp =
    m_nameLabelListMap.value("PeakLabelList");

  if(!trace_plot_label_list_qsp->size())
    return;

  trace_plot_label_list_qsp->eraseLabel(0);

#if 0
  // Old code that worked time back:
  
  PeakLabelQSPtr peak_label_qsp = label_list_sqp->labels.first();

  bool res = mp_plotWidget->removeItem(peak_label_qsp->rp_qcpItemText);
  if(!res)
    qDebug() << "The item could not be removed.";
  else
    mp_plotWidget->replot();

  label_list_sqp->labels.removeFirst();
#endif
}

void
BaseTracePlotCompositeWidget::removeLastPeakLabel()
{
  // qDebug();

  TracePlotLabelListQSPtr trace_plot_label_list_qsp =
    m_nameLabelListMap.value("PeakLabelList");

  if(!trace_plot_label_list_qsp->size())
    return;

  trace_plot_label_list_qsp->eraseLabel(trace_plot_label_list_qsp->size() - 1);

#if 0
  // Old code that worked time back:
  PeakLabelQSPtr peak_label_qsp = label_list_sqp->labels.last();

  bool res = mp_plotWidget->removeItem(peak_label_qsp->rp_qcpItemText);
  if(!res)
    qDebug() << "The item could not be removed.";
  else
    mp_plotWidget->replot();

  label_list_sqp->labels.removeLast();
#endif
}

void
BaseTracePlotCompositeWidget::removeAllPeakLabels()
{
  // qDebug();

  TracePlotLabelListQSPtr trace_plot_label_list_qsp =
    m_nameLabelListMap.value("PeakLabelList");

  if(!trace_plot_label_list_qsp->size())
    return;

  trace_plot_label_list_qsp->eraseLabels();
}

void
BaseTracePlotCompositeWidget::exportPeakLabelsToClipboard()
{
  TracePlotLabelListQSPtr trace_plot_label_list_qsp =
    m_nameLabelListMap.value("PeakLabelList");

  if(!trace_plot_label_list_qsp->size())
    return;

  QString text;

  for(const TracePlotLabelQSPtr &trace_plot_label_qsp :
      trace_plot_label_list_qsp->getLabelsCstRef())
    text += QString("%1\n").arg(trace_plot_label_qsp->getText());

  if(!text.isEmpty())
    {
      QClipboard *clipboard_p = QApplication::clipboard();

      clipboard_p->setText(text, QClipboard::Clipboard);
    }
}

pappso::Trace
BaseTracePlotCompositeWidget::pickPeaksOnlyOnVisibleMaxima()
{
  pappso::Trace trace;
  // First we get the source graph that might be of interest to the user.

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(this,
                               "Please select *one* trace",
                               "In order to perform peak picking, the source "
                               "graph needs to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return trace;
    }

  // Thus user has zoomed in such a manner that only the desired peaks
  // in a trace are visible.

  // Get the y range, so that we know what the bottom clipping end is (the floor
  // of the visible data).

  QCPRange plot_widget_x_axis_range = mp_plotWidget->xAxis->range();
  QCPRange plot_widget_y_axis_range = mp_plotWidget->yAxis->range();

  double y_floor_value = plot_widget_y_axis_range.lower;

  pappso::Trace data_trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(plot_widget_x_axis_range,
                static_cast<QCPGraph *>(source_plottable_p));

  // Now use the Trace filter to get a new trace with the local maxima points
  // only:

  trace =
    flooredLocalMaxima(data_trace.begin(), data_trace.end(), y_floor_value);

  labelPeaks(trace);
  return trace;
}

bool
BaseTracePlotCompositeWidget::savePeakListToClipboard(
  bool only_in_visible_range)
{
  // This slot is activated when a QAction is triggered. That unique
  // QAction might be referenced multiple times in various menus
  // inside various base trace plot composite widgets from various
  // plot windows. We thus need
  // to only act if the trigger is while this widget is actually focussed and
  // the window that contains it is actually active.
  if(!static_cast<QWidget *>(parent())->isActiveWindow() || !isFocussed())
    return false;

  qDebug() << "Only visible range:" << only_in_visible_range;
  qDebug() << "Member only visible range:"
           << m_isSavePeakListOnlyForVisibleRange;

  // Check first how many plottables there are in the widget.

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      // We should inform the user if there are more than one plottable.

      int plottable_count = mp_plotWidget->plottableCount();

      if(plottable_count > 1)
        QMessageBox::information(this,
                                 "Save peak list to clipboard",
                                 "Please select a single trace and try again.");

      return false;
    }

  pappso::Trace trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(source_graph_p, only_in_visible_range);

  QString trace_string = trace.toString();
  // qDebug().noquote() << "The trace is:" << trace_string;

  QClipboard *clipboard_p = QApplication::clipboard();

  clipboard_p->setText(trace_string, QClipboard::Clipboard);

  return true;
}

bool
BaseTracePlotCompositeWidget::savePeakListToFile(bool only_in_visible_range)
{
  // This slot is activated when a QAction is triggered. That unique
  // QAction might be referenced multiple times in various menus
  // inside various base trace plot composite widgets from various
  // plot windows. We thus need
  // to only act if the trigger is while this widget is actually focussed and
  // the window that contains it is actually active.
  if(!static_cast<QWidget *>(parent())->isActiveWindow() || !isFocussed())
    return false;

  qDebug() << "Only visible range:" << only_in_visible_range;

  QString file_name = QFileDialog::getSaveFileName(
    this, tr("Export to text file"), QDir::homePath(), tr("Any file type(*)"));

  if(file_name.isEmpty())
    return false;

  // Check first how many plottables there are in the widget.

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      // We should inform the user if there are more than one plottable.

      int plottable_count = mp_plotWidget->plottableCount();

      if(plottable_count > 1)
        QMessageBox::information(this,
                                 "Save peak list to clipboard",
                                 "Please select a single trace and try again.");

      return false;
    }

  pappso::Trace trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(source_graph_p, only_in_visible_range);

  // qDebug().noquote() << "The trace is:" << trace.toString();

  return pappso::Utils::writeToFile(trace.toString(), file_name);
}


} // namespace MineXpert

} // namespace MsXpS
