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


///////////////////////////// Qt include
#include <QVector>

///////////////////////////// Local includes
#include <minexpert/gui/AbstractPlotWidget.hpp>
#include <minexpert/gui/AbstractMultiPlotWnd.hpp>
#include <minexpert/nongui/MassSpecSqlite3Handler.hpp>
#include <minexpert/nongui/SqlMassDataSlicer.hpp>
#include <minexpert/gui/SqlMassDataSlicerWnd.hpp>
#include <minexpert/nongui/globals.hpp>
#include <minexpert/gui/MainWindow.hpp>
#include <minexpert/gui/SaveToGraphicsFileDlg.hpp>


namespace msXpSmineXpert
{

/*/js/ Class: AbstractPlotWidget
 *
 * <comment>This class is not exported to the JavaScript environment, but it
 * serves as the base class for all the plot widget subclasses. Its methods
 * are thus available to all the objects of the various subclasses, like
 * MassSpecPlotWidget, DriftSpecPlotWidget, ColorMapPlotWidget and
 * TicChromPlotWidget, collectively referred to as <PlotWidget> below.
 *
 * When using objects of the various classes above, the object name will thus
 * be, for example, ticChromPlotWidget0 or driftSpecPlotWidget1. These objects
 * are made available to the scripting environment in the tree view on the
 * left part of the scripting window. See the user manual for
 * details.</comment>
 */


//! Construct a AbstractPlotWidget instance.
/*!

  The instance is initialized using the set of parameters:

  \param parent pointer to the parent widget.

  \param parentWnd pointer to the parent window.

  \param name name of the plot widget.

  \param  desc description of the plot widget.

  \param massSpecDataSet pointer to the MassSpecDataSet instance in which the
  mass data are stored.

  \param fileName name of the file from which the data were loaded.

  \param isMultiGraph tells if \c this plot widget is deemed to receive more
  than one graph.

*/
AbstractPlotWidget::AbstractPlotWidget(QWidget *parent,
                                       const QString &name,
                                       const QString &desc,
                                       const MassSpecDataSet *massSpecDataSet,
                                       const QString &fileName,
                                       bool isMultiGraph)
  : QCustomPlot{parent},
    mp_parentWnd{parent},
    m_name{name},
    m_desc{desc},
    mp_massSpecDataSet{massSpecDataSet},
    m_fileName{fileName},
    m_isMultiGraph{isMultiGraph}
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Creating new plot widget:" << this
  //<< "with parent window:" << parent;

  if(parent == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Not possible that parent pointer is nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(!setupWidget())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  setObjectName("plotWidget");

#if 0
		if(fileName.isEmpty())
			qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
				<< "The file name passed to the plot widget constructor is empty";
		else
		{
			QFileInfo fileInfo(fileName);

			if(!fileInfo.exists())
				qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
					<< "The file name passed as parameter does not exist:" << fileName;
			else
			{
				if(!fileInfo.isAbsolute())
				{
					qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
						<< "The file name passed as parameter is not absolute";
				}
				else
					qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
						<< "The file name passed as parameter:" << fileName;
			}
		}
#endif

  show();
}


//! Destruct \c this AbstractPlotWidget instance.
/*!

  The destruction involves clearing the history, deleting all the axis range
  history items for x and y axes.

*/
AbstractPlotWidget::~AbstractPlotWidget()
{
  clearHistory();

  // Clear all the element of the axis range histories:
  for(int iter = 0; iter < m_xAxisRangeHistory.size(); ++iter)
    delete m_xAxisRangeHistory.at(iter);

  for(int iter = 0; iter < m_yAxisRangeHistory.size(); ++iter)
    delete m_yAxisRangeHistory.at(iter);

  // Inform the parent window that this is no longer be the last focused widget:

  AbstractMultiPlotWnd *parentWnd =
    static_cast<AbstractMultiPlotWnd *>(mp_parentWnd);

  if(parentWnd == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Not possible that parent pointer is nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);


  if(parentWnd->mp_lastFocusedPlotWidget == this)
    parentWnd->mp_lastFocusedPlotWidget = Q_NULLPTR;
}


QWidget *
AbstractPlotWidget::parentWnd()
{
  return mp_parentWnd;
}


void
AbstractPlotWidget::setGraphData(QVector<double> keys, QVector<double> values)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "before setting new graph data m_plotTraces size:"
  //<< m_plotTraces.size()
  //<< "m_currentPlotTraceIndex:" << m_currentPlotTraceIndex;

  // The actual setting of the data to the graph.
  graph()->setData(keys, values);

  // Now handle the multiple data sets stuff.

  std::shared_ptr<msXpSlibmass::Trace> newTrace =
    std::make_shared<msXpSlibmass::Trace>(keys, values);

  // Sanity check
  if(!m_plotTraces.size() && m_currentPlotTraceIndex)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Cannot be that the traces list is empty and the index is not 0."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(!m_plotTraces.size())
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

      // Sanity check

      if(m_currentPlotTraceIndex)
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Cannot be that the traces list is empty and the index is not 0."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);

      m_plotTraces.push_back(newTrace);

      // Do not increment the index, because we want to keep the index an index
      // and not to make it a position.
    }
  else if(!m_currentPlotTraceIndex)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

      // All we need to do is  append the new data.
      auto iterator = m_plotTraces.begin();
      m_plotTraces.insert(iterator + 1, newTrace);
      ++m_currentPlotTraceIndex;
    }
  else if(m_currentPlotTraceIndex == m_plotTraces.size() - 1)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

      // All we need to do is  append the new data.
      m_plotTraces.push_back(newTrace);
      ++m_currentPlotTraceIndex;
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "m_currentPlotTraceIndex is" << m_currentPlotTraceIndex;

      // The is/are other traces, we want to put the new one to the *right* of
      // the currently active trace.

      auto iterator = m_plotTraces.begin();
      m_plotTraces.insert(iterator + m_currentPlotTraceIndex + 1, newTrace);
      ++m_currentPlotTraceIndex;
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "after setting new graph data m_plotTraces size:"
  //<< m_plotTraces.size()
  //<< "m_currentPlotTraceIndex:" << m_currentPlotTraceIndex;
}


void
AbstractPlotWidget::showGraphData(std::size_t plotTraceIndex)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "m_plotTraces size:" << m_plotTraces.size()
  //<< "m_currentPlotTraceIndex:" << m_currentPlotTraceIndex
  //<< "plotTraceIndex:" << plotTraceIndex;

  if(plotTraceIndex < 0 || plotTraceIndex >= m_plotTraces.size())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Cannot be that index is less than zero or greater than size -1."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  std::shared_ptr<msXpSlibmass::Trace> currentTrace =
    m_plotTraces.at(plotTraceIndex);

  graph()->setData(QVector<double>::fromList(currentTrace->keyList()),
                   QVector<double>::fromList(currentTrace->valList()));

  m_currentPlotTraceIndex = plotTraceIndex;

  replot();

  // We need to update the correcponding trace in the multiGraph plot widget...
  mp_multiGraph->setData(QVector<double>::fromList(currentTrace->keyList()),
                         QVector<double>::fromList(currentTrace->valList()));
  mp_multiGraph->parentPlot()->replot();
}


//! Set the name of the plot widget.
void
AbstractPlotWidget::setName(QString name)
{
  m_name = name;
}


//! Get the name of the plot widget.
QString
AbstractPlotWidget::name() const
{
  return m_name;
}

//! Set the description of the plot widget.
void
AbstractPlotWidget::setDesc(QString desc)
{
  m_desc = desc;
}


//! Get the description of the plot widget.
QString
AbstractPlotWidget::desc() const
{
  return m_desc;
}


//! Set up the widget.
/*!

  Setting up the widget involves:

  - setting the focus policy
  - the interactions working with the QCustomPlot
  - create the context menu
  - add a graph and configure it
  - perform the signal/slot connections

*/
bool
AbstractPlotWidget::setupWidget()
{
  // This is required so that we get the keyboard events.
  setFocusPolicy(Qt::StrongFocus);
  setInteractions(QCP::iRangeZoom | QCP::iSelectPlottables | QCP::iMultiSelect);

  addGraph();

  m_pen.setStyle(Qt::SolidLine);
  m_pen.setBrush(Qt::black);
  m_pen.setWidth(1);

  graph()->setPen(m_pen);

  // Create a copy of the member pen for later modifs.
  QPen pen = m_pen;

  // Create a text item to describe the plot.
  m_descText = new QCPItemText(this);

  // No more needed, as the new item is automatically added to *this.
  // addItem(dynamic_cast<QCPAbstractItem *>(m_descText));
  // Same for all other occurrences below.

  m_descText->setPositionAlignment(Qt::AlignTop | Qt::AlignRight);
  m_descText->position->setType(QCPItemPosition::ptAxisRectRatio);
  m_descText->position->setCoords(
    1, 0); // place position at center/top of axis rect
  m_descText->setText(m_desc);
  m_descText->setFont(QFont(font().family(), 7));

  // Create the lines that will act as tracers.
  // The Starting tracer is the one that works without clicks and is a cross
  // tracer (so we need two lines)
  m_hStartTracer = new QCPItemLine(this);
  m_hStartTracer->setPen(m_pen);
  m_hStartTracer->start->setType(QCPItemPosition::ptPlotCoords);
  m_hStartTracer->end->setType(QCPItemPosition::ptPlotCoords);
  m_hStartTracer->start->setCoords(0, 0);
  m_hStartTracer->end->setCoords(0, 0);

  // Make a copy of the pen to just change its color and set that color to
  // the tracer line.
  pen = m_pen;
  pen.setColor(QColor("green"));

  m_vStartTracer = new QCPItemLine(this);
  m_vStartTracer->setPen(pen);
  m_vStartTracer->start->setType(QCPItemPosition::ptPlotCoords);
  m_vStartTracer->end->setType(QCPItemPosition::ptPlotCoords);
  m_vStartTracer->start->setCoords(0, 0);
  m_vStartTracer->end->setCoords(0, 0);

  // The other tracer is the tracer that is draw when a clickdrag operation is
  // performed, it is only vertical.
  pen.setColor(QColor("red"));
  m_endTracer = new QCPItemLine(this);

  m_endTracer->setPen(pen);
  m_endTracer->start->setType(QCPItemPosition::ptPlotCoords);
  m_endTracer->end->setType(QCPItemPosition::ptPlotCoords);
  m_endTracer->start->setCoords(0, 0);
  m_endTracer->end->setCoords(0, 0);

  m_zoomRect = new QCPItemRect(this);
  m_zoomRect->setPen(m_pen);
  m_zoomRect->topLeft->setType(QCPItemPosition::ptPlotCoords);
  m_zoomRect->bottomRight->setType(QCPItemPosition::ptPlotCoords);
  m_zoomRect->setVisible(false);

  m_selectLine = new QCPItemLine(this);
  m_selectLine->setPen(m_pen);
  m_selectLine->start->setType(QCPItemPosition::ptPlotCoords);
  m_selectLine->end->setType(QCPItemPosition::ptPlotCoords);
  m_selectLine->setVisible(false);

  m_xDeltaText = new QCPItemText(this);
  m_xDeltaText->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
  m_xDeltaText->position->setType(QCPItemPosition::ptPlotCoords);
  m_xDeltaText->setVisible(false);

  connect(this,
          &AbstractPlotWidget::mouseMove,
          this,
          &AbstractPlotWidget::mouseMoveHandler);

  connect(this,
          &AbstractPlotWidget::selectionChangedByUser,
          this,
          &AbstractPlotWidget::selectionChangedHandler);

  connect(this,
          &AbstractPlotWidget::mousePress,
          this,
          &AbstractPlotWidget::mousePressHandler);

  connect(this,
          &AbstractPlotWidget::mouseRelease,
          this,
          &AbstractPlotWidget::mouseReleaseHandler);

  connect(this,
          &AbstractPlotWidget::axisDoubleClick,
          this,
          &AbstractPlotWidget::axisDoubleClickHandler);

  return true;
}


//! Create the contextual menu.
QMenu *
AbstractPlotWidget::createContextMenu()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "m_plotTraces size:" << m_plotTraces.size()
  //<< "m_currentPlotTraceIndex:" << m_currentPlotTraceIndex;

  if(mpa_contextMenu != Q_NULLPTR)
    delete mpa_contextMenu;

  mpa_contextMenu = new QMenu(this);

  if(mp_colorMap == Q_NULLPTR)
    {
      QMenu *filteringMenu = mpa_contextMenu->addMenu("Filtering");

      QAction *action = filteringMenu->addAction("Apply &Savitzky-Golay");
      connect(action,
              &QAction::triggered,
              this,
              &AbstractPlotWidget::applySavitzkyGolay);

      filteringMenu->addSeparator();

      if(m_plotTraces.size() > 1 && m_currentPlotTraceIndex > 0)
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
          QAction *action = filteringMenu->addAction("&Back");
          connect(action,
                  &QAction::triggered,
                  this,
                  &AbstractPlotWidget::backFilteringStep);
        }

      if(m_plotTraces.size() > 1 &&
         m_currentPlotTraceIndex <= m_plotTraces.size() - 2)
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
          QAction *action = filteringMenu->addAction("&Forward");
          connect(action,
                  &QAction::triggered,
                  this,
                  &AbstractPlotWidget::forwardFilteringStep);
        }

      mpa_contextMenu->addSeparator();

      QMenu *exportMenu = mpa_contextMenu->addMenu("Export");

      mpa_exportDataAction = exportMenu->addAction("&Data");
      connect(mpa_exportDataAction,
              &QAction::triggered,
              this,
              &AbstractPlotWidget::exportData);

      mpa_plotXyFileAction = exportMenu->addAction("&Plot as xy file");
      connect(mpa_plotXyFileAction,
              &QAction::triggered,
              this,
              &AbstractPlotWidget::exportPlot);

      mpa_contextMenu->addSeparator();
    }

  mpa_savePlotGraphics =
    mpa_contextMenu->addAction("&Save plot to graphics file");
  connect(mpa_savePlotGraphics,
          &QAction::triggered,
          this,
          &AbstractPlotWidget::savePlotToGraphicsFile);

  return mpa_contextMenu;
}


//! Clear the history.
void
AbstractPlotWidget::clearHistory()
{
  m_history.freeList();

  // qDebug() << __FILE__ << __LINE__
  // << "Cleared history of window:" << m_name << "--" << m_desc;
}


//! Add (append) the \p item HistoryItem instance to the member \c m_history.
void
AbstractPlotWidget::addHistoryItem(HistoryItem *item)
{
  m_history.appendHistoryItem(item);
}


/*/js/
 * <PlotWidget>.showHistory()
 *
 * Shows a tooltip with the History of this plot widget.
 */
//! Craft a tooltip and display it with text describing the History of \c this
//! AbstractPlotWidget instance.
/*!

  \sa historyAsText().

*/
void
AbstractPlotWidget::showHistory()
{
  // We want to craft a tooltip widget with the brief history of *this
  // widget.

  QString text = m_history.asText(QString(), QString(), true /* brief */);
  text += m_history.innermostRangesAsText();

  QSize widgetSize = size();
  QPoint windowPos = mp_parentWnd->pos();

  QPoint toolTipPos = windowPos + QPoint(widgetSize.width() / 5, 0);

  // Let's position the tooltip at the left upper corner.
  QToolTip::showText(toolTipPos, text, this, QRect(), 10000);
}


/*/js/
 * <PlotWidget>.historyAsText()
 *
 * Return a textual representation of the history of this plot widget.
 */
//! Craft a textual representation of \c this AbstractPlotWidget History.
/*!

  \return A string with a textual representation of the innermost ranges of
  the \c m_history History.

  \sa showHistory().

*/
QString
AbstractPlotWidget::historyAsText()
{
  QString text = m_history.asText(QString(), QString(), true /* brief */);
  text += m_history.innermostRangesAsText();

  return text;
}


//! Find a minimal integration range starting at an existing data point
/*!

  If the user clicks onto a plot at a location that is not a true data point,
  get a data range that begins at the preceding data point and that ends at
  the clicked location point.

*/
bool
AbstractPlotWidget::findIntegrationLowerRangeForKey(double key,
                                                    QCPRange *p_range)
{
  if(p_range == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Given a key double value, we want to know what is the range that will
  // frame correctly the key double value if that key value is not exactly
  // the one of a point of the trace.

  // First of all get the keys of the graph.

  QCPGraph *theGraph = graph();

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


  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer = theGraph->data();
  // QCPGraphDataContainer is a typedef QCPDataContainer<QCPGraphData> and
  // QCPDataContainer< DataType > is a Class Template. So in this context,
  // DataType is QCPGraphData.
  // QCPGraphData is the data point, that is the (key,value) pair.

  QCPDataRange dataRange = p_graphDataContainer->dataRange();

  if(!dataRange.isValid())
    return false;

  if(!dataRange.size())
    return false;

  if(dataRange.size() > 1)
    {
      double firstKey = p_graphDataContainer->at(dataRange.begin())->key;
      double lastKey  = p_graphDataContainer->at(dataRange.end())->key;

      // There is one check to be done: the user might erroneously set the mouse
      // cursor beyond the last point of the graph. If that is the case, then
      // upper key needs to be that very point. All we need to do is return the
      // lower key, that is the pre-last key of the keys list. No need to
      // iterate in the keys list.

      if(key > lastKey)
        {
          // No need to search for the key in the keys, just get the lower key
          // immediately, that is, the key that is one slot left the last key.
          p_range->lower = p_graphDataContainer->at(dataRange.end() - 2)->key;
          p_range->upper = p_graphDataContainer->at(dataRange.end() - 1)->key;

          return true;
        }

      // Likewise, if the cursor is set left of the first plot point, then that
      // will be the lower range point. All we need is to provide the upper
      // range point as the second point of the plot.

      if(key < firstKey)
        {
          p_range->lower = firstKey;
          p_range->upper = p_graphDataContainer->at(dataRange.begin() + 1)->key;

          return true;
        }

      // Finally the generic case where the user point to any point *in* the
      // graph.

      p_range->lower =
        p_graphDataContainer->findBegin(key, /*expandedRange*/ true)->key;
      p_range->upper =
        std::prev(p_graphDataContainer->findEnd(key, /*expandedRange*/ true))
          ->key;

      return true;
    }

  return false;
}


//! Clear the history of the axis ranges.
/*!

  Each time a view on a plot is modified by zooming/unzooming panning via the
  axes, the new view ranges are stored. It is thus possible to rewind the axis
  range history (using the backspace key). This function clears that history.

*/
void
AbstractPlotWidget::clearAxisRangeHistory()
{
  while(!m_xAxisRangeHistory.isEmpty())
    delete m_xAxisRangeHistory.takeFirst();

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

  m_lastAxisRangeHistoryIndex = -1;
}


//! Create new axis range history items and append them to the history.
/*!

  The plot widget is queried to get the current x/y-axis ranges and the
  current ranges are appended to the history for x-axis and for y-axis.

*/
void
AbstractPlotWidget::updateAxisRangeHistory()
{
  m_xAxisRangeHistory.append(new QCPRange(xAxis->range()));
  m_yAxisRangeHistory.append(new QCPRange(yAxis->range()));

  // Set the indices to the current position -1;
  m_lastAxisRangeHistoryIndex = m_xAxisRangeHistory.size() - 1;

  // qDebug() << __FILE__ << __LINE__
  // << "AbstractPlotWidget::updateAxisRangeHistory";
}


//! Go up one history element in the axis history.
/*!

  If possible, back up one history item in the axis histories and update the
  plot's x/y-axis ranges to match that history item.

*/
void
AbstractPlotWidget::backOneAxisHistoryStep()
{
  if(m_lastAxisRangeHistoryIndex <= 0)
    return;

  setAxisRangesWithHistoryIndex(--m_lastAxisRangeHistoryIndex);
}


//! Get the axis histories at index \p index and update the plot ranges.
/*!

  \param index index at which to select the axis history item.

  \sa updateAxisRangeHistory().

*/
void
AbstractPlotWidget::setAxisRangesWithHistoryIndex(int index)
{
  if(index >= m_xAxisRangeHistory.size())
    return;

  xAxis->setRange(*(m_xAxisRangeHistory.at(index)));
  yAxis->setRange(*(m_yAxisRangeHistory.at(index)));

  replot();
}


//! Clear the plot.
/*!

  The graph is asked to clear its data, the axes are reset and the graph is
  replot to update it as an empty plot.

*/
void
AbstractPlotWidget::clearPlot()
{
  graph()->data().clear();

  // Set axes ranges, so we see all data:
  xAxis->setRange(-1, 1);
  yAxis->setRange(0, 1);
  rescaleAxes(false);

  clearAxisRangeHistory();
  updateAxisRangeHistory();

  replot();
}


//! Insert a keyboard key code in the list of registered key.
/*!

  The \c m_regQtKeyCodeMap map of keyboard keys is a register that stores
  key codes of keys that have a special meaning while working on the data
  plotted in the plot widgets along with the helper string.

*/
void
AbstractPlotWidget::registerQtKeyCode(int qtKeyCode, const QString &helpText)
{
  m_regQtKeyCodeMap.insert(qtKeyCode, helpText);
}


/*/js/
 * <PlotWidget>.showHelpSummary()
 *
 * Show a tooltip with a help string summarizing the various keyboard key
 * combinations.
 */

//! Craft a string explaining the various key combinations for the
//! integrations.
void
AbstractPlotWidget::showHelpSummary()
{
  QString helpText;

  QMapIterator<int, QString> iterator(m_regQtKeyCodeMap);

  while(iterator.hasNext())
    {
      iterator.next();

      helpText.append(iterator.value() + "\n");
    }

  QSize widgetSize = size();
  QPoint windowPos = mp_parentWnd->pos();
  QPoint toolTipPos =
    windowPos + QPoint(widgetSize.width() / 4, widgetSize.height() / 4);
  QToolTip::showText(toolTipPos, helpText, this, QRect());
}


//! Set the \c m_pressedKeyCode to the key code in \p event.
void
AbstractPlotWidget::keyPressEvent(QKeyEvent *event)
{
  m_pressedKeyCode = event->key();
}


//! Handle specific key codes and trigger respective actions.
void
AbstractPlotWidget::keyReleaseEvent(QKeyEvent *event)
{
  m_pressedKeyCode = 0;

  if(event->key() == Qt::Key_Backspace)
    {
      backOneAxisHistoryStep();

      event->accept();
    }
  else if(event->key() == Qt::Key_T)
    {
      m_tracersVisible = !m_tracersVisible;

      if(!m_tracersVisible)
        hideTracers();
      else
        showTracers();

      event->accept();
    }
  else if(event->key() == Qt::Key_H)
    {

      // The user wants to hide/show the corresponding graph from the multi
      // graph plot. If the shift key is pressed, then the user wants that all
      // the target plot widgets' multigraphgraph be hidden also.

      Qt::KeyboardModifiers modifiers =
        QGuiApplication::queryKeyboardModifiers();

      if(modifiers & Qt::ShiftModifier)
        emit toggleMultiGraphSignal(this, true);
      else
        emit toggleMultiGraphSignal(this, false);

      event->accept();
    }
  else if(event->key() == Qt::Key_Delete)
    {
      // The user wants to destroy this plot widget.
      // We do not directly handle our destruction, we delegate that
      // destructino to the parent window.

      shouldDestroyPlotWidget();
    }
  else if(event->key() == Qt::Key_O)
    {
      // The user wants to show the history (brief form) of *this widget.
      showHistory();
    }
}


/*/js/
 * <PlotWidget>.shouldDestroyPlotWidget()
 *
 * Start the destruction of this plot widget.
 */

//! Start the process of destroying a plot widget.
/*!

  The process involves removing the corresponding plot from the multigraph
  plot widget. Then a signal is emitted to destroy the plot widget.

*/
void
AbstractPlotWidget::shouldDestroyPlotWidget()
{
  // When a single graph plot widget is destroyed, it is necessary to
  // automatically destroy the corresponding, the matching multigraph
  // QGraph. This signal is connected to a slot that will handle the removal
  // of the matching multigraph graph.
  static_cast<AbstractMultiPlotWnd *>(mp_parentWnd)->removeMultiGraph(this);

  // This abstract plot widget does not handle its destruction by
  // itself, it delegates its distruction to the main window of the
  // program that will handle the slot matching this signal.

  emit destroyPlotWidget(this);
}


/*/js/
 * <PlotWidget>.toggleMultiGraph()
 *
 * If the multi-graph plot corresponding to this plot widget is visible, make
 * it invisible. And vice versa.
 */

//! Toggle visibility of the corresponding graph in the multigraph plot
//! widget.
void
AbstractPlotWidget::toggleMultiGraph()
{
  static_cast<AbstractMultiPlotWnd *>(mp_parentWnd)->toggleMultiGraph(this);
}


/*/js/
 * <PlotWidget>.showMultiGraph()
 *
 * Make the multi-graph plot corresponding to this plot widget visible.
 */
//! Make visible the corresponding graph in the multigraph plot widget.
void
AbstractPlotWidget::showMultiGraph()
{
  static_cast<AbstractMultiPlotWnd *>(mp_parentWnd)->showMultiGraph(this);
}


/*/js/
 * <PlotWidget>.hideMultiGraph()
 *
 * Make the multi-graph plot corresponding to this plot widget invisible.
 */
//! Make invisible the corresponding graph in the multigraph plot widget.
void
AbstractPlotWidget::hideMultiGraph()
{
  static_cast<AbstractMultiPlotWnd *>(mp_parentWnd)->hideMultiGraph(this);
}


//! Set the plotting and decoration color for \c this plot widget.
/*!

  This function is called when the user wants to change the color of the plot
  widget's graph and decorations (tick labels, axis names).
  */
void
AbstractPlotWidget::setPlottingColor(const QColor &newColor)
{
  if(!newColor.isValid())
    return;

  // This function does not do anything if *this is a multi-graph plot
  // widget.
  if(m_isMultiGraph)
    {
      // qDebug() << __FILE__ << __LINE__
      //<< "Returning because *this plot widget is multi-graph.";

      return;
    }

  // We want to have the color handy
  m_plottingColor = newColor;

  // First this single-graph widget
  QPen pen;

  // If this plot widget is hosting the color map, there is a specific
  // treatement.
  if(mp_colorMap != Q_NULLPTR)
    {
      QCPAxis *xAxis = mp_colorMap->keyAxis();

      pen = xAxis->basePen();
      pen.setColor(m_plottingColor);

      xAxis->setBasePen(pen);
      xAxis->setLabelColor(m_plottingColor);
      xAxis->setTickLabelColor(m_plottingColor);

      QCPAxis *yAxis = mp_colorMap->valueAxis();
      yAxis->setBasePen(pen);
      yAxis->setLabelColor(m_plottingColor);
      yAxis->setTickLabelColor(m_plottingColor);

      replot();

      // There is not multi-graph replica, just return.
      return;
    }

  pen = graph()->pen();
  pen.setColor(m_plottingColor);
  graph()->setPen(pen);

  replot();

  return;
}


//! Change the plotting and decoration color for \c this plot widget.
/*!

  This function is called when the user wants to change the color of the plot
  widget's graph and decorations (tick labels, axis names).
  */
void
AbstractPlotWidget::changePlottingColor(const QColor &newColor)
{
  if(!newColor.isValid())
    return;

  // This function does not do anything if *this is a multi-graph plot
  // widget.
  if(m_isMultiGraph)
    {
      // qDebug() << __FILE__ << __LINE__
      //<< "Returning because *this plot widget is multi-graph.";

      return;
    }

  // We want to have the color handy
  m_plottingColor = newColor;

  // First this single-graph widget
  QPen pen;

  // If this plot widget is hosting the color map, there is a specific
  // treatement.
  if(mp_colorMap != Q_NULLPTR)
    {
      QCPAxis *xAxis = mp_colorMap->keyAxis();

      pen = xAxis->basePen();
      pen.setColor(m_plottingColor);

      xAxis->setBasePen(pen);
      xAxis->setLabelColor(m_plottingColor);
      xAxis->setTickLabelColor(m_plottingColor);

      QCPAxis *yAxis = mp_colorMap->valueAxis();
      yAxis->setBasePen(pen);
      yAxis->setLabelColor(m_plottingColor);
      yAxis->setTickLabelColor(m_plottingColor);

      replot();

      // There is not multi-graph replica, just return.
      return;
    }

  pen = graph()->pen();
  pen.setColor(m_plottingColor);
  graph()->setPen(pen);

  replot();

  // Then, the replica graph in the multi-graph plot widget

  AbstractMultiPlotWnd *parentWnd =
    static_cast<AbstractMultiPlotWnd *>(mp_parentWnd);

  QCPGraph *multiGraph = parentWnd->multiGraph(this);

  // qDebug() << __FILE__ << __LINE__
  //<< "for this:" << this << "found multiGraph: " << multiGraph;

  pen = multiGraph->pen();
  pen.setColor(m_plottingColor);
  multiGraph->setPen(pen);
  multiGraph->parentPlot()->replot();

  return;
}


//! Return the pen color of \c this plot widget graph.
QColor
AbstractPlotWidget::color() const
{
  return m_plottingColor;
}


/*/js/
 * <PlotWidget>.exportData()
 *
 * Open the data export configuration window. Exporting data allows exporting
 * only a subset of the whole data set. It can only be performed if the
 * original data were read from a SQLite3 database-formatted mass data file.
 */
//! Export data to one or more files.
/*!

  This function starts the process. It invokes a number of other helper
  functions.

  When the data are initially loaded from a SQLite3 db-formatted file, they
  can be exported to new files according to a number of criteria defined by
  the user.

  \sa SqlMassDataSlicer <br> SqlMassDataSlicerWnd.
  */
void
AbstractPlotWidget::exportData()
{
  // The user asks to export data. There are different ways of doing this:
  //
  // 1. export all the data contained in the current plot widget view, that
  // is the key axis (X axis). That is easy, choose a file and that's done.
  //
  // 2. export the data according to some criteria:
  // a. export the data in the key range (modifiable in the dialog box) and
  // slice all these data in chunks of a given size (count is deduced) or
  // b. export the data in a number of chunks (size is deduced).

  // Note that the window in which the user configures the data export
  // operation is owned by MainWindow, the program's main window. That
  // window is available for any plot widget to use, and thus needs to be
  // updated with the caller widget's data to be useful.

  if(m_isMultiGraph || mp_colorMap != Q_NULLPTR)
    return;

  // At the moment, this export only works with data that are available as
  // an SQLite database file (format massDb).
  if(mp_massSpecDataSet->fileFormat() !=
     MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3)
    {
      QMessageBox msgBox;
      msgBox.setText(
        "The data cannot be exported because they were not from "
        "a SQLite3 database format file.");
      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.exec();
      return;
    }

  // Get a pointer to the configuratin window

  MainWindow *p_mainWindow = static_cast<MainWindow *>(mp_parentWnd->parent());
  SqlMassDataSlicerWnd *p_massDataSlicerWnd =
    p_mainWindow->mp_sqlMassDataSlicerWnd;

  QCPAxis *xAxis = graph()->keyAxis();
  double start   = xAxis->range().lower;
  double end     = xAxis->range().upper;

  // We want to do the data export by taking into account the history of
  // the current widget. We have m_history that accounts for previous
  // events, but we need to craft one new for the start and end values
  // above.

  History localHistory(m_history);

  HistoryItem *histItem = new HistoryItem;

  // arbitraryIntegrationType() is pure virtual, defined in the derived class

  histItem->newIntegrationRange(arbitraryIntegrationType(), start, end);
  localHistory.appendHistoryItem(histItem);

  // We want to provide the full path name to the file

  QFileInfo fileInfo(m_fileName);

  // Sanity check
  if(!fileInfo.exists())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Cannot be that the plot widget has no file name member data set "
      "correctly."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(!fileInfo.isAbsolute())
    {

      // Try to craft an absolute path and check that it exists.

      QString absPath = QDir::current().absolutePath() + '/' + m_fileName;

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "absPath:" << absPath;

      fileInfo.setFile(absPath);

      if(!fileInfo.exists())
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "The plot widget's member m_fileName must be an absolute file path "
          "name. Current value: %s."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__,
          m_fileName.toLatin1().data());
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The plot widget's m_fileName:" << m_fileName;

  p_massDataSlicerWnd->initialize(
    fileInfo.absoluteFilePath(), start, end, localHistory);

  p_massDataSlicerWnd->activate();
}


/*/js/
 * <PlotWidget>.asXyText()
 *
 * Return  a textual representation of \c this plot widget data.
 *
 * Only the data range currently displayed is processed.
 */
//! Return a textual representation of \c this plot widget.
/*!
 * Only the currently visible data range is processed.
 */
QString
AbstractPlotWidget::asXyText()
{
  if(m_isMultiGraph)
    return QString();

  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = xAxis->range().lower;
  double rangeEnd   = xAxis->range().upper;

  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer = graph()->data();

  // Iterate in the keys
  auto beginIt =
    p_graphDataContainer->findBegin(rangeStart, /*expandedRange*/ true);
  auto endIt = p_graphDataContainer->findEnd(rangeEnd, /*expandedRange*/ true);

  QString xyString;

  for(auto iter = beginIt; iter != endIt; ++iter)
    {
      xyString += QString("%1 %2\n")
                    .arg(iter->key, 0, 'f', 10)
                    .arg(iter->value, 0, 'f', 10);
    }

  return xyString;
}


/*/js/
 * <PlotWidget>.exportPlot()
 *
 * Open a file selection dialog window to select a file in which to export
 * this plot widget's data.
 *
 * Unlike the exportData() function, this function only exports the currently
 * displayed graph data to a comma-separated value text file.
 */
//! Export plot data to a text file as a simple plot.
/*!

  Unlike the exportData() function, this function only exports the currently
  displayed graph data to a comma-separated value text file.

  Note that only the currently selected plot ranges are exported. To export
  the whole plot data, make sure to unzoom the plot.
  */
void
AbstractPlotWidget::exportPlot()
{
  if(m_isMultiGraph)
    return;

  // The user asks that the (that is not the whole set of data), but only
  // the
  // x,y plot for the current plot widget. This is the easiest solution. The
  // only constraint is that we only export the data corresponding to the
  // current X axis range.

  QString outputFileName = m_fileName;
  outputFileName += ".xy";

  QString newFileName =
    QFileDialog::getSaveFileName(this,
                                 "Open file",
                                 outputFileName,
                                 tr("Ascii (x,y) files (*.txt *.xy *.asc)"));

  if(newFileName.isEmpty())
    return;

  outputFileName = newFileName;

  QString xyString = asXyText();

  QFile file(outputFileName);

  file.open(QIODevice::WriteOnly);
  QTextStream out(&file); // we will serialize the data into the file
  out << xyString;

  file.close();
}


/*/js/
 * <PlotWidget>.exportPlotToFile(fileName, rangeStart, rangeEnd)
 *
 * Export plot data to a text file as a simple plot. Limit the data range to
 * export using the numerical parameters.
 *
 * fileName: <String> holding the file name
 * rangeStart: <Number> holding the beginning of the range to be exported
 * rangeEnd <Number> holding the end of the range to be exported
 */
//! Export plot data to a text file as a simple plot.
/*!

  Unlike the exportPlo() function, this function takes the name of the file in
  which to export the data and the range that is required to be exported. This
  function is useful in scripting.
  */
void
AbstractPlotWidget::exportPlotToFile(const QString &fileName,
                                     double start,
                                     double end)
{
  if(m_isMultiGraph)
    return;

  // The user asks that the (that is not the whole set of data), but only
  // the x,y plot for the current plot widget. This is the easiest solution.
  // The only constraint is that we only export the data corresponding to
  // the current X axis range.

  QString outputFileName = fileName;

  if(outputFileName.isEmpty())
    {
      outputFileName = m_fileName;

      outputFileName += ".xy";

      QString newFileName = QFileDialog::getSaveFileName(
        this,
        "Open file",
        outputFileName,
        tr("Ascii (x,y) files (*.txt *.xy *.asc)"));

      if(newFileName.isEmpty())
        return;

      outputFileName = newFileName;
    }

  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = start;

  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = end;

  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer = graph()->data();

  // Iterate in the keys
  auto beginIt =
    p_graphDataContainer->findBegin(rangeStart, /*expandedRange*/ true);
  auto endIt = p_graphDataContainer->findEnd(rangeEnd, /*expandedRange*/ true);

  QString xyString;

  for(auto iter = beginIt; iter != endIt; ++iter)
    {
      xyString += QString("%1 %2\n")
                    .arg(iter->key, 0, 'f', 10)
                    .arg(iter->value, 0, 'f', 10);
    }

  // Now craft a header that will identify clearly the data. The '#' is a
  // comment prefix.

  QString header;

  QFile file(outputFileName);

  if(file.open(QIODevice::WriteOnly) == false)
    {
      qWarning()
        << __FILE__ << __LINE__ << __FUNCTION__
        << "Failed to export the plot because the file could not be opened.";

      return;
    }

  // qWarning() << "opened file with text:" << outputString;

  QTextStream out(&file); // we will serialize the data into the file
  out << xyString;

  file.close();
}


/*/js/
 * <PlotWidget>.savePlotToGraphicsFile()
 *
 * Open the graphics export configuration dialog window.
 */
//! Open the graphics export configuration dialog window.
bool
AbstractPlotWidget::savePlotToGraphicsFile()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  SaveToGraphicsFileDlg dlg(this, this, "mineXpert");

  if(QDialog::Accepted == dlg.exec())
    return true;
  else
    return false;
}


/*/js/
 * <PlotWidget>.saveToPdfFile(fileName, noCosmeticPen, width, height,
 * pdfCreator, title)
 *
 * Save this plot widget as a PDF file graphics.
 *
 * fileName: <String> holding the file name
 * noCosmeticPen: <Boolean> that tells if pen optimizations should be
 * performed
 * width: <Number> holding the size of the graphics file in pixels
 * height: <Number> holding the size of the graphics file in pixels
 * pdfCreator: <String> holding the name of the creator of the PDF file
 * title: <String> holding the title of the graphics file
 */
//! Save this plot widget as a PDF file graphics.
bool
AbstractPlotWidget::savePlotToPdfFile(const QString &fileName,
                                      bool noCosmeticPen,
                                      int width,
                                      int height,
                                      const QString &pdfCreator,
                                      const QString &title)
{
  return savePdf(fileName,
                 width,
                 height,
                 noCosmeticPen ? QCP::epNoCosmetic : QCP::epAllowCosmetic,
                 pdfCreator,
                 title);
}


/*/js/
 * <PlotWidget>.keys()
 *
 * Return an <Array> of <Number> values representing the keys of the data
 * plotted.
 */
//! Return a list of double values representing the keys of the data plotted.
QList<double>
AbstractPlotWidget::keys()
{
  QList<double> keyList;

  if(m_isMultiGraph)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "Cannot select keys from multiGraph plot.";

      return keyList;
    }

  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer = graph()->data();

  // Iterate in the keys
  auto beginIt = p_graphDataContainer->begin();
  auto endIt   = p_graphDataContainer->end();

  for(auto iter = beginIt; iter != endIt; ++iter)
    keyList.append(iter->key);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "keys:" << keyList();

  return keyList;
}


/*/js/
 * <PlotWidget>.values()
 *
 * Return an <Array> of <Number> values representing the values of the data
 * plotted.
 */
//! Return a list of double values representing the values of the data
//! plotted.
QList<double>
AbstractPlotWidget::values()
{
  QList<double> valueList;

  if(m_isMultiGraph)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "Cannot select keys from multiGraph plot.";

      return valueList;
    }

  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer = graph()->data();

  // Iterate in the keys
  auto beginIt = p_graphDataContainer->begin();
  auto endIt   = p_graphDataContainer->end();

  for(auto iter = beginIt; iter != endIt; ++iter)
    valueList.append(iter->value);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "values:" << valueList;

  return valueList;
}


//! Set the last TIC intensity value.
void
AbstractPlotWidget::setLastTicIntensity(double value)
{
  m_lastTicIntensity = value;
}


/*/js/
 * <PlotWidget>.lastTicIntensity()
 *
 * Return the last TIC intensity value that was computed.
 */
//! Return the last TIC intensity value that was computed.
double
AbstractPlotWidget::lastTicIntensity(void)
{
  return m_lastTicIntensity;
}


/*/js/
 * <PlotWidget>.trace()
 *
 * Return a Trace object initialized with the data in this plot widget.
 */
//! Return a Trace instance initialized with the data in this plot widget
msXpSlibmass::Trace
AbstractPlotWidget::trace()
{
  if(m_isMultiGraph)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "Cannot select keys from multiGraph plot.";

      return msXpSlibmass::Trace();
    }

  msXpSlibmass::Trace trace;

  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer = graph()->data();

  // Iterate in the keys
  auto beginIt = p_graphDataContainer->begin();
  auto endIt   = p_graphDataContainer->end();

  for(auto iter = beginIt; iter != endIt; ++iter)
    {
      msXpSlibmass::DataPoint *dataPoint =
        new msXpSlibmass::DataPoint(iter->key, iter->value);

      trace.append(dataPoint);
    }

  return trace;
}


//! Trigger a graph replot action with new x-axis and y-axis ranges.
void
AbstractPlotWidget::replotWithAxisRange(QCPRange xAxisRange,
                                        QCPRange yAxisRange,
                                        int whichAxis)
{
  if(whichAxis & X_AXIS)
    xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
  if(whichAxis & Y_AXIS)
    yAxis->setRange(yAxisRange.lower, yAxisRange.upper);

  replot();

  // We do not want to update the history, because there would be way too
  // much history items, since this function is called upon mouse moving
  // handling and not only during mouse release events.
  // updateAxisRangeHistory();
}


/*/js/
 * <PlotWidget>.replotWithAxisRangeX(lower, upper)
 *
 * Replot this plot widget with new key (X-axis) data range.
 *
 * lower: <Number> holding the start value of the range
 * upper: <Number> holding the end value of the range
 */
//! Trigger a graph replot action with new x-axis range.
void
AbstractPlotWidget::replotWithAxisRangeX(double lower, double upper)
{
  xAxis->setRange(lower, upper);
  replot();
}


/*/js/
 * <PlotWidget>.replotWithAxisRangeY(lower, upper)
 *
 * Replot this plot widget with new value (Y-axis) data range.
 *
 * lower: <Number> holding the start value of the range
 * upper: <Number> holding the end value of the range
 */
//! Trigger a graph replot action with new y-axis range.
void
AbstractPlotWidget::replotWithAxisRangeY(double lower, double upper)
{
  yAxis->setRange(lower, upper);
  replot();
}


void
AbstractPlotWidget::applySavitzkyGolay()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // We want to use the configuration of the MzIntegrationParams dialog window.

  SavGolFilter savGolFilter =
    SavGolFilter(mzIntegrationParams().m_savGolParams);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "SavGolFilter as text:" << savGolFilter.asText();

  QVector<double> keyVector = QVector<double>::fromList(keys());
  QVector<double> valVector = QVector<double>::fromList(values());

  savGolFilter.initializeData(keyVector, valVector);
  savGolFilter.filter();
  QVector<double> filteredValues;
  savGolFilter.filteredData(filteredValues);

  setGraphData(keyVector, filteredValues);

  // xAxis->rescale();
  // yAxis->rescale();

  replot();

  // We need to update the correcponding trace in the multiGraph plot widget...
  mp_multiGraph->setData(keyVector, filteredValues);
  mp_multiGraph->parentPlot()->replot();
}


void
AbstractPlotWidget::backFilteringStep()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
  showGraphData(--m_currentPlotTraceIndex);
}


void
AbstractPlotWidget::forwardFilteringStep()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
  showGraphData(++m_currentPlotTraceIndex);
}


//! Hide the selection line, the xDelta text and the zoom rectangle items.
void
AbstractPlotWidget::hideAllPlotItems()
{
  if(m_selectLine->visible())
    m_selectLine->setVisible(false);

  if(m_xDeltaText->visible())
    m_xDeltaText->setVisible(false);

  m_zoomRect->setVisible(false);
}


//! Show the traces (vertical and horizontal).
void
AbstractPlotWidget::showTracers()
{
  m_tracersVisible = true;
  m_vStartTracer->setVisible(true);
  m_hStartTracer->setVisible(true);
  m_endTracer->setVisible(true);

  // Force a replot to make sure the action is immediately visible by the
  // user, even without moving the mouse.
  replot();
}


//! Hide the traces (vertical and horizontal).
void
AbstractPlotWidget::hideTracers()
{
  m_tracersVisible = false;
  m_vStartTracer->setVisible(false);
  m_hStartTracer->setVisible(false);
  m_endTracer->setVisible(false);

  // Force a replot to make sure the action is immediately visible by the
  // user, even without moving the mouse.
  replot();
}


//! Tell if the current selection has a rectangular shape.
/*!

  The goal here is to establish if the user is selecting a rectangle of enough
  height to make a meaningful zoom operation so that we can switch from delta
  line measurement display to actual rectangle zoom display. The criterion is
  that the height of the selection must be at least 10% the height of the
  plot.

  \return true if the selection looks like a rectangle, that is it has a
  height at least 10% of the plot height.

*/
bool
AbstractPlotWidget::isSelectionARectangle()
{
  // First get the height of the plot.
  double plotHeight = yAxis->range().upper - yAxis->range().lower;

  double heightDiff = fabs(m_startDragPoint.y() - m_currentDragPoint.y());

  double heightDiffRatio = (heightDiff / plotHeight) * 100;

  if(heightDiffRatio > 10)
    return true;

  return false;
}


//! Draw the zoom rectangle and actually zoom.
/*!

  This action is triggered when the user drags the mouse left button over the
  plot area. The rectangle drawn make for the new zoomed-in view.

*/
void
AbstractPlotWidget::drawRectangleAndZoom()
{
  // The user has drawn the mouse left button on the graph, which means he is
  // willing to draw a zoom rectangle.

  if(m_selectLine->visible())
    m_selectLine->setVisible(false);

  if(m_xDeltaText->visible())
    m_xDeltaText->setVisible(false);

  m_zoomRect->topLeft->setCoords(m_startDragPoint.x(), m_startDragPoint.y());
  m_zoomRect->bottomRight->setCoords(m_currentDragPoint.x(),
                                     m_currentDragPoint.y());

  m_zoomRect->setVisible(true);

  // qDebug() << __FILE__ << __LINE__
  // << "should be drawing a rectangle:" << m_startDragPoint.x() <<
  // m_startDragPoint.y()
  // << m_currentDragPoint.x() << m_currentDragPoint.y();

  replot();
}


//! Draw the horizontal x-delta line and write the delta value.
/*!

  When the user drags the left mouse button in the horizontal direction, with
  almost no vertical movement, draw the line that span the x-axis interval
  spanning the mouse dragging movement and print the delta value.

  This is typically used by the user willing to measure the distance between
  two peaks in the graph.

*/
void
AbstractPlotWidget::drawXDeltaLineAndMeasure()
{
  // The user has drawn the mouse left button on the graph in such a way
  // that the xDelta is big and the yDelta is almost nothing, that
  // means that he does not want to draw a rectangle but a line to
  // measure the delta between two points of the graph.

  m_zoomRect->setVisible(false);

  // Note that the m_xRangeMin, m_xRangeMax and m_xDelta values were set
  // in the mouse move with dragging on handler. They are *sorted* such that
  // the m_xRangeMin contains systematically the lowest value. m_xDelta is
  // already stored as abs().

  double m_xDeltaHalf = m_xDelta / 2;

  m_xDeltaText->position->setCoords(m_xRangeMin + m_xDeltaHalf,
                                    m_currentDragPoint.y());
  m_xDeltaText->setText(QString("%1").arg(m_xDelta, 0, 'f', 3));

  m_xDeltaText->setFont(QFont(font().family(), 7));

  m_xDeltaText->setVisible(true);

  // If we do not use the m_xRangeMin/Max values, it is because they are
  // sorted, and we do not want, we want the user the actually see the real
  // starting drag point and the real current drag point.
  QString statusBarMsg = QString("(%1,%2)->(%3,%4) ; Delta: %5")
                           .arg(m_startDragPoint.x(), 0, 'f', 3)
                           .arg(m_startDragPoint.y(), 0, 'f', 3)
                           .arg(m_currentDragPoint.x(), 0, 'f', 3)
                           .arg(m_currentDragPoint.y(), 0, 'f', 3)
                           .arg(m_xDelta, 0, 'f', 6);

  emit updateStatusBarSignal(statusBarMsg);

  if(!m_selectLine->visible())
    m_selectLine->setVisible(true);

  m_selectLine->start->setCoords(m_startDragPoint.x(), m_startDragPoint.y());
  // But we want the line to be horizontal, thus we keep the original y
  // value.
  m_selectLine->end->setCoords(m_currentDragPoint.x(), m_startDragPoint.y());

  // Also, we do not want arrows, because we are not integrating anything
  // here.
  m_selectLine->setHead(QCPLineEnding::esNone);
  m_selectLine->setTail(QCPLineEnding::esNone);

  replot();
}


//! Draw the horizontal x-delta line and write the delta value.
/*!

  When the user drags the right mouse button in the horizontal direction, with
  almost no vertical movement, draw the line that span the x-axis interval
  spanning the mouse dragging movement and print the delta value. Show line
  endings for the delta measurement line because this mouse drag might be for
  an integration (this is only known by looking at the \c m_pressedKeyCode
  value and checking if it listed in \c m_regQtKeyCodeList.

  This is typically used by the user willing to integrate mass data in the
  range selected.

*/
void
AbstractPlotWidget::drawXDeltaLineForZoomOrIntegration()
{
  // When the user draws a right-button line, then she is willing to select
  // an X span to integrate. Depending on the keyboard key that is pressed
  // that integration does one thing or another. This is only known when the
  // mouse button is released. So we do not trigger the integration here.

  m_zoomRect->setVisible(false);

  // We also want to show the span as a text item.

  // Note that the m_xRangeMin, m_xRangeMax and m_xDelta values were set
  // in the mouse move with dragging on handler. They are *sorted* such that
  // the m_xRangeMin contains systematically the lowest value. m_xDelta is
  // already stored as abs().

  double m_xDeltaHalf = m_xDelta / 2;

  m_xDeltaText->position->setCoords(m_xRangeMin + m_xDeltaHalf,
                                    m_currentDragPoint.y());
  m_xDeltaText->setText(QString("%1").arg(m_xDelta, 0, 'f', 3));

  m_xDeltaText->setFont(QFont(font().family(), 7));

  m_xDeltaText->setVisible(true);

  if(!m_selectLine->visible())
    m_selectLine->setVisible(true);

  m_selectLine->start->setCoords(m_startDragPoint.x(), m_startDragPoint.y());

  // We only want the arrows when we are preparing an integration
  // operation. So check if one corresponding key is pressed.

  if(m_regQtKeyCodeMap.contains(m_pressedKeyCode))
    {
      m_selectLine->setHead(QCPLineEnding::esSpikeArrow);
      m_selectLine->setTail(QCPLineEnding::esSpikeArrow);
    }
  else
    {
      m_selectLine->setHead(QCPLineEnding::esNone);
      m_selectLine->setTail(QCPLineEnding::esNone);
    }

  // But we want the line to be horizontal, thus we keep the original y
  // value.
  m_selectLine->end->setCoords(m_currentDragPoint.x(), m_startDragPoint.y());

  replot();

  // But we also want to update the status bar with the current cursor
  // positon and also the delta that the cursor drag is spanning.

  QString statusBarMsg = QString("(%1,%2)->(%3,%4) ; Delta: %5")
                           .arg(m_startDragPoint.x())
                           .arg(m_startDragPoint.y())
                           .arg(m_currentDragPoint.x())
                           .arg(m_currentDragPoint.y())
                           .arg(m_xDelta, 0, 'f', 6);

  emit updateStatusBarSignal(statusBarMsg);
}


//! Tell if the mouse click was right the one of the axes of the plot.
bool
AbstractPlotWidget::isClickOntoAnyAxis(const QPointF &mousePoint)
{
  if(isClickOntoXAxis(mousePoint) || isClickOntoYAxis(mousePoint))
    return true;

  return false;
}


//! Tell if the mouse click was right on the x-axis.
bool
AbstractPlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
{
  QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);

  if(layoutElement &&
     layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
    {
      // The graph is *inside* the axisRect that is the outermost envelope of
      // the graph. Thus, if we want to know if the click was indeed on an
      // axis, we need to check what selectable part of the the axisRect we
      // were
      // clicking:
      QCPAxis::SelectablePart selectablePart;

      selectablePart = xAxis->getPartAt(mousePoint);

      if(selectablePart == QCPAxis::spAxisLabel ||
         selectablePart == QCPAxis::spAxis ||
         selectablePart == QCPAxis::spTickLabels)
        return true;
    }
  return false;
}


//! Tell if the mouse click was right on the y-axis.
bool
AbstractPlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
{
  QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);

  if(layoutElement &&
     layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
    {
      // The graph is *inside* the axisRect that is the outermost envelope of
      // the graph. Thus, if we want to know if the click was indeed on an
      // axis, we need to check what selectable part of the the axisRect we
      // were
      // clicking:
      QCPAxis::SelectablePart selectablePart;

      selectablePart = yAxis->getPartAt(mousePoint);

      if(selectablePart == QCPAxis::spAxisLabel ||
         selectablePart == QCPAxis::spAxis ||
         selectablePart == QCPAxis::spTickLabels)
        return true;
    }
  return false;
}


//! Calcuted delta values for both x/y axes and sort the values.
/*!

  The values are sorted so that the min value is always the lower value and
  the max value is always the greater value. This is because the user might
  perform a mouse dragging operation in reverse direction, that is from the
  right of the plot to the left of that plot (x-axis) or from the top of the
  plot to the bottom of the plot (y-axis), thus from greater values to lower
  values.

*/
void
AbstractPlotWidget::calculateSortedDragDeltas()
{
  m_xRangeMin = 0;
  m_xRangeMax = 0;

  m_yRangeMin = 0;
  m_yRangeMax = 0;

  double deltaX = m_currentDragPoint.x() - m_startDragPoint.x();
  if(deltaX < 0)
    {
      m_xRangeMin = m_currentDragPoint.x();
      m_xRangeMax = m_startDragPoint.x();
    }
  else
    {
      m_xRangeMin = m_startDragPoint.x();
      m_xRangeMax = m_currentDragPoint.x();
    }

  double deltaY = m_currentDragPoint.y() - m_startDragPoint.y();
  if(deltaY < 0)
    {
      m_yRangeMin = m_currentDragPoint.y();
      m_yRangeMax = m_startDragPoint.y();
    }
  else
    {
      m_yRangeMin = m_startDragPoint.y();
      m_yRangeMax = m_currentDragPoint.y();
    }

  return;
}

//! Rescale the x/y axes as a result of user interaction.
/*!

  Depending on the keyboard key that is pressed, axis rescaling can be carried
  over in different ways:

  - full scale on both axes
  - full scale only on one of the axes

*/
void
AbstractPlotWidget::axisRescale()
{
  // Get the current x lower/upper range.
  double xLower = xAxis->range().lower;
  double xUpper = xAxis->range().upper;

  // Get the current y lower/upper range.
  double yLower = yAxis->range().lower;
  double yUpper = yAxis->range().upper;


  if(m_clickWasOnXAxis)
    {
      // We are changing the range of the X axis.

      // What is the x delta ?
      double xDelta = m_currentDragPoint.x() - m_startDragPoint.x();

      if(xDelta < 0)
        {
          // The dragging operation was from right to left, we are enlarging
          // the range (thus, we are unzooming the view, since the widget
          // always has the same size).

          xAxis->setRange(xLower, xUpper + fabs(xDelta));
        }
      else
        {
          // The dragging operation was from left to right, we are reducing
          // the range (thus, we are zooming the view, since the widget
          // always has the same size).

          xAxis->setRange(xLower, xUpper - fabs(xDelta));
        }

      // We may either leave the scale of the Y axis as is (default) or
      // the user may want an automatic scale of the Y axis such that the
      // data displayed in the new X axis range are full scale on the Y
      // axis. For this, the Shift modifier key should be pressed.

      Qt::KeyboardModifiers modifiers =
        QGuiApplication::queryKeyboardModifiers();

      if(modifiers & Qt::ShiftModifier)
        {
          // In this case, we want to make a rescale of the Y axis such that
          // it displays full scale the data in the current X axis range only.
          double min = 0;
          double max = 0;

          yMinMaxOnXAxisCurrentRange(min, max);

          yAxis->setRange(min, max);
        }
      // else, do leave the Y axis range unchanged.
    }
  // End of
  // if(m_clickWasOnXAxis)
  else // that is, if(m_clickWasOnYAxis)
    {
      // We are changing the range of the Y axis.

      // What is the y delta ?
      double yDelta = m_currentDragPoint.y() - m_startDragPoint.y();

      if(yDelta < 0)
        {
          // The dragging operation was from top to bottom, we are enlarging
          // the range (thus, we are unzooming the view, since the widget
          // always has the same size).

          yAxis->setRange(yLower, yUpper + fabs(yDelta));
        }
      else
        {
          // The dragging operation was from bottom to top, we are reducing
          // the range (thus, we are zooming the view, since the widget
          // always has the same size).

          yAxis->setRange(yLower, yUpper - fabs(yDelta));
        }
    }
  // End of
  // else // that is, if(m_clickWasOnYAxis)

  replot();
}


//! Determine the minimum and maximum y-axis values in the plot.
/*!

  The values are determined for the current x-axis range.

  If \p g is nullptr, then graph() is used as the graph from which to read the
  data.

  \param min reference to the double value in which to set the minimum y-axis
  value.

  \param max reference to the double value in which to set the maximum y-axis
  value.

*/
void
AbstractPlotWidget::yMinMaxOnXAxisCurrentRange(double &min,
                                               double &max,
                                               QCPGraph *g)
{
  // This code is not for the color map, it is only for the conventional graphs.
  if(mp_colorMap != nullptr)
    return;

  // We need to find the min and max Y values in the min->max range of the X
  // axis. Note that depending on the kind of plot widget, we'll have to check
  // multiple graphs or not. If there are multiple graphs, then we need to
  // iterate in them and find the max Y value.
  QSharedPointer<QCPGraphDataContainer> p_graphDataContainer;

  if(g == nullptr && m_isMultiGraph)
    {
      int count = graphCount();

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "There are" << count << "graphs currently";

      double largestMaxYAxisValue = std::numeric_limits<double>::min();
      QCPGraph *p_foundGraph      = nullptr;

      for(int iter = 0; iter < count; ++iter)
        {
          QCPGraph *p_graph = graph(iter);

          double minimumY = 0;
          double maximumY = 0;

          yMinMaxOnXAxisCurrentRange(minimumY, maximumY, p_graph);

          if(maximumY > largestMaxYAxisValue)
            {
              largestMaxYAxisValue = maximumY;
              p_foundGraph         = p_graph;

              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "The winner graph is at index:" << iter;
            }
        }

      p_graphDataContainer = p_foundGraph->data();
    }
  else
    {
      if(g == Q_NULLPTR)
        p_graphDataContainer = graph()->data();
      else
        p_graphDataContainer = g->data();
    }

  double xAxisRangeLower = xAxis->range().lower;
  double xAxisRangeUpper = xAxis->range().upper;

  // qDebug() << __FILE__ << __LINE__
  // << "xAxisRangeLower:" << xAxisRangeLower << "xAxisRangeUpper: " <<
  // xAxisRangeUpper;

  double tempY = 0;
  double minY  = 0;
  double maxY  = 0;

  bool firstIteration = true;

  auto beginIt =
    p_graphDataContainer->findBegin(xAxisRangeLower, /*expandedRange*/ true);
  auto endIt =
    p_graphDataContainer->findEnd(xAxisRangeUpper, /*expandedRange*/ true);

  for(auto iter = beginIt; iter != endIt; ++iter)
    {
      tempY = iter->value;

      // qDebug() << __FILE__ << __LINE__
      // << "currentKey: " << currentKey << "tempY: " << tempY;

      if(firstIteration)
        {

          // This is the first iteration, use the values to seed the
          // comparison
          // system.

          minY = maxY    = tempY;
          firstIteration = false;
        }
      else
        {
          if(tempY > maxY)
            maxY = tempY;
          else if(tempY < minY)
            minY = tempY;
        }
    }

  min = minY;
  max = maxY;
}


//! Get the value corresponding to key \p k in graph \p g.
/*!

  The \p g pointer cannot be nullptr.

  \param k key value (x-axis value) for which to get the value (y-axis
  corresponding value).

  \param g pointer to the graph in which to peek for the value.

  \return double value containing the value corresponding to key \p k.

*/
double
AbstractPlotWidget::getYatX(double k, QCPGraph *g)
{
  if(g == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  QCPItemTracer tracer(this);
  tracer.setGraph(g);
  tracer.setInterpolating(true);
  tracer.setGraphKey(k);
  tracer.updatePosition();

  return tracer.position->value();
}


/*/js/
 * <PlotWidget>.getYatX(x)
 *
 * get the value at key x.
 *
 * x: <Number> holding the X-axis key for which the value is returned.
 */
//! Get the value corresponding to key \p k.
/*!

  The value is searched in the default graph of this plot widget: graph().

  \param k key value (x-axis value) for which to get the value (y-axis
  corresponding value).

  \return double value containing the value corresponding to key \p k.
  */
double
AbstractPlotWidget::getYatX(double x)
{
  return getYatX(x, graph());
}


//! Handle mouse movements, in particular record all the last visited points.
/*!

  This function is reponsible for storing at each time the last visited point
  in the graph. Here, point is intended as any x/y coordinate in the plot
  widget viewport, not a graph point.

  The stored values are then the basis for a large set of calculations
  throughout all the plot widget.

  \param pointer to QMouseEvent from which to retrieve the coordinates of the
  visited viewport points.
  */
void
AbstractPlotWidget::mouseMoveHandler(QMouseEvent *event)
{
  // Whatever happens, we want to store the plot coordinates of the current
  // mouse cursor position (will be useful of the label feature).
  QPointF mousePoint = event->localPos();

  m_lastMousedPlotPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
  m_lastMousedPlotPoint.setY(yAxis->pixelToCoord(mousePoint.y()));

  // qDebug() << __FILE__ << __LINE__
  // << "last moused plot point: (" << m_lastMousedPlotPoint.x()
  // << "," << m_lastMousedPlotPoint.y() << ")";

  if(event->buttons() != Qt::LeftButton && event->buttons() != Qt::RightButton)
    {
      m_isDragging = false;

      // We are not dragging the mouse (no button pressed), simply show the
      // x,y coordinates in the status bar.
      QString statusBarMsg = QString("(%1 , %2)")
                               .arg(m_lastMousedPlotPoint.x())
                               .arg(m_lastMousedPlotPoint.y());

      emit updateStatusBarSignal(statusBarMsg);

      // We are not dragging, so we only show the start tracer lines.
      m_endTracer->setVisible(false);

      // Only bother with the tracers if the user wants them to be visible.
      if(m_tracersVisible)
        {
          m_vStartTracer->setVisible(true);
          m_vStartTracer->start->setCoords(m_lastMousedPlotPoint.x(),
                                           yAxis->range().upper);

          m_vStartTracer->end->setCoords(m_lastMousedPlotPoint.x(),
                                         yAxis->range().lower);

          m_hStartTracer->setVisible(true);
          m_hStartTracer->start->setCoords(xAxis->range().lower,
                                           m_lastMousedPlotPoint.y());

          m_hStartTracer->end->setCoords(xAxis->range().upper,
                                         m_lastMousedPlotPoint.y());

          replot();
        }

      event->accept();

      return;
    }

  // If we are here, that means that one of the buttons was pressed, that is
  // we are dragging the mouse.
  //
  m_isDragging = true;

  // Now store the mouse position data into the the current drag point
  // member datum, that will be used in countless occasions later.
  m_currentDragPoint = m_lastMousedPlotPoint;

  // Now that we know the current position, we can compute the X-axis range:

  double tempXmin = m_startDragPoint.x();
  double tempXmax = m_currentDragPoint.x();

  // Compute the xAxis differential:

  m_xDelta = tempXmax - tempXmin;
  if(m_xDelta < 0)
    {
      m_xRangeMin = tempXmax;
      m_xRangeMax = tempXmin;
    }
  else
    {
      m_xRangeMin = tempXmin;
      m_xRangeMax = tempXmax;
    }

  // We do not need the delta sign anymore:
  m_xDelta = fabs(m_xDelta);

  // Same with the Y-axis range:

  double tempYmin = m_startDragPoint.y();
  double tempYmax = m_currentDragPoint.y();

  // Compute the yAxis differential:

  m_yDelta = tempYmax - tempYmin;
  if(m_yDelta < 0)
    {
      m_yRangeMin = tempYmax;
      m_yRangeMax = tempYmin;
    }
  else
    {
      m_yRangeMin = tempYmin;
      m_yRangeMax = tempYmax;
    }

  // We do not need the delta sign anymore:
  m_yDelta = fabs(m_yDelta);

  // When we drag, either right or left-button wise, we loose the
  // horizontal line of the start tracer.
  m_hStartTracer->setVisible(false);

  // We we left drag the mouse with the left button pressed, we show the
  // end tracer (it has only one vertical line).

  // Only bother with the tracers if the user wants them to be visible.
  if(m_tracersVisible)
    {
      m_endTracer->start->setCoords(m_currentDragPoint.x(),
                                    yAxis->range().upper);

      m_endTracer->end->setCoords(m_currentDragPoint.x(), yAxis->range().lower);

      m_endTracer->setVisible(true);
    }

  // Whatever the button, when we are dealing with the axes, we do not
  // want to show the tracers.
  if(m_clickWasOnXAxis || m_clickWasOnYAxis)
    {
      // We do not want to show the tracers when we are doing that axis drag
      // operation.
      m_hStartTracer->setVisible(false);
      m_vStartTracer->setVisible(false);
      m_endTracer->setVisible(false);

      // The update of the plot is automatically handled by the plot widget,
      // but we may want to propagate the axis range changes if we have locked
      // the x and/or y axis... When the parent window will emit the signal,
      // that signal will be caught up by all the receivers (plot widgets)
      // that will update the range synchronously if the user asked for
      // locking the X and/or Y axis range(s).
      dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd)
        ->emitReplotSignal(xAxis->range(), yAxis->range());
    }

  // Now deal with the BUTTON-SPECIFIC CODE.

  if(event->buttons() == Qt::LeftButton)
    {
      // There are two situations:
      //
      // 1. The drag operation is perfectly horizontal, in which case we are
      // measuring the distance between one point and another. Calculate the
      // distance and show it on the middle of the line.
      //
      // 2. The drag operation has a big vertical vector component, in which
      // case we are dragging a zoom rectangle. Draw that rectangle.

      if(m_clickWasOnXAxis || m_clickWasOnYAxis)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Click was on one of the axes, let the plot widget "
          // << "to the QCP:iRangeDrag stuff by itself.";

          // FIXME: it might be interesting to do that stuff ourselves, so
          // that
          // we can apply the same logic as the one we have with the
          // right-button drag X axis rescale with the shift modifier key
          // pressed.

          event->accept();

          return;
        }

      // Let's check if the user is actually drawing a rectangle (covering a
      // real area) or is drawing a line.

      if(isSelectionARectangle())
        {
          // When we draw a rectangle the tracers are of no use.
          m_hStartTracer->setVisible(false);
          m_vStartTracer->setVisible(false);
          m_endTracer->setVisible(false);

          drawRectangleAndZoom();
        }
      else
        {
          // Then, make sure the tracers are visible.
          m_hStartTracer->setVisible(true);
          m_vStartTracer->setVisible(true);
          m_endTracer->setVisible(true);

          drawXDeltaLineAndMeasure();
        }
    }
  // End of
  // if(event->buttons() == Qt::LeftButton)
  else if(event->buttons() == Qt::RightButton)
    {

      // There are two situations:
      //
      // 1. The user has clicked onto the X or Y axis. In that case, since
      // this is the right button, he want to actually scale that axis up or
      // down.
      //
      // 2. The user has clicked onto the graph. In that case, was is done is
      // a line drag for a mass spectrometry integration. What integration it
      // is will be known upon mouse release, because it will be defined by
      // looking at the pressed keyboard key. If no integration is asked that
      // line draw operation is nothing but a zoom operation with Y axis being
      // kept constant (unlike the left button-based zoom operation that draws
      // a rectangle).

      if(m_clickWasOnXAxis || m_clickWasOnYAxis)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Zoom on the clicked axis.";

          // This operation is particularly intensive, thus we want to reduce
          // the number of calculations by skipping this calculation a number
          // of
          // times. The user can ask for this feature by clicking the 'Q'
          // letter.
          if(m_pressedKeyCode == Qt::Key_Q)
            {

              if(m_mouseMoveHandlerSkipCount < m_mouseMoveHandlerSkipAmount)
                m_mouseMoveHandlerSkipCount++;
              else
                {
                  axisRescale();
                  m_mouseMoveHandlerSkipCount = 0;
                }
            }
          else
            {
              axisRescale();
            }
        }
      // End of
      // if(m_clickWasOnXAxis || m_clickWasOnYAxis)
      else
        {
          drawXDeltaLineForZoomOrIntegration();
          // qDebug() << __FILE__ << __LINE__
          // << "Line draw for integration.";
        }
    }
  // End of
  // else if(event->buttons() == Qt::RightButton)

  // qDebug() << __FILE__ << __LINE__
  // << "Dragging from" << m_startDragPoint.x() << "-" << m_startDragPoint.y()
  // << "to" << m_currentDragPoint.x() << "-" << m_currentDragPoint.y();
}


void
AbstractPlotWidget::selectionChangedHandler()
{
}


void
AbstractPlotWidget::setFocus()
{
  QCustomPlot::setFocus();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Setting focus to this plot widget:" << this
  //<< "With parent window:" << mp_parentWnd;

  // Inform the parent window that this is the last focused widget:
  AbstractMultiPlotWnd *parentWnd =
    dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd);
  if(parentWnd == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(parentWnd->mp_lastFocusedPlotWidget != this)
    {
      parentWnd->mp_lastFocusedPlotWidget = dynamic_cast<QWidget *>(this);
      parentWnd->emitRedrawPlotBackground(dynamic_cast<QWidget *>(this));
    }
}


//! Record the clicks of the mouse.
void
AbstractPlotWidget::mousePressHandler(QMouseEvent *event)
{
  setFocus();

  QPointF mousePoint = event->localPos();

  // Let's check if the click is on the axes, either X or Y, because that
  // will allow us to take proper actions.

  if(isClickOntoXAxis(mousePoint))
    {
      // The X axis was clicked upon, we need to document that:
      // qDebug() << __FILE__ << __LINE__
      //<< "Layout element is axisRect and actually on an X axis part.";

      m_clickWasOnXAxis = true;

      int currentInteractions = interactions();
      currentInteractions |= QCP::iRangeDrag;
      setInteractions((QCP::Interaction)currentInteractions);
      axisRect()->setRangeDrag(xAxis->orientation());
    }
  else
    m_clickWasOnXAxis = false;

  if(isClickOntoYAxis(mousePoint))
    {
      // The Y axis was clicked upon, we need to document that:
      // qDebug() << __FILE__ << __LINE__
      //<< "Layout element is axisRect and actually on an Y axis part.";

      m_clickWasOnYAxis = true;

      int currentInteractions = interactions();
      currentInteractions |= QCP::iRangeDrag;
      setInteractions((QCP::Interaction)currentInteractions);
      axisRect()->setRangeDrag(yAxis->orientation());
    }
  else
    m_clickWasOnYAxis = false;

  // At this point, let's see if we need to remove the QCP::iRangeDrag bit:

  if(!m_clickWasOnXAxis && !m_clickWasOnYAxis)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "Click outside of axes.";

      int currentInteractions = interactions();
      currentInteractions     = currentInteractions & ~QCP::iRangeDrag;
      setInteractions((QCP::Interaction)currentInteractions);
    }

  m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
  m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
}


//! Set the \p params MzIntegrationParams for this plot widget
void
AbstractPlotWidget::setMzIntegrationParams(MzIntegrationParams params)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "with params:" << params.asText();

  m_history.setMzIntegrationParams(params);
}


//! Get the MzIntegrationParams for this plot widget
MzIntegrationParams
AbstractPlotWidget::mzIntegrationParams() const
{
  return m_history.mzIntegrationParams();
}


void
AbstractPlotWidget::setMultiGraph(QCPGraph *graph)
{
  if(graph == nullptr)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  mp_multiGraph = graph;
}

//! React to the release of the mouse buttons.
void
AbstractPlotWidget::mouseReleaseHandler(QMouseEvent *event)
{
  if(!m_isDragging)
    {
      // Even if we are not dragging, if one of the registered keys is
      // pressed, then handle the case.
      if(m_regQtKeyCodeMap.contains(m_pressedKeyCode))
        {
          mouseReleaseHandledEvent(event);
          return;
        }

      // We only want to show the context menu if the button is the right
      // button.

      if(event->button() == Qt::RightButton)
        {

          // We only support data export on non-multi-graph plot widgets and
          // on
          // non-color-map widgets.

          if(m_isMultiGraph)
            {
              event->accept();
              return;
            }

          createContextMenu()->popup(QCursor::pos());

          event->accept();
          return;
        }
    }

  // We cannot hide all items in one go because we rely on their visibility
  // to know what kind of dragging operation we need to perform (line-only
  // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
  // only thing we know is that we can make the text invisible.

  if(m_xDeltaText->visible())
    m_xDeltaText->setVisible(false);

  // When we release the mouse button, whatever it was, we have the
  // horizontal line of the start tracer that is drawn again.

  // Only bother with the tracers if the user wants them to be visible.
  if(m_tracersVisible)
    {
      m_hStartTracer->setVisible(true);
    }

  // If we were using the "quantum" display for the rescale of the axes
  // using right-click drag on any axis, then reset the count to 0.
  m_mouseMoveHandlerSkipCount = 0;

  // Compute the delta values, X and Y, that correspond to the movement that
  // was done by the user while pressing the mouse button, that is get the
  // geometry of the drag movement.

  calculateSortedDragDeltas();

  // Now that we have computed the useful range, we need to check what to do
  // depending on the button that was pressed.
  if(event->button() == Qt::RightButton)
    {
      // Whatever we were doing (selection with a keyboard key pressed for an
      // integration operation or simple drag operation to zoom the
      // selection), we need to make the selection line invisible:
      if(m_selectLine->visible())
        m_selectLine->setVisible(false);

      // Now see what we need to do depending on the key pressed code or
      // nothing.
      if(m_regQtKeyCodeMap.contains(m_pressedKeyCode))
        {
          mouseReleaseHandledEvent(event);
        }
      else
        {
          // // There are two situations:
          // 1. either we are doing a zoom operation à
          // la mmmass, that is, dragging a line horizontally that will
          // delimit a
          // x-axis-only zoom operation;
          // 2. or we are terminating a x or y axis range expansion or
          // restriction.
          //
          // We can tell because the boolean values m_clickWasOnXAxis or
          // m_clickWasOnYAxis should be set if we were dragging on any of the
          // axes.

          if(m_clickWasOnXAxis || m_clickWasOnYAxis)
            {

              // We were doing an axis range operation which means we do not
              // need
              // to compute any thing, just let the system as is. This is
              // because
              // when the user clicks onto an axis and then right-button-drags
              // the
              // mouse, then the plot is update real-time, so we do not need
              // to
              // replot it, it is already plot fine. The only thing would be
              // to
              // change the Y axis range if the X axis was dragged-upon so as
              // to
              // maximise the zoom on the Y axis (à la mmass, in fact).

              if(m_clickWasOnXAxis)
                {

                  // We may either leave the scale of the Y axis as is
                  // (default)
                  // or
                  // the user may want an automatic scale of the Y axis such
                  // that
                  // the data displayed in the new X axis range are full scale
                  // on
                  // the Y axis. For this, the Shift modifier key should be
                  // pressed.

                  Qt::KeyboardModifiers modifiers =
                    QGuiApplication::queryKeyboardModifiers();

                  if(modifiers & Qt::ShiftModifier)
                    {
                      // In this case, we want to make a rescale of the Y axis
                      // such that
                      // it displays full scale the data in the current X axis
                      // range only.
                      double min = 0;
                      double max = 0;

                      yMinMaxOnXAxisCurrentRange(min, max);

                      yAxis->setRange(min, max);
                    }
                  // else
                  // do nothing, leave the Y axis scale as is.

                  // qDebug() << __FILE__ << __LINE__
                  // << "min: " << min << "max: " << max;
                }
            }
          else
            {

              // We were doing a mmass-like zoom operation, so modify the X axis
              // range accordingly. Then, set the Y axis full scale only for the
              // data in the X axis if the user has pressed the Shift modifier
              // key.

              xAxis->setRange(m_xRangeMin, m_xRangeMax);

              // We may either leave the scale of the Y axis as is (default) or
              // the user may want an automatic scale of the Y axis such that
              // the data displayed in the new X axis range are full scale on
              // the Y axis. For this, the Shift modifier key should be pressed.

              Qt::KeyboardModifiers modifiers =
                QGuiApplication::queryKeyboardModifiers();

              if(mp_colorMap == nullptr && modifiers & Qt::ShiftModifier)
                {

                  // In this case, we want to make a rescale of the Y axis such
                  // that it displays full scale the data in the current X axis
                  // range only.

                  double min = 0;
                  double max = 0;

                  yMinMaxOnXAxisCurrentRange(min, max);

                  yAxis->setRange(min, max);
                }
              else
                {

                  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
                  // If we do not want to rescale the Y axis to display the new
                  // X axis range data full scale, then this is the way to do
                  // it.  The Y axis scale is unchanged.

                  yAxis->setRange(yAxis->range().lower, m_yRangeMax);
                }
            }
        }

      // We have modified the ranges, thus store their new values in the
      // history.

      updateAxisRangeHistory();

      // Do not forget that we may have locked the x and/or y axis...
      dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd)
        ->emitReplotSignal(xAxis->range(), yAxis->range());
    }
  else if(event->button() == Qt::LeftButton)
    {
      if(m_clickWasOnXAxis || m_clickWasOnYAxis)
        {
          // The range is handled automatically by the plot widget, but we
          // want
          // to propagate to the other widgets the range change.
          // Do not forget that we may have locked the x and/or y axis...
          dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd)
            ->emitReplotSignal(xAxis->range(), yAxis->range());
        }

      // We were dragging with the left button pressed. Either we were
      // dragging to draw a rectangle, wishing to have a zoom operation
      // performed or we were dragging x-axis-wise-only to have a xDelta
      // measured.

      if(m_zoomRect->visible())
        {
          // We were selecting a region for a zoom operation.
          m_zoomRect->setVisible(false);

          xAxis->setRange(m_xRangeMin, m_xRangeMax);
          yAxis->setRange(m_yRangeMin, m_yRangeMax);

          updateAxisRangeHistory();

          // Do not forget that we may have locked the x and/or y axis...
          dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd)
            ->emitReplotSignal(xAxis->range(), yAxis->range());
        }
      else if(m_selectLine->visible())
        {
          // We were dragging x-axis-only-wise, to get a x-axis delta
          // measurement.
          m_selectLine->setVisible(false);

          // And then, also erase the xDeltaText itself.
          if(m_xDeltaText->visible())
            m_xDeltaText->setVisible(false);
        }
    }

  // By definition we are stopping the drag operation by releasing the mouse
  // button. Whatever that mouse button was pressed before and if there was
  // one pressed before.
  // We cannot set that boolean value to false before this place, because we
  // call a number of routines above that need to know that dragging was
  // occurring. Like mouseReleaseHandledEvent(event) for example.
  m_isDragging = false;

  // Replot because we want to make the graphical items invisible, be them
  // line or
  // rectangle.

  replot();

  event->accept();
}


//! React to the double-click on the x/y axes.
void
AbstractPlotWidget::axisDoubleClickHandler(QCPAxis *axis,
                                           QCPAxis::SelectablePart part,
                                           QMouseEvent *event)
{
  Qt::KeyboardModifiers modifiers = QGuiApplication::queryKeyboardModifiers();

  // If the Ctrl modifiers is active, then both axes are to be reset. Also
  // the histories are reset also.

  if(modifiers & Qt::ControlModifier)
    {
      xAxis->rescale();
      yAxis->rescale();

      clearAxisRangeHistory();
    }

  if(axis == xAxis)
    {
      // Reset the range of the x axis to the max view possible
      axis->rescale();
      // qDebug() << __FILE__ << __LINE__
      // << "X axis double click";
      replot();
    }
  else if(axis == yAxis)
    {
      // The code below is of no use for the colormap, worse, it is toxic
      // (yMinMaxOnXAxisCurrentRange call).
      if(mp_colorMap == nullptr && modifiers & Qt::ShiftModifier)
        {
          // The axis should be rescaled such that the view is maximised.
          double min = 0;
          double max = 0;

          yMinMaxOnXAxisCurrentRange(min, max);

          // Reset the range of the y axis to full scale in Y only for the
          // current
          // X axis range
          axis->setRange(min, max);
        }
      else
        // The axis should be rescaled to full scale of the whole plot, not
        // only the range.
        axis->rescale();
      // qDebug() << __FILE__ << __LINE__
      // << "Y axis double click";
      replot();
    }

  updateAxisRangeHistory();

  // Do not forget that we may have locked the x and/or y axis...
  dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd)
    ->emitReplotSignal(xAxis->range(), yAxis->range());

  event->accept();
}


//! Redraw the background of the \p focusedPlotWidget plot widget.
void
AbstractPlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
{
  if(focusedPlotWidget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
    {
      // The focused widget is not *this widget. We should make sure that
      // we were not the one that had the focus, because in this case we
      // need to redraw an unfocused background.

      axisRect()->setBackground(m_unfocusedBrush);
    }
  else
    {
      axisRect()->setBackground(m_focusedBrush);
    }

  replot();
}


} // namespace msXpSmineXpert
