/* This code comes right from the msXpertSuite software project.
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * 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
#include <vector>


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


/////////////////////// Local includes
#include "../../core/types.h"
#include "pappsomspp/core/utils.h"
#include "baseplotwidget.h"
#include "pappsomspp/core/pappsoexception.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"


int basePlotContextMetaTypeId =
  qRegisterMetaType<pappso::BasePlotContext>("pappso::BasePlotContext");
int basePlotContextPtrMetaTypeId =
  qRegisterMetaType<pappso::BasePlotContext *>("pappso::BasePlotContext *");

namespace pappso
{
BasePlotWidget::BasePlotWidget(QWidget *parent): QCustomPlot(parent)
{
  if(parent == nullptr)
    qFatal("Programming error.");

  // Default settings for the pen used to graph the data.
  m_pen.setStyle(Qt::SolidLine);
  m_pen.setBrush(Qt::black);
  m_pen.setWidth(1);

  // qDebug() << "Created new BasePlotWidget with" << layerCount()
  //<< "layers before setting up widget.";
  // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();

  // As of today 20210313, the QCustomPlot is created with the following 6
  // layers:
  //
  // All layers' name:
  //
  // Layer index 0 name: background
  // Layer index 1 name: grid
  // Layer index 2 name: main
  // Layer index 3 name: axes
  // Layer index 4 name: legend
  // Layer index 5 name: overlay

  if(!setupWidget())
    qFatal("Programming error.");

  // Do not call createAllAncillaryItems() in this base class because all the
  // items will have been created *before* the addition of plots and then the
  // rendering order will hide them to the viewer, since the rendering order is
  // according to the order in which the items have been created.
  //
  // The fact that the ancillary items are created before trace plots is not a
  // problem because the trace plots are sparse and do not effectively hide the
  // data.
  //
  // But, in the color map plot widgets, we cannot afford to create the
  // ancillary items *before* the plot itself because then, the rendering of the
  // plot (created after) would screen off the ancillary items (created before).
  //
  // So, the createAllAncillaryItems() function needs to be called in the
  // derived classes at the most appropriate moment in the setting up of the
  // widget.
  //
  // All this is only a workaround of a bug in QCustomPlot. See
  // https://www.qcustomplot.com/index.php/support/forum/2283.
  //
  // I initially wanted to have a plots layer on top of the default background
  // layer and a items layer on top of it. But that setting prevented the
  // selection of graphs.

  // qDebug() << "Created new BasePlotWidget with" << layerCount()
  //<< "layers after setting up widget.";
  // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();

  show();
}

BasePlotWidget::BasePlotWidget(QWidget *parent,
                               const QString &x_axis_label,
                               const QString &y_axis_label)
  : QCustomPlot(parent), m_axisLabelX(x_axis_label), m_axisLabelY(y_axis_label)
{
  // qDebug();

  if(parent == nullptr)
    qFatal("Programming error.");

  // Default settings for the pen used to graph the data.
  m_pen.setStyle(Qt::SolidLine);
  m_pen.setBrush(Qt::black);
  m_pen.setWidth(1);

  xAxis->setLabel(x_axis_label);
  yAxis->setLabel(y_axis_label);

  // qDebug() << "Created new BasePlotWidget with" << layerCount()
  //<< "layers before setting up widget.";
  // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();

  // As of today 20210313, the QCustomPlot is created with the following 6
  // layers:
  //
  // All layers' name:
  //
  // Layer index 0 name: background
  // Layer index 1 name: grid
  // Layer index 2 name: main
  // Layer index 3 name: axes
  // Layer index 4 name: legend
  // Layer index 5 name: overlay

  if(!setupWidget())
    qFatal("Programming error.");

  // qDebug() << "Created new BasePlotWidget with" << layerCount()
  //<< "layers after setting up widget.";
  // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();

  show();
}

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

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

*/
BasePlotWidget::~BasePlotWidget()
{
  // qDebug() << "In the destructor of plot widget:" << this;

  m_xAxisRangeHistory.clear();
  m_yAxisRangeHistory.clear();

  // Note that the QCustomPlot xxxItem objects are allocated with (this) which
  // means their destruction is automatically handled upon *this' destruction.
}

QString
BasePlotWidget::allLayerNamesToString() const
{

  QString text;

  for(int iter = 0; iter < layerCount(); ++iter)
    {
      text +=
        QString("Layer index %1: %2\n").arg(iter).arg(layer(iter)->name());
    }

  return text;
}

QString
BasePlotWidget::layerableLayerName(QCPLayerable *layerable_p) const
{
  if(layerable_p == nullptr)
    qFatal("Programming error.");

  QCPLayer *layer_p = layerable_p->layer();

  return layer_p->name();
}

int
BasePlotWidget::layerableLayerIndex(QCPLayerable *layerable_p) const
{
  if(layerable_p == nullptr)
    qFatal("Programming error.");

  QCPLayer *layer_p = layerable_p->layer();

  for(int iter = 0; iter < layerCount(); ++iter)
    {
      if(layer(iter) == layer_p)
        return iter;
    }

  return -1;
}

void
BasePlotWidget::createAllAncillaryItems()
{
  // Make a copy of the pen to just change its color and set that color to
  // the tracer line.
  QPen pen = m_pen;

  // Create the lines that will act as tracers for position and selection of
  // regions.
  //
  // We have the cross hair that serves as the cursor. That crosshair cursor is
  // made of a vertical line (green, because when click-dragging the mouse it
  // becomes the tracer that is being anchored at the region start. The second
  // line i horizontal and is always black.

  pen.setColor(QColor("steelblue"));

  // The set of tracers (horizontal and vertical) that track the position of the
  // mouse cursor.

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

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

  // The set of tracers (horizontal only) that track the region
  // spanning/selection regions.
  //
  // The start vertical tracer is colored in greeen.
  pen.setColor(QColor("green"));

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

  // The end vertical tracer is colored in red.
  pen.setColor(QColor("red"));

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

  // When the user click-drags the mouse, the X distance between the drag start
  // point and the drag end point (current point) is the xDelta.
  mp_xDeltaTextItem = new QCPItemText(this);
  mp_xDeltaTextItem->setLayer("plotsLayer");
  mp_xDeltaTextItem->setColor(QColor("steelblue"));
  mp_xDeltaTextItem->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
  mp_xDeltaTextItem->position->setType(QCPItemPosition::ptPlotCoords);
  mp_xDeltaTextItem->setVisible(false);

  // Same for the y delta
  mp_yDeltaTextItem = new QCPItemText(this);
  mp_yDeltaTextItem->setLayer("plotsLayer");
  mp_yDeltaTextItem->setColor(QColor("steelblue"));
  mp_yDeltaTextItem->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
  mp_yDeltaTextItem->position->setType(QCPItemPosition::ptPlotCoords);
  mp_yDeltaTextItem->setVisible(false);

  // Make sure we prepare the four lines that will be needed to
  // draw the selection rectangle.
  pen = m_pen;

  pen.setColor("steelblue");

  mp_selectionRectangeLine1 = new QCPItemLine(this);
  mp_selectionRectangeLine1->setLayer("plotsLayer");
  mp_selectionRectangeLine1->setPen(pen);
  mp_selectionRectangeLine1->start->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine1->end->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine1->start->setCoords(0, 0);
  mp_selectionRectangeLine1->end->setCoords(0, 0);
  mp_selectionRectangeLine1->setVisible(false);

  mp_selectionRectangeLine2 = new QCPItemLine(this);
  mp_selectionRectangeLine2->setLayer("plotsLayer");
  mp_selectionRectangeLine2->setPen(pen);
  mp_selectionRectangeLine2->start->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine2->end->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine2->start->setCoords(0, 0);
  mp_selectionRectangeLine2->end->setCoords(0, 0);
  mp_selectionRectangeLine2->setVisible(false);

  mp_selectionRectangeLine3 = new QCPItemLine(this);
  mp_selectionRectangeLine3->setLayer("plotsLayer");
  mp_selectionRectangeLine3->setPen(pen);
  mp_selectionRectangeLine3->start->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine3->end->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine3->start->setCoords(0, 0);
  mp_selectionRectangeLine3->end->setCoords(0, 0);
  mp_selectionRectangeLine3->setVisible(false);

  mp_selectionRectangeLine4 = new QCPItemLine(this);
  mp_selectionRectangeLine4->setLayer("plotsLayer");
  mp_selectionRectangeLine4->setPen(pen);
  mp_selectionRectangeLine4->start->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine4->end->setType(QCPItemPosition::ptPlotCoords);
  mp_selectionRectangeLine4->start->setCoords(0, 0);
  mp_selectionRectangeLine4->end->setCoords(0, 0);
  mp_selectionRectangeLine4->setVisible(false);
}

bool
BasePlotWidget::setupWidget()
{
  // qDebug();

  // By default the widget comes with a graph. Remove it.

  if(graphCount())
    {
      // QCPLayer *layer_p = graph(0)->layer();
      // qDebug() << "The graph was on layer:" << layer_p->name();

      // As of today 20210313, the graph is created on the currentLayer(), that
      // is "main".

      removeGraph(0);
    }

  // The general idea is that we do want custom layers for the trace|colormap
  // plots.

  // qDebug().noquote() << "Right before creating the new layer, layers:\n"
  //<< allLayerNamesToString();

  // Add the layer that will store all the plots and all the ancillary items.
  addLayer(
    "plotsLayer", layer("background"), QCustomPlot::LayerInsertMode::limAbove);
  // Add the layer that will store the labels.
  addLayer("labelsLayer", layer("background"), QCustomPlot::LayerInsertMode::limAbove);
  // qDebug().noquote() << "Added new plotsLayer, layers:\n"
  //<< allLayerNamesToString();

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

  // We want to capture the signals emitted by the QCustomPlot base class.
  connect(
    this, &QCustomPlot::mouseMove, this, &BasePlotWidget::mouseMoveHandler);

  connect(
    this, &QCustomPlot::mousePress, this, &BasePlotWidget::mousePressHandler);

  connect(this,
          &QCustomPlot::mouseRelease,
          this,
          &BasePlotWidget::mouseReleaseHandler);

  connect(
    this, &QCustomPlot::mouseWheel, this, &BasePlotWidget::mouseWheelHandler);

  connect(this,
          &QCustomPlot::axisDoubleClick,
          this,
          &BasePlotWidget::axisDoubleClickHandler);

  connect(this, &QCustomPlot::beforeReplot, this, [&]() { emit beforeReplotSignal(); });
  connect(this, &QCustomPlot::afterLayout, this, [&]() { emit afterLayoutSignal(); });
  connect(this, &QCustomPlot::afterReplot, this, [&]() { emit afterReplotSignal(); });

  return true;
}

void
BasePlotWidget::setPen(const QPen &pen)
{
  m_pen = pen;
}

const QPen &
BasePlotWidget::getPen() const
{
  return m_pen;
}

void
BasePlotWidget::setPlottingColor(QCPAbstractPlottable *plottable_p,
                                 const QColor &new_color)
{
  if(plottable_p == nullptr)
    qFatal("Pointer cannot be nullptr.");

  // First this single-graph widget
  QPen pen;

  pen = plottable_p->pen();
  pen.setColor(new_color);
  plottable_p->setPen(pen);

  replot();
}

void
BasePlotWidget::setPlottingColor(int index, const QColor &new_color)
{
  if(!new_color.isValid())
    return;

  QCPGraph *graph_p = graph(index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return setPlottingColor(graph_p, new_color);
}

QColor
BasePlotWidget::getPlottingColor(QCPAbstractPlottable *plottable_p) const
{
  if(plottable_p == nullptr)
    qFatal("Programming error.");

  return plottable_p->pen().color();
}

QColor
BasePlotWidget::getPlottingColor(int index) const
{
  QCPGraph *graph_p = graph(index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return getPlottingColor(graph_p);
}

void
BasePlotWidget::setAxisLabelX(const QString &label)
{
  xAxis->setLabel(label);
}

void
BasePlotWidget::setAxisLabelY(const QString &label)
{
  yAxis->setLabel(label);
}

// AXES RANGE HISTORY-related functions
void
BasePlotWidget::resetAxesRangeHistory()
{
  m_xAxisRangeHistory.clear();
  m_yAxisRangeHistory.clear();

  m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
  m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));

  // qDebug() << "size of history:" << m_xAxisRangeHistory.size()
  //<< "setting index to 0";

  // qDebug() << "resetting axes history to values:" << xAxis->range().lower
  //<< "--" << xAxis->range().upper << "and" << yAxis->range().lower
  //<< "--" << yAxis->range().upper;

  m_lastAxisRangeHistoryIndex = 0;
}

//! 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
BasePlotWidget::updateAxesRangeHistory()
{
  m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
  m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));

  m_lastAxisRangeHistoryIndex = m_xAxisRangeHistory.size() - 1;

  // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
  //<< "current index:" << m_lastAxisRangeHistoryIndex
  //<< xAxis->range().lower << "--" << xAxis->range().upper << "and"
  //<< yAxis->range().lower << "--" << yAxis->range().upper;
}

//! 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
BasePlotWidget::restorePreviousAxesRangeHistory()
{
  // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
  //<< "current index:" << m_lastAxisRangeHistoryIndex;

  if(m_lastAxisRangeHistoryIndex == 0)
    {
      // qDebug() << "current index is 0 returning doing nothing";

      return;
    }

  // qDebug() << "Setting index to:" << m_lastAxisRangeHistoryIndex - 1
  //<< "and restoring axes history to that index";

  restoreAxesRangeHistory(--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 updateAxesRangeHistory().

*/
void
BasePlotWidget::restoreAxesRangeHistory(std::size_t index)
{
  // qDebug() << "Axes history size:" << m_xAxisRangeHistory.size()
  //<< "current index:" << m_lastAxisRangeHistoryIndex
  //<< "asking to restore index:" << index;

  if(index >= m_xAxisRangeHistory.size())
    {
      // qDebug() << "index >= history size. Returning.";
      return;
    }

  // We want to go back to the range history item at index, which means we want
  // to pop back all the items between index+1 and size-1.

  while(m_xAxisRangeHistory.size() > index + 1)
    m_xAxisRangeHistory.pop_back();

  if(m_xAxisRangeHistory.size() - 1 != index)
    qFatal("Programming error.");

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

  hideAllPlotItems();

  mp_vPosTracerItem->setVisible(false);
  mp_hPosTracerItem->setVisible(false);

  mp_vStartTracerItem->setVisible(false);
  mp_vEndTracerItem->setVisible(false);


  // The start tracer will keep beeing represented at the last position and last
  // size even if we call this function repetitively. So actually do not show,
  // it will reappare as soon as the mouse is moved.
  // if(m_shouldTracersBeVisible)
  //{
  // mp_vStartTracerItem->setVisible(true);
  //}

  replot();

  updateContextXandYAxisRanges();

  // qDebug() << "restored axes history to index:" << index
  //<< "with values:" << xAxis->range().lower << "--"
  //<< xAxis->range().upper << "and" << yAxis->range().lower << "--"
  //<< yAxis->range().upper;

  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
}

// AXES RANGE HISTORY-related functions


/// KEYBOARD-related EVENTS
void
BasePlotWidget::keyPressEvent(QKeyEvent *event)
{
  // qDebug() << "ENTER";

  // We need this because some keys modify our behaviour.
  m_context.m_pressedKeyCode    = event->key();
  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();

  if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
     event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
    {
      return directionKeyPressEvent(event);
    }
  else if(event->key() == m_leftMousePseudoButtonKey ||
          event->key() == m_rightMousePseudoButtonKey)
    {
      return mousePseudoButtonKeyPressEvent(event);
    }

  // Do not do anything here, because this function is used by derived classes
  // that will emit the signal below. Otherwise there are going to be multiple
  // signals sent.
  // qDebug() << "Going to emit keyPressEventSignal(m_context);";
  // emit keyPressEventSignal(m_context);
}

//! Handle specific key codes and trigger respective actions.
void
BasePlotWidget::keyReleaseEvent(QKeyEvent *event)
{
  m_context.m_releasedKeyCode = event->key();

  // The keyboard key is being released, set the key code to 0.
  m_context.m_pressedKeyCode = 0;

  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();

  // Now test if the key that was released is one of the housekeeping keys.
  if(event->key() == Qt::Key_Backspace)
    {
      // qDebug();

      // The user wants to iterate back in the x/y axis range history.
      restorePreviousAxesRangeHistory();

      event->accept();
    }
  else if(event->key() == Qt::Key_Space)
    {
      return spaceKeyReleaseEvent(event);
    }
  else if(event->key() == Qt::Key_Delete)
    {
      // The user wants to delete a graph. What graph is to be determined
      // programmatically:

      // If there is a single graph, then that is the graph to be removed.
      // If there are more than one graph, then only the ones that are selected
      // are to be removed.

      // Note that the user of this widget might want to provide the user with
      // the ability to specify if all the children graph needs to be removed
      // also. This can be coded in key modifiers. So provide the context.

      int graph_count = plottableCount();

      if(!graph_count)
        {
          // qDebug() << "Not a single graph in the plot widget. Doing
          // nothing.";

          event->accept();
          return;
        }

      if(graph_count == 1)
        {
          // qDebug() << "A single graph is in the plot widget. Emitting a graph
          // " "destruction requested signal for it:"
          //<< graph();

          emit plottableDestructionRequestedSignal(this, graph(), m_context);
        }
      else
        {
          // At this point we know there are more than one graph in the plot
          // widget. We need to get the selected one (if any).
          QList<QCPGraph *> selected_graph_list;

          selected_graph_list = selectedGraphs();

          if(!selected_graph_list.size())
            {
              event->accept();
              return;
            }

          // qDebug() << "Number of selected graphs to be destrobyed:"
          //<< selected_graph_list.size();

          for(int iter = 0; iter < selected_graph_list.size(); ++iter)
            {
              // qDebug()
              //<< "Emitting a graph destruction requested signal for graph:"
              //<< selected_graph_list.at(iter);

              emit plottableDestructionRequestedSignal(
                this, selected_graph_list.at(iter), m_context);

              // We do not do this, because we want the slot called by the
              // signal above to handle that removal. Remember that it is not
              // possible to delete graphs manually.
              //
              // removeGraph(selected_graph_list.at(iter));
            }
          event->accept();
        }
    }
  // End of
  // else if(event->key() == Qt::Key_Delete)
  else if(event->key() == Qt::Key_T)
    {
      // The user wants to toggle the visibiity of the tracers.
      m_shouldTracersBeVisible = !m_shouldTracersBeVisible;

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

      event->accept();
    }
  else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
          event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
    {
      return directionKeyReleaseEvent(event);
    }
  else if(event->key() == m_leftMousePseudoButtonKey ||
          event->key() == m_rightMousePseudoButtonKey)
    {
      return mousePseudoButtonKeyReleaseEvent(event);
    }
  else if(event->key() == Qt::Key_S)
    {
      // The user is defining the size of the rhomboid fixed side. That could be
      // either a vertical side (less intuitive) or a horizontal size (more
      // intuitive, first exclusive implementation). But, in order to be able to
      // perform identical integrations starting from non-transposed color maps
      // and transposed color maps, the ability to define a vertical fixed size
      // side of the rhomboid integration scope has become necessary.

      // Check if the vertical displacement is significant (>= 10% of the color
      // map height.

      if(isVerticalDisplacementAboveThreshold())
        {
          // The user is dragging the cursor vertically in a sufficient delta to
          // consider that they are willing to define a vertical fixed size
          // of the rhomboid integration scope.

          m_context.m_integrationScopeRhombWidth  = 0;
          m_context.m_integrationScopeRhombHeight = abs(
            m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y());

          // qDebug() << "Set m_context.m_integrationScopePolyHeight to"
          //          << m_context.m_integrationScopeRhombHeight
          //          << "upon release of S key";
        }
      else
        {
          // The user is dragging the cursor horiontally  to define a horizontal
          // fixed size of the rhomboid integration scope.

          m_context.m_integrationScopeRhombWidth = abs(
            m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x());
          m_context.m_integrationScopeRhombHeight = 0;

          // qDebug() << "Set m_context.m_integrationScopePolyWidth to"
          //          << m_context.m_integrationScopeRhombWidth
          //          << "upon release of S key";
        }
    }
  // At this point emit the signal, since we did not treat it. Maybe the
  // consumer widget wants to know that the keyboard key was released.

  emit keyReleaseEventSignal(m_context);
}

void
BasePlotWidget::spaceKeyReleaseEvent([[maybe_unused]] QKeyEvent *event)
{
  // qDebug();
}

void
BasePlotWidget::directionKeyPressEvent(QKeyEvent *event)
{
  // qDebug() << "event key:" << event->key();

  // The user is trying to move the positional cursor/markers. There are
  // multiple way they can do that:
  //
  // 1.a. Hitting the arrow left/right keys alone will search for next pixel.
  // 1.b. Hitting the arrow left/right keys with Alt modifier will search for
  // a multiple of pixels that might be equivalent to one 20th of the pixel
  // width of the plot widget. 1.c Hitting the left/right keys with Alt and
  // Shift modifiers will search for a multiple of pixels that might be the
  // equivalent to half of the pixel width.
  //
  // 2. Hitting the Control modifier will move the cursor to the next data
  // point of the graph.

  int pixel_increment = 0;

  if(m_context.m_keyboardModifiers == Qt::NoModifier)
    pixel_increment = 1;
  else if(m_context.m_keyboardModifiers == Qt::AltModifier)
    pixel_increment = 50;

  // The user is moving the positional markers. This is equivalent to a
  // non-dragging cursor movement to the next pixel. Note that the origin is
  // located at the top left, so key down increments and key up decrements.

  if(event->key() == Qt::Key_Left)
    horizontalMoveMouseCursorCountPixels(-pixel_increment);
  else if(event->key() == Qt::Key_Right)
    horizontalMoveMouseCursorCountPixels(pixel_increment);
  else if(event->key() == Qt::Key_Up)
    verticalMoveMouseCursorCountPixels(-pixel_increment);
  else if(event->key() == Qt::Key_Down)
    verticalMoveMouseCursorCountPixels(pixel_increment);

  event->accept();
}

void
BasePlotWidget::directionKeyReleaseEvent(QKeyEvent *event)
{
  // qDebug() << "event key:" << event->key();
  event->accept();
}

void
BasePlotWidget::mousePseudoButtonKeyPressEvent(
  [[maybe_unused]] QKeyEvent *event)
{
  // qDebug();
}

void
BasePlotWidget::mousePseudoButtonKeyReleaseEvent(QKeyEvent *event)
{

  QPointF pixel_coordinates(
    xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
    yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));

  Qt::MouseButton button    = Qt::NoButton;
  QEvent::Type q_event_type = QEvent::MouseButtonPress;

  if(event->key() == m_leftMousePseudoButtonKey)
    {
      // Toggles the left mouse button on/off

      button = Qt::LeftButton;

      m_context.m_isLeftPseudoButtonKeyPressed =
        !m_context.m_isLeftPseudoButtonKeyPressed;

      if(m_context.m_isLeftPseudoButtonKeyPressed)
        q_event_type = QEvent::MouseButtonPress;
      else
        q_event_type = QEvent::MouseButtonRelease;
    }
  else if(event->key() == m_rightMousePseudoButtonKey)
    {
      // Toggles the right mouse button.

      button = Qt::RightButton;

      m_context.m_isRightPseudoButtonKeyPressed =
        !m_context.m_isRightPseudoButtonKeyPressed;

      if(m_context.m_isRightPseudoButtonKeyPressed)
        q_event_type = QEvent::MouseButtonPress;
      else
        q_event_type = QEvent::MouseButtonRelease;
    }

  // qDebug() << "pressed/released pseudo button:" << button
  //<< "q_event_type:" << q_event_type;

  // Synthesize a QMouseEvent and use it.

  QMouseEvent *mouse_event_p =
    new QMouseEvent(q_event_type,
                    pixel_coordinates,
                    mapToGlobal(pixel_coordinates.toPoint()),
                    mapToGlobal(pixel_coordinates.toPoint()),
                    button,
                    button,
                    m_context.m_keyboardModifiers,
                    Qt::MouseEventSynthesizedByApplication);

  if(q_event_type == QEvent::MouseButtonPress)
    mousePressHandler(mouse_event_p);
  else
    mouseReleaseHandler(mouse_event_p);

  delete mouse_event_p;
  // event->accept();
}

/// KEYBOARD-related EVENTS


/// MOUSE-related EVENTS

void
BasePlotWidget::mouseMoveHandler(QMouseEvent *event)
{

  // If we have no focus, then get it. See setFocus() to understand why asking
  // for focus is cosly and thus why we want to make this decision first.
  if(!hasFocus())
    setFocus();

  // qDebug() << (graph() != nullptr);
  // if(graph(0) != nullptr)
  //  { // check if the widget contains some graphs

  // The event->button() must be by Qt instructions considered to be 0.

  // Whatever happens, we want to store the plot coordinates of the current
  // mouse cursor position (will be useful later for countless needs).

  QPointF mousePoint = event->position();

  // qDebug() << "local mousePoint position in pixels:" << mousePoint;

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

  // qDebug() << "lastCursorHoveredPoint coord:"
  //<< m_context.m_lastCursorHoveredPoint;

  // Now, depending on the button(s) (if any) that are pressed or not, we
  // have a different processing.

  // qDebug();

  if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
     m_context.m_pressedMouseButtons & Qt::RightButton)
    mouseMoveHandlerDraggingCursor(event);
  else
    mouseMoveHandlerNotDraggingCursor(event);
  //  }
  // qDebug();
  event->accept();
}

void
BasePlotWidget::mouseMoveHandlerNotDraggingCursor(QMouseEvent *event)
{
  Q_UNUSED(event)

  // qDebug();
  m_context.m_isMouseDragging = false;

  // qDebug();
  // We are not dragging the mouse (no button pressed), simply let this
  // widget's consumer know the position of the cursor and update the markers.
  // The consumer of this widget will update mouse cursor position at
  // m_context.m_lastCursorHoveredPoint if so needed.

  emit lastCursorHoveredPointSignal(m_context.m_lastCursorHoveredPoint);

  // qDebug();

  // We are not dragging, so we do not show the region end tracer we only
  // show the anchoring start trace that might be of use if the user starts
  // using the arrow keys to move the cursor.
  if(mp_vEndTracerItem != nullptr)
    mp_vEndTracerItem->setVisible(false);

  // qDebug();
  // Only bother with the tracers if the user wants them to be visible.
  // Their crossing point must be exactly at the last cursor-hovered point.

  if(m_shouldTracersBeVisible)
    {
      // We are not dragging, so only show the position markers (v and h);

      // qDebug();
      if(mp_hPosTracerItem != nullptr)
        {
          // Horizontal position tracer.
          mp_hPosTracerItem->setVisible(true);
          mp_hPosTracerItem->start->setCoords(
            xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
          mp_hPosTracerItem->end->setCoords(
            xAxis->range().upper, m_context.m_lastCursorHoveredPoint.y());
        }

      // qDebug();
      // Vertical position tracer.
      if(mp_vPosTracerItem != nullptr)
        {
          mp_vPosTracerItem->setVisible(true);

          mp_vPosTracerItem->setVisible(true);
          mp_vPosTracerItem->start->setCoords(
            m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
          mp_vPosTracerItem->end->setCoords(
            m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
        }

      // qDebug();
      replot();
    }


  return;
}

void
BasePlotWidget::mouseMoveHandlerDraggingCursor(QMouseEvent *event)
{
  // qDebug();

  m_context.m_isMouseDragging = true;

  // Now store the mouse position data into the the current drag point
  // member datum, that will be used in countless occasions later.
  m_context.m_currentDragPoint  = m_context.m_lastCursorHoveredPoint;
  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();

  // When we drag (either keyboard or mouse), we hide the position markers
  // (black) and we show the start and end vertical markers for the region.
  // Then, we draw the horizontal region range marker that delimits
  // horizontally the dragged-over region.

  if(mp_hPosTracerItem != nullptr)
    mp_hPosTracerItem->setVisible(false);
  if(mp_vPosTracerItem != nullptr)
    mp_vPosTracerItem->setVisible(false);

  // Only bother with the tracers if the user wants them to be visible.
  if(m_shouldTracersBeVisible && (mp_vEndTracerItem != nullptr))
    {

      // The vertical end tracer position must be refreshed.
      mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
                                          yAxis->range().upper);

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

      mp_vEndTracerItem->setVisible(true);
    }

  // Whatever the button, when we are dealing with the axes, we do not
  // want to show any of the tracers.

  if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
    {
      if(mp_hPosTracerItem != nullptr)
        mp_hPosTracerItem->setVisible(false);
      if(mp_vPosTracerItem != nullptr)
        mp_vPosTracerItem->setVisible(false);

      if(mp_vStartTracerItem != nullptr)
        mp_vStartTracerItem->setVisible(false);
      if(mp_vEndTracerItem != nullptr)
        mp_vEndTracerItem->setVisible(false);
    }
  else
    {
      // qDebug() << "Not moving the mouse cursor over any of the axes.";

      // Since we are not dragging the mouse cursor over the axes, make sure
      // we store the drag directions in the context, as this might be
      // useful for later operations.
      // qDebug() << "Recording the drag direction(s).";

      m_context.recordDragDirections();

      // qDebug() << "Drag direction(s): " <<
      // m_context.dragDirectionsToString();
    }

  // Because when we drag the mouse button (whatever the button) we need to
  // know what is the drag delta (distance between start point and current
  // point of the drag operation) on both axes, ask that these x|y deltas be
  // computed.
  calculateDragDeltas();

  // Now deal with the BUTTON-SPECIFIC CODE.

  if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
    {
      return mouseMoveHandlerLeftButtonDraggingCursor(event);
    }
  else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
    {
      return mouseMoveHandlerRightButtonDraggingCursor(event);
    }
}

void
BasePlotWidget::mouseMoveHandlerLeftButtonDraggingCursor(QMouseEvent *event)
{
  Q_UNUSED(event)

  // qDebug() << "The left button is dragging.";

  // Set the context.m_isMeasuringDistance to false, which later might be set
  // to true if effectively we are measuring a distance. This is required
  // because the derived widget classes might want to know if they have to
  // perform some action on the basis that context is measuring a distance,
  // for example the mass spectrum-specific widget might want to compute
  // deconvolutions.

  m_context.m_isMeasuringDistance = false;

  // Let's first check if the mouse drag operation originated on either
  // axis. In that case, the user is performing axis reframing or rescaling.

  if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
    {
      // qDebug() << "Click was on one of the axes.";

      if(m_context.m_keyboardModifiers & Qt::ControlModifier)
        {
          // The user is asking a rescale of the plot.

          // We know that we do not want the tracers when we perform axis
          // rescaling operations.

          if(mp_hPosTracerItem != nullptr)
            mp_hPosTracerItem->setVisible(false);
          if(mp_vPosTracerItem != nullptr)
            mp_vPosTracerItem->setVisible(false);

          if(mp_vStartTracerItem != nullptr)
            mp_vStartTracerItem->setVisible(false);
          if(mp_vEndTracerItem != nullptr)
            mp_vEndTracerItem->setVisible(false);

          // 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_context.m_pressedKeyCode == Qt::Key_Q)
            {
              if(m_mouseMoveHandlerSkipCount < m_mouseMoveHandlerSkipAmount)
                {
                  m_mouseMoveHandlerSkipCount++;
                  return;
                }
              else
                {
                  m_mouseMoveHandlerSkipCount = 0;
                }
            }

          // qDebug() << "Asking that the axes be rescaled.";

          axisRescale();
        }
      else
        {
          // The user was simply dragging the axis. Just pan, that is slide
          // the plot in the same direction as the mouse movement and with the
          // same amplitude.

          // qDebug() << "Asking that the axes be panned.";

          axisPan();
        }

      return;
    }

  // At this point we understand that the user was not performing any
  // panning/rescaling operation by clicking on any one of the axes.. Go on
  // with other possibilities.

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

  // qDebug() << "The mouse dragging did not originate on an axis.";

  if(isVerticalDisplacementAboveThreshold())
    {
      // qDebug() << "Apparently the selection is two-dimensional.";

      // When we draw a two-dimensional integration scope, the tracers are of no
      // use.

      if(mp_hPosTracerItem != nullptr)
        mp_hPosTracerItem->setVisible(false);
      if(mp_vPosTracerItem != nullptr)
        mp_vPosTracerItem->setVisible(false);

      if(mp_vStartTracerItem != nullptr)
        mp_vStartTracerItem->setVisible(false);
      if(mp_vEndTracerItem != nullptr)
        mp_vEndTracerItem->setVisible(false);

      // Draw the rectangle,  false, not as line segment and
      // false, not for integration
      drawSelectionRectangleAndPrepareZoom(false /*as_line_segment*/,
                                           false /* for_integration*/);

      // Draw the selection width/height text
      drawXScopeSpanFeatures();
      drawYScopeSpanFeatures();
    }
  else
    {
      // qDebug() << "Apparently we are measuring a delta.";

      // Draw the rectangle, true, as line segment and
      // false, not for integration
      drawSelectionRectangleAndPrepareZoom(true, false);

      // The pure position tracers should be hidden.
      if(mp_hPosTracerItem != nullptr)
        mp_hPosTracerItem->setVisible(true);
      if(mp_vPosTracerItem != nullptr)
        mp_vPosTracerItem->setVisible(true);

      // Then, make sure the region range vertical tracers are visible.
      if(mp_vStartTracerItem != nullptr)
        mp_vStartTracerItem->setVisible(true);
      if(mp_vEndTracerItem != nullptr)
        mp_vEndTracerItem->setVisible(true);

      // Draw the selection width text
      drawXScopeSpanFeatures();
    }
}

void
BasePlotWidget::mouseMoveHandlerRightButtonDraggingCursor(QMouseEvent *event)
{
  Q_UNUSED(event)
  // qDebug() << "The right button is dragging.";

  // Set the context.m_isMeasuringDistance to false, which later might be set
  // to true if effectively we are measuring a distance. This is required
  // because the derived widgets might want to know if they have to perform
  // some action on the basis that context is measuring a distance, for
  // example the mass spectrum-specific widget might want to compute
  // deconvolutions.

  m_context.m_isMeasuringDistance = false;

  if(isVerticalDisplacementAboveThreshold())
    {
      // qDebug() << "Apparently the selection has height.";

      // When we draw a rectangle the tracers are of no use.

      if(mp_hPosTracerItem != nullptr)
        mp_hPosTracerItem->setVisible(false);
      if(mp_vPosTracerItem != nullptr)
        mp_vPosTracerItem->setVisible(false);

      if(mp_vStartTracerItem != nullptr)
        mp_vStartTracerItem->setVisible(false);
      if(mp_vEndTracerItem != nullptr)
        mp_vEndTracerItem->setVisible(false);

      // Draw the rectangle, false for as_line_segment and true for
      // integration.
      drawSelectionRectangleAndPrepareZoom(false, true);

      // Draw the selection width/height text
      drawXScopeSpanFeatures();
      drawYScopeSpanFeatures();
    }
  else
    {
      // qDebug() << "Apparently the selection is a not a rectangle.";

      // Draw the rectangle, true as line segment and
      // true for integration
      drawSelectionRectangleAndPrepareZoom(true, true);

      // Draw the selection width text
      drawXScopeSpanFeatures();
    }
}

void
BasePlotWidget::mousePressHandler(QMouseEvent *event)
{
  // qDebug() << "Entering";

  // When the user clicks this widget it has to take focus.
  setFocus();

  QPointF mousePoint = event->position();

  m_context.m_lastPressedMouseButton   = event->button();
  m_context.m_mouseButtonsAtMousePress = event->buttons();

  // The pressedMouseButtons must continually inform on the status of
  // pressed buttons so add the pressed button.
  m_context.m_pressedMouseButtons |= event->button();

  // qDebug().noquote() << m_context.toString();

  // In all the processing of the events, we need to know if the user is
  // clicking somewhere with the intent to change the plot ranges (reframing
  // or rescaling the plot).
  //
  // Reframing the plot means that the new x and y axes ranges are modified
  // so that they match the region that the user has encompassed by left
  // clicking the mouse and dragging it over the plot. That is we reframe
  // the plot so that it contains only the "selected" region.
  //
  // Rescaling the plot means the the new x|y axis range is modified such
  // that the lower axis range is constant and the upper axis range is moved
  // either left or right by the same amont as the x|y delta encompassed by
  // the user moving the mouse. The axis is thus either compressed (mouse
  // movement is leftwards) or un-compressed (mouse movement is rightwards).

  // There are two ways to perform axis range modifications:
  //
  // 1. By clicking on any of the axes
  // 2. By clicking on the plot region but using keyboard key modifiers,
  // like Alt and Ctrl.
  //
  // We need to know both cases separately which is why we need to perform a
  // number of tests below.

  // 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_context.m_wasClickOnXAxis = true;

      // int currentInteractions = interactions();
      // currentInteractions |= QCP::iRangeDrag;
      // setInteractions((QCP::Interaction)currentInteractions);
      // axisRect()->setRangeDrag(xAxis->orientation());
    }
  else
    m_context.m_wasClickOnXAxis = 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_context.m_wasClickOnYAxis = true;

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

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

  if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "Click outside of axes.";

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

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

  // Now install the vertical start tracer at the last cursor hovered
  // position.
  if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
    mp_vStartTracerItem->setVisible(true);

  if(mp_vStartTracerItem != nullptr)
    {
      mp_vStartTracerItem->start->setCoords(
        m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
      mp_vStartTracerItem->end->setCoords(
        m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
    }

  replot();

  emit mousePressEventSignal(event, m_context);

  // qDebug() << "Exiting after having emitted mousePressEventSignal with base
  // context:"
  // << m_context.toString();
}

void
BasePlotWidget::mouseReleaseHandler(QMouseEvent *event)
{
  // qDebug() << "Entering";

  // Now the real code of this function.

  m_context.m_lastReleasedMouseButton = event->button();

  // The event->buttons() is the description of the buttons that are pressed
  // at the moment the handler is invoked, that is now. If left and right were
  // pressed, and left was released, event->buttons() would be right.
  m_context.m_mouseButtonsAtMouseRelease = event->buttons();

  // The pressedMouseButtons must continually inform on the status of pressed
  // buttons so remove the released button.
  m_context.m_pressedMouseButtons ^= event->button();

  // qDebug().noquote() << m_context.toString();

  // We'll need to know if modifiers were pressed a the moment the user
  // released the mouse button.
  m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();

  if(!m_context.m_isMouseDragging)
    {
      // Let the user know that the mouse was *not* being dragged.
      m_context.m_wasMouseDragging = false;

      event->accept();

      return;
    }

  // Let the user know that the mouse was being dragged.
  m_context.m_wasMouseDragging = true;

  // 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.

  // Same for the x delta text item
  mp_xDeltaTextItem->setVisible(false);
  mp_yDeltaTextItem->setVisible(false);

  // We do not show the end vertical region range marker.
  mp_vEndTracerItem->setVisible(false);

  // Horizontal position tracer.
  mp_hPosTracerItem->setVisible(true);
  mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
                                      m_context.m_lastCursorHoveredPoint.y());
  mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
                                    m_context.m_lastCursorHoveredPoint.y());

  // Vertical position tracer.
  mp_vPosTracerItem->setVisible(true);

  mp_vPosTracerItem->setVisible(true);
  mp_vPosTracerItem->start->setCoords(m_context.m_lastCursorHoveredPoint.x(),
                                      yAxis->range().upper);
  mp_vPosTracerItem->end->setCoords(m_context.m_lastCursorHoveredPoint.x(),
                                    yAxis->range().lower);

  // Force replot now because later that call might not be performed.
  replot();

  // If we were using the "quantum" display for the rescale of the axes
  // using the Ctrl-modified left button click drag in the axes, then reset
  // the count to 0.
  m_mouseMoveHandlerSkipCount = 0;

  // 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_context.m_isMouseDragging = false;

  // Now that we have computed the useful ranges, we need to check what to do
  // depending on the button that was pressed.

  if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
    {
      return mouseReleaseHandlerLeftButton(event);
    }
  else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
    {
      return mouseReleaseHandlerRightButton(event);
    }

  // FIXME: should we really accept ? No, since we pass the event on.
  // event->accept();

  // Before returning, emit the signal for the user of
  // this class consumption.
  // qDebug() << "Emitting mouseReleaseEventSignal.";
  emit mouseReleaseEventSignal(event, m_context);

  // qDebug() << "Exiting after having emitted mouseReleaseEventSignal with base
  // context:"
  //          << m_context.toString();

  return;
}

void
BasePlotWidget::mouseReleaseHandlerLeftButton(QMouseEvent *event)
{
  Q_UNUSED(event)
  // qDebug();

  if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
    {

      // When the mouse move handler pans the plot, we cannot store each axes
      // range history element that would mean store a huge amount of such
      // elements, as many element as there are mouse move event handled by
      // the Qt event queue. But we can store an axis range history element
      // for the last situation of the mouse move: when the button is
      // released:

      updateAxesRangeHistory();

      // qDebug() << "emit plotRangesChangedSignal(m_context);"

      emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

      replot();

      // Nothing else to do.
      return;
    }

  // There are two possibilities:
  //
  // 1. The full integration scope (four lines) were currently drawn, which
  // means the user was willing to perform a zoom operation.
  //
  // 2. Only the first top line was drawn, which means the user was dragging
  // the cursor horizontally. That might have two ends, as shown below.

  // So, first check what is drawn of the selection polygon.

  SelectionDrawingLines selection_drawing_lines =
    whatIsVisibleOfTheSelectionRectangle();

  // Now that we know what was currently drawn of the selection polygon, we
  // can remove it.  true to reset the values to 0.
  hideSelectionRectangle(true);

  // Force replot now because later that call might not be performed.
  replot();

  if(selection_drawing_lines == SelectionDrawingLines::FULL_POLYGON)
    {
      // qDebug() << "Yes, the full polygon was visible";

      // If we were dragging with the left button pressed and could draw a
      // rectangle, then we were preparing a zoom operation. Let's bring that
      // operation to its accomplishment.

      axisZoom();

      return;
    }
  else if(selection_drawing_lines == SelectionDrawingLines::TOP_LINE)
    {
      // qDebug() << "No, only the top line of the full polygon was visible";

      // The user was dragging the left mouse cursor and that may mean they
      // were measuring a distance or willing to perform a special zoom
      // operation if the Ctrl key was down.

      // If the user started by clicking in the plot region, dragged the mouse
      // cursor with the left button and pressed the Ctrl modifier, then that
      // means that they wanted to do a rescale over the x-axis in the form of
      // a reframing.

      if(m_context.m_keyboardModifiers & Qt::ControlModifier)
        {
          return axisReframe();
        }
    }
  // else
  // qDebug() << "Another possibility.";
}

void
BasePlotWidget::mouseReleaseHandlerRightButton(QMouseEvent *event)
{
  Q_UNUSED(event)
  // qDebug();
  // The right button is used for the integrations. Not for axis range
  // operations. So all we have to do is remove the various graphics items and
  // send a signal with the context that contains all the data required by the
  // user to perform the integrations over the right plot regions.

  // Whatever we were doing we need to make the selection line invisible:

  if(mp_xDeltaTextItem->visible())
    mp_xDeltaTextItem->setVisible(false);
  if(mp_yDeltaTextItem->visible())
    mp_yDeltaTextItem->setVisible(false);

  // Also make the vertical end tracer invisible.
  mp_vEndTracerItem->setVisible(false);

  // Once the integration is asked for, then the selection rectangle if of no
  // more use.
  hideSelectionRectangle();

  // Force replot now because later that call might not be performed.
  replot();

  // Note that we only request an integration if the x-axis delta is enough.

  double x_delta_pixel =
    fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
         xAxis->coordToPixel(m_context.m_startDragPoint.x()));

  if(x_delta_pixel > 3)
    {
      // qDebug() << "Emitting integrationRequestedSignal(m_context)";
      emit integrationRequestedSignal(m_context);
    }
  // else
  // qDebug() << "Not asking for integration.";
}

void
BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
{
  // We should record the new range values each time the wheel is used to
  // zoom/unzoom.

  m_context.m_xRange = QCPRange(xAxis->range());
  m_context.m_yRange = QCPRange(yAxis->range());

  // qDebug() << "New x range: " << m_context.m_xRange;
  // qDebug() << "New y range: " << m_context.m_yRange;

  updateAxesRangeHistory();

  emit plotRangesChangedWheelEventSignal(event, m_context);
  emit mouseWheelEventSignal(event, m_context);

  event->accept();
}

void
BasePlotWidget::axisDoubleClickHandler(
  QCPAxis *axis,
  [[maybe_unused]] QCPAxis::SelectablePart part,
  QMouseEvent *event)
{
  // qDebug();

  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();

  if(m_context.m_keyboardModifiers & Qt::ControlModifier)
    {
      // qDebug();

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

      rescaleAxes();
      resetAxesRangeHistory();
    }
  else
    {
      // qDebug();

      // Only the axis passed as parameter is to be rescaled.
      // Reset the range of that axis to the max view possible.

      axis->rescale();

      updateAxesRangeHistory();

      event->accept();
    }

  // The double-click event does not cancel the mouse press event. That is, if
  // left-double-clicking, at the end of the operation the button still
  // "pressed". We need to remove manually the button from the pressed buttons
  // context member.

  m_context.m_pressedMouseButtons ^= event->button();

  updateContextXandYAxisRanges();

  emit plotRangesChangedSignal(event, m_context);

  replot();
}

bool
BasePlotWidget::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;
}

bool
BasePlotWidget::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;
}

/// MOUSE-related EVENTS


/// MOUSE MOVEMENTS mouse/keyboard-triggered

int
BasePlotWidget::dragDirection()
{
  // The user is dragging the mouse, probably to rescale the axes, but we need
  // to sort out in which direction the drag is happening.

  // This function should be called after calculateDragDeltas, so that
  // m_context has the proper x/y delta values that we'll compare.

  // Note that we cannot compare simply x or y deltas because the y axis might
  // have a different scale that the x axis. So we first need to convert the
  // positions to pixels.

  double x_delta_pixel =
    fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
         xAxis->coordToPixel(m_context.m_startDragPoint.x()));

  double y_delta_pixel =
    fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
         yAxis->coordToPixel(m_context.m_startDragPoint.y()));

  if(x_delta_pixel > y_delta_pixel)
    return Qt::Horizontal;

  return Qt::Vertical;
}

void
BasePlotWidget::moveMouseCursorGraphCoordToGlobal(QPointF graph_coordinates)
{
  // First convert the graph coordinates to pixel coordinates.

  QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
                             yAxis->coordToPixel(graph_coordinates.y()));

  moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
}

void
BasePlotWidget::moveMouseCursorPixelCoordToGlobal(QPointF pixel_coordinates)
{
  // qDebug() << "Calling set pos with new cursor position.";
  QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
}

void
BasePlotWidget::horizontalMoveMouseCursorCountPixels(int pixel_count)
{
  QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);

  QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
                      yAxis->coordToPixel(graph_coord.y()));

  // Now we need ton convert the new coordinates to the global position system
  // and to move the cursor to that new position. That will create an event to
  // move the mouse cursor.

  moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
}

QPointF
BasePlotWidget::horizontalGetGraphCoordNewPointCountPixels(int pixel_count)
{
  QPointF pixel_coordinates(
    xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
    yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));

  // Now convert back to local coordinates.

  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
                            yAxis->pixelToCoord(pixel_coordinates.y()));

  return graph_coordinates;
}

void
BasePlotWidget::verticalMoveMouseCursorCountPixels(int pixel_count)
{

  QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);

  QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
                      yAxis->coordToPixel(graph_coord.y()));

  // Now we need ton convert the new coordinates to the global position system
  // and to move the cursor to that new position. That will create an event to
  // move the mouse cursor.

  moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
}

QPointF
BasePlotWidget::verticalGetGraphCoordNewPointCountPixels(int pixel_count)
{
  QPointF pixel_coordinates(
    xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
    yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);

  // Now convert back to local coordinates.

  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
                            yAxis->pixelToCoord(pixel_coordinates.y()));

  return graph_coordinates;
}

/// MOUSE MOVEMENTS mouse/keyboard-triggered


/// RANGE-related functions

QCPRange
BasePlotWidget::getRangeX(bool &found_range, int index) const
{
  QCPGraph *graph_p = graph(index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return graph_p->getKeyRange(found_range);
}

QCPRange
BasePlotWidget::getRangeY(bool &found_range, int index) const
{
  QCPGraph *graph_p = graph(index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return graph_p->getValueRange(found_range);
}

QCPRange
BasePlotWidget::getRange(Enums::Axis axis,
                         RangeType range_type,
                         bool &found_range) const
{

  // Iterate in all the graphs in this widget and return a QCPRange that has
  // its lower member as the greatest lower value of all
  // its upper member as the smallest upper value of all

  if(!graphCount())
    {
      found_range = false;

      return QCPRange(0, 1);
    }

  if(graphCount() == 1)
    return graph()->getKeyRange(found_range);

  bool found_at_least_one_range = false;

  // Create an invalid range.
  QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);

  for(int iter = 0; iter < graphCount(); ++iter)
    {
      QCPRange temp_range;

      bool found_range_for_iter = false;

      QCPGraph *graph_p = graph(iter);

      // Depending on the axis param, select the key or value range.

      if(axis == Enums::Axis::x)
        temp_range = graph_p->getKeyRange(found_range_for_iter);
      else if(axis == Enums::Axis::y)
        temp_range = graph_p->getValueRange(found_range_for_iter);
      else
        qFatal("Cannot reach this point. Programming error.");

      // Was a range found for the iterated graph ? If not skip this
      // iteration.

      if(!found_range_for_iter)
        continue;

      // While the innermost_range is invalid, we need to seed it with a good
      // one. So check this.

      if(!QCPRange::validRange(result_range))
        qFatal("The obtained range is invalid !");

      // At this point we know the obtained range is OK.
      result_range = temp_range;

      // We found at least one valid range!
      found_at_least_one_range = true;

      // At this point we have two valid ranges to compare. Depending on
      // range_type, we need to perform distinct comparisons.

      if(range_type == RangeType::innermost)
        {
          if(temp_range.lower > result_range.lower)
            result_range.lower = temp_range.lower;
          if(temp_range.upper < result_range.upper)
            result_range.upper = temp_range.upper;
        }
      else if(range_type == RangeType::outermost)
        {
          if(temp_range.lower < result_range.lower)
            result_range.lower = temp_range.lower;
          if(temp_range.upper > result_range.upper)
            result_range.upper = temp_range.upper;
        }
      else
        qFatal("Cannot reach this point. Programming error.");

      // Continue to next graph, if any.
    }
  // End of
  // for(int iter = 0; iter < graphCount(); ++iter)

  // Let the caller know if we found at least one range.
  found_range = found_at_least_one_range;

  return result_range;
}

QCPRange
BasePlotWidget::getInnermostRangeX(bool &found_range) const
{

  return getRange(Enums::Axis::x, RangeType::innermost, found_range);
}

QCPRange
BasePlotWidget::getOutermostRangeX(bool &found_range) const
{
  return getRange(Enums::Axis::x, RangeType::outermost, found_range);
}

QCPRange
BasePlotWidget::getInnermostRangeY(bool &found_range) const
{

  return getRange(Enums::Axis::y, RangeType::innermost, found_range);
}

QCPRange
BasePlotWidget::getOutermostRangeY(bool &found_range) const
{
  return getRange(Enums::Axis::y, RangeType::outermost, found_range);
}

/// RANGE-related functions


/// PLOTTING / REPLOTTING functions

void
BasePlotWidget::axisRescale()
{
  // Get the current x lower/upper range, that is, leftmost/rightmost x
  // coordinate.
  double xLower = xAxis->range().lower;
  double xUpper = xAxis->range().upper;

  // Get the current y lower/upper range, that is, bottommost/topmost y
  // coordinate.
  double yLower = yAxis->range().lower;
  double yUpper = yAxis->range().upper;

  // This function is called only when the user has clicked on the x/y axis or
  // when the user has dragged the left mouse button with the Ctrl key
  // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
  // move handler. So we need to test which axis was clicked-on.

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

      // If xDelta is < 0, then we were dragging from right to left, we are
      // compressing the view on the x axis, by adding new data to the right
      // hand size of the graph. So we add xDelta to the upper bound of the
      // range. Otherwise we are uncompressing the view on the x axis and
      // remove the xDelta from the upper bound of the range. This is why we
      // have the
      // '-'
      // and not '+' below;

      xAxis->setRange(xLower, xUpper - m_context.m_xDelta);
    }
  // End of
  // if(m_context.m_wasClickOnXAxis)
  else // that is, if(m_context.m_wasClickOnYAxis)
    {
      // We are changing the range of the Y axis.

      // See above for an explanation of the computation (the - sign below).

      yAxis->setRange(yLower, yUpper - m_context.m_yDelta);
    }
  // End of
  // else // that is, if(m_context.m_wasClickOnYAxis)

  // Update the context with the current axes ranges

  updateContextXandYAxisRanges();

  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BasePlotWidget::axisReframe()
{

  // double sorted_start_drag_point_x =
  // std::min(m_context.m_startDragPoint.x(),
  // m_context.m_currentDragPoint.x());

  // xAxis->setRange(sorted_start_drag_point_x,
  // sorted_start_drag_point_x + fabs(m_context.m_xDelta));

  xAxis->setRange(
    QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeEnd));

  // Note that the y axis should be rescaled from current lower value to new
  // upper value matching the y-axis position of the cursor when the mouse
  // button was released.

  yAxis->setRange(xAxis->range().lower,
                  std::max<double>(m_context.m_yRegionRangeStart,
                                   m_context.m_yRegionRangeEnd));

  // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
  // xAxis->range().upper
  //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;

  updateContextXandYAxisRanges();

  updateAxesRangeHistory();
  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BasePlotWidget::axisZoom()
{

  // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
  // values before using them, because now we want to really have the lower x
  // value. Simply craft a QCPRange that will swap the values if lower is not
  // < than upper QCustomPlot calls this normalization).

  xAxis->setRange(
    QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeEnd));

  yAxis->setRange(
    QCPRange(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeEnd));

  updateContextXandYAxisRanges();

  updateAxesRangeHistory();
  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BasePlotWidget::axisPan()
{
  // Sanity check
  if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
    qFatal(
      "This function can only be called if the mouse click was on one of the "
      "axes");

  if(m_context.m_wasClickOnXAxis)
    {
      xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
                      m_context.m_xRange.upper - m_context.m_xDelta);
    }

  if(m_context.m_wasClickOnYAxis)
    {
      yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
                      m_context.m_yRange.upper - m_context.m_yDelta);
    }

  updateContextXandYAxisRanges();

  // qDebug() << "The updated context:" << m_context.toString();

  // We cannot store the new ranges in the history, because the pan operation
  // involved a huge quantity of micro-movements elicited upon each mouse move
  // cursor event so we would have a huge history.
  // updateAxesRangeHistory();

  // Now that the context has the right range values, we can emit the
  // signal that will be used by this plot widget users, typically to
  // abide by the x/y range lock required by the user.

  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BasePlotWidget::replotWithAxesRanges(QCPRange xAxisRange,
                                     QCPRange yAxisRange,
                                     Enums::Axis axis)
{
  // qDebug() << "With axis:" << (int)axis;

  if(static_cast<int>(axis) & static_cast<int>(Enums::Axis::x))
    {
      xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
    }

  if(static_cast<int>(axis) & static_cast<int>(Enums::Axis::y))
    {
      yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
    }

  // 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.
  // updateAxesRangeHistory();

  replot();
}

void
BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
{
  // qDebug();

  xAxis->setRange(lower, upper);

  replot();
}

void
BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
{
  // qDebug();

  yAxis->setRange(lower, upper);

  replot();
}

/// PLOTTING / REPLOTTING functions


/// PLOT ITEMS : TRACER TEXT ITEMS...

//! Hide the selection line, the xDelta text and the zoom rectangle items.
void
BasePlotWidget::hideAllPlotItems()
{
  mp_xDeltaTextItem->setVisible(false);
  mp_yDeltaTextItem->setVisible(false);

  // mp_zoomRectItem->setVisible(false);
  hideSelectionRectangle();

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

//! Show the traces (vertical and horizontal).
void
BasePlotWidget::showTracers()
{
  m_shouldTracersBeVisible = true;

  mp_vPosTracerItem->setVisible(true);
  mp_hPosTracerItem->setVisible(true);

  mp_vStartTracerItem->setVisible(true);
  mp_vEndTracerItem->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
BasePlotWidget::hideTracers()
{
  m_shouldTracersBeVisible = false;
  mp_hPosTracerItem->setVisible(false);
  mp_vPosTracerItem->setVisible(false);

  mp_vStartTracerItem->setVisible(false);
  mp_vEndTracerItem->setVisible(false);

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

void
BasePlotWidget::drawSelectionRectangleAndPrepareZoom(bool as_line_segment,
                                                     bool for_integration)
{
  // The user has dragged the mouse left button on the graph, which means he
  // is willing to draw a selection rectangle, either for zooming-in or for
  // integration.

  if(mp_xDeltaTextItem != nullptr)
    mp_xDeltaTextItem->setVisible(false);
  if(mp_yDeltaTextItem != nullptr)
    mp_yDeltaTextItem->setVisible(false);

  // Ensure the right selection rectangle is drawn.

  updateIntegrationScopeDrawing(as_line_segment, for_integration);

  // Note that if we draw a zoom rectangle, then we are certainly not
  // measuring anything. So set the boolean value to false so that the user of
  // this widget or derived classes know that there is nothing to perform upon
  // (like deconvolution, for example).

  m_context.m_isMeasuringDistance = false;

  // Also remove the delta value from the pipeline by sending a simple
  // distance without measurement signal.

  emit xAxisMeasurementSignal(m_context, false);

  replot();
}

void
BasePlotWidget::drawXScopeSpanFeatures()
{
  // Depending on the kind of integration scope, we will have to display
  // differently calculated values. We want to provide the user with
  // the horizontal span of the integration scope. There are different
  // situations.

  // 1. The scope is mono-dimensional across the x axis: the span
  // is thus simply the width.

  // 2. The scope is bi-dimensional and is a rectangle: the span is
  // thus simply the width.

  // 3. The socpe is bi-dimensional and is a rhomboid: the span is
  // the width.

  // In the first and second cases above, the width is equal to the
  // m_context.m_xDelta.

  // In the case of the rhomboid, the span is not m_context.m_xDelta,
  // it is more than that if the rhomboid is horizontal because it is
  // the m_context.m_xDelta plus the rhomboid's horizontal size.

  // FIXME: is this still true?
  //
  // We do not want to show the position markers because the only horiontal
  // line to be visible must be contained between the start and end vertical
  // tracer items.
  mp_hPosTracerItem->setVisible(false);
  mp_vPosTracerItem->setVisible(false);

  // We want to draw the text in the middle position of the leftmost-rightmost
  // point, even with rhomboid scopes.

  QPointF leftmost_point;
  if(!m_context.msp_integrationScope->getLeftMostPoint(leftmost_point))
    qFatal("Could not get the left-most point.");

  double width;
  if(!m_context.msp_integrationScope->getWidth(width))
    qFatal("Could not get width.");
  // qDebug() << "width:" << width;

  double x_axis_center_position = leftmost_point.x() + width / 2;

  // We want the text to print inside the rectangle, always at the current
  // drag point so the eye can follow the delta value while looking where to
  // drag the mouse. To position the text inside the rectangle, we need to
  // know what is the drag direction.

  // What is the distance between the rectangle line at current drag point and
  // the text itself. Think of this as a margin distance between the
  // point of interest and the actual position of the text.
  int pixels_away_from_line = 15;

  QPointF reference_point_for_y_axis_label_position;

  // ATTENTION: the pixel coordinates for the vertical direction go in reverse
  // order with respect to the y axis values !!! That is, pixel(0,0) is top
  // left of the graph.
  if(static_cast<int>(m_context.m_dragDirections) &
     static_cast<int>(DragDirections::BOTTOM_TO_TOP))
    {
      // We need to print outside the rectangle, that is pixels_away_from_line
      // pixels to the top, so with pixel y value decremented of that
      // pixels_above_line value (one would have expected to increment that
      // value, along the y axis, but the coordinates in pixel go in reverse
      // order).

      pixels_away_from_line *= -1;

      if(!m_context.msp_integrationScope->getTopMostPoint(
           reference_point_for_y_axis_label_position))
        qFatal("Failed to get top most point.");
    }
  else
    {
      if(!m_context.msp_integrationScope->getBottomMostPoint(
           reference_point_for_y_axis_label_position))
        qFatal("Failed to get bottom most point.");
    }

  // double y_axis_pixel_coordinate =
  // yAxis->coordToPixel(m_context.m_currentDragPoint.y());
  double y_axis_pixel_coordinate =
    yAxis->coordToPixel(reference_point_for_y_axis_label_position.y());

  // Now that we have the coordinate in pixel units, we can correct
  // it by the value of the margin we want to give.
  double y_axis_modified_pixel_coordinate =
    y_axis_pixel_coordinate + pixels_away_from_line;

  // Set aside a point instance to store the pixel coordinates of the text.
  QPointF pixel_coordinates;

  pixel_coordinates.setX(x_axis_center_position);
  pixel_coordinates.setY(y_axis_modified_pixel_coordinate);

  // Now convert back to graph coordinates.
  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
                            yAxis->pixelToCoord(pixel_coordinates.y()));

  // qDebug() << "Should print the label at point:" << graph_coordinates;

  if(mp_xDeltaTextItem != nullptr)
    {
      mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
                                             graph_coordinates.y());

      // Dynamically set the number of decimals to ensure we can read
      // a meaning full delta value even if it is very very very small.
      // That is, allow one to read 0.00333, 0.000333, 1.333 and so on.

      // The computation below only works properly when the passed
      // value is fabs() (not negative !!!).

      int decimals = Utils::zeroDecimalsInValue(width) + 3;

      QString label_text = QString("full x span %1 -- x drag delta %2")
                             .arg(width, 0, 'f', decimals)
                             .arg(fabs(m_context.m_xDelta), 0, 'f', decimals);

      mp_xDeltaTextItem->setText(label_text);

      mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
      mp_xDeltaTextItem->setVisible(true);
    }

  // Set the boolean to true so that derived widgets know that something is
  // being measured, and they can act accordingly, for example by computing
  // deconvolutions in a mass spectrum.
  m_context.m_isMeasuringDistance = true;

  replot();

  // Let the caller know that we were measuring something.
  emit xAxisMeasurementSignal(m_context, true);

  return;
}

void
BasePlotWidget::drawYScopeSpanFeatures()
{
  // See drawXScopeSpanFeatures() for explanations.

  // Check right away if there is height!
  double height;
  if(!m_context.msp_integrationScope->getHeight(height))
    qFatal("Could not get height.");

  // If there is no height, we have nothing to do here.
  if(!height)
    return;
  // qDebug() << "height:" << height;

  // FIXME: is this still true?
  //
  // We do not want to show the position markers because the only horiontal
  // line to be visible must be contained between the start and end vertical
  // tracer items.
  mp_hPosTracerItem->setVisible(false);
  mp_vPosTracerItem->setVisible(false);

  // First the easy part: the vertical position: centered on the
  // scope Y span.
  QPointF bottom_most_point;
  if(!m_context.msp_integrationScope->getBottomMostPoint(bottom_most_point))
    qFatal("Could not get the bottom-most bottom point.");

  double y_axis_center_position = bottom_most_point.y() + height / 2;

  // We want to draw the text outside the rectangle (if normal rectangle)
  // at a small distance from the vertical limit of the scope at the
  // position of the current drag point. We need to check the horizontal
  // drag direction to put the text at the right place (left of
  // current drag point if dragging right to left, for example).

  // What is the distance between the rectangle line at current drag point and
  // the text itself.
  int pixels_away_from_line = 15;
  double x_axis_coordinate;
  double x_axis_pixel_coordinate;

  if(static_cast<int>(m_context.m_dragDirections) &
     static_cast<int>(DragDirections::RIGHT_TO_LEFT))
    {
      QPointF left_most_point;

      if(!m_context.msp_integrationScope->getLeftMostPoint(left_most_point))
        qFatal("Failed to get left most point.");

      x_axis_coordinate = left_most_point.x();

      pixels_away_from_line *= -1;
    }
  else
    {
      QPointF right_most_point;

      if(!m_context.msp_integrationScope->getRightMostPoint(right_most_point))
        qFatal("Failed to get right most point.");

      x_axis_coordinate = right_most_point.x();
    }
  x_axis_pixel_coordinate = xAxis->coordToPixel(x_axis_coordinate);

  double x_axis_modified_pixel_coordinate =
    x_axis_pixel_coordinate + pixels_away_from_line;

  // Set aside a point instance to store the pixel coordinates of the text.
  QPointF pixel_coordinates;

  pixel_coordinates.setX(x_axis_modified_pixel_coordinate);
  pixel_coordinates.setY(y_axis_center_position);

  // Now convert back to graph coordinates.

  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
                            yAxis->pixelToCoord(pixel_coordinates.y()));

  mp_yDeltaTextItem->position->setCoords(graph_coordinates.x(),
                                         y_axis_center_position);

  int decimals = Utils::zeroDecimalsInValue(height) + 3;

  QString label_text = QString("full y span %1 -- y drag delta %2")
                         .arg(height, 0, 'f', decimals)
                         .arg(fabs(m_context.m_yDelta), 0, 'f', decimals);

  mp_yDeltaTextItem->setText(label_text);
  mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
  mp_yDeltaTextItem->setVisible(true);
  mp_yDeltaTextItem->setRotation(90);

  // Set the boolean to true so that derived widgets know that something is
  // being measured, and they can act accordingly, for example by computing
  // deconvolutions in a mass spectrum.
  m_context.m_isMeasuringDistance = true;

  replot();

  // Let the caller know that we were measuring something.
  emit xAxisMeasurementSignal(m_context, true);
}

void
BasePlotWidget::calculateDragDeltas()
{

  // We compute signed differentials. If the user does not want the sign,
  // fabs(double) is their friend.

  // Compute the xAxis differential:

  m_context.m_xDelta =
    m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x();

  // Same with the Y-axis range:

  m_context.m_yDelta =
    m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y();

  return;
}

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

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

  double heightDiffRatio = (heightDiff / plotHeight) * 100;

  if(heightDiffRatio > 10)
    {
      return true;
    }

  return false;
}

void
BasePlotWidget::updateIntegrationScope(bool for_integration)
{

  // if(for_integration)
  // qDebug() << "for_integration:" << for_integration;

  // By essence, the one-dimension IntegrationScope is characterized
  // by the left-most point and the width. Using these two data bits
  // it is possible to compute the x value of the right-most point.

  double x_range_start =
    std::min(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
  double x_range_end =
    std::max(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());

  // qDebug() << "x_range_start:" << x_range_start << "-" << "x_range_end:" <<
  // x_range_end;

  double y_position = m_context.m_startDragPoint.y();

  m_context.updateIntegrationScope();

  // Top line
  mp_selectionRectangeLine1->start->setCoords(
    QPointF(x_range_start, y_position));
  mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));

  // Only if we are drawing a selection rectangle for integration, do we set
  // arrow heads to the line.
  if(for_integration)
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
    }
  else
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
    }
  mp_selectionRectangeLine1->setVisible(true);

  // Right line: does not exist, start and end are the same end point of the
  // top line.
  mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
  mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
  mp_selectionRectangeLine2->setVisible(false);

  // Bottom line: identical to the top line, but invisible
  mp_selectionRectangeLine3->start->setCoords(
    QPointF(x_range_start, y_position));
  mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
  mp_selectionRectangeLine3->setVisible(false);

  // Left line: does not exist: start and end are the same end point of the
  // top line.
  mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
  mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
  mp_selectionRectangeLine4->setVisible(false);
}

void
BasePlotWidget::updateIntegrationScopeRect(bool for_integration)
{
  // qDebug();

  // if(for_integration)
  // qDebug() << "for_integration:" << for_integration;

  // We are handling a conventional rectangle. Just create four points
  // from top left to bottom right. But we want the top left point to be
  // effectively the top left point and the bottom point to be the bottom
  // point. So we need to try all four direction combinations, left to right
  // or converse versus top to bottom or converse.

  m_context.updateIntegrationScopeRect();

  // Now that the integration scope has been updated as a rectangle,
  // use these newly set data to actually draw the integration
  // scope lines.

  QPointF bottom_left_point;
  if(!m_context.msp_integrationScope->getPoint(bottom_left_point))
    qFatal("Failed to get point.");
  // qDebug() << "Starting point is left bottom point:" << bottom_left_point;

  double width;
  if(!m_context.msp_integrationScope->getWidth(width))
    qFatal("Failed to get width.");
  // qDebug() << "Width:" << width;

  double height;
  if(!m_context.msp_integrationScope->getHeight(height))
    qFatal("Failed to get height.");
  // qDebug() << "Height:" << height;

  QPointF bottom_right_point(bottom_left_point.x() + width,
                             bottom_left_point.y());
  // qDebug() << "bottom_right_point:" << bottom_right_point;

  QPointF top_right_point(bottom_left_point.x() + width,
                          bottom_left_point.y() + height);
  // qDebug() << "top_right_point:" << top_right_point;

  QPointF top_left_point(bottom_left_point.x(), bottom_left_point.y() + height);

  // qDebug() << "top_left_point:" << top_left_point;

  // Start by drawing the bottom line because the IntegrationScopeRect has the
  // left bottom point and the width and the height to fully characterize it.

  // Bottom line (left to right)
  mp_selectionRectangeLine3->start->setCoords(bottom_left_point);
  mp_selectionRectangeLine3->end->setCoords(bottom_right_point);
  mp_selectionRectangeLine3->setVisible(true);

  // Right line (bottom to top)
  mp_selectionRectangeLine2->start->setCoords(bottom_right_point);
  mp_selectionRectangeLine2->end->setCoords(top_right_point);
  mp_selectionRectangeLine2->setVisible(true);

  // Top line (right to left)
  mp_selectionRectangeLine1->start->setCoords(top_right_point);
  mp_selectionRectangeLine1->end->setCoords(top_left_point);
  mp_selectionRectangeLine1->setVisible(true);

  // Left line (top to bottom)
  mp_selectionRectangeLine4->start->setCoords(top_left_point);
  mp_selectionRectangeLine4->end->setCoords(bottom_left_point);
  mp_selectionRectangeLine4->setVisible(true);

  // Only if we are drawing a selection rectangle for integration, do we
  // set arrow heads to the line.
  if(for_integration)
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
    }
  else
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
    }
}

void
BasePlotWidget::updateIntegrationScopeHorizontalRhomb(bool for_integration)
{
  // We are handling a rhomboid scope, that is, a rectangle that
  // is tilted either to the left or to the right.

  // There are two kinds of rhomboid integration scopes: horizontal and
  // vertical.

  /*
   *                            +----------+
   *                            |          |
   *                           |          |
   *                          |          |
   *                         |          |
   *                        |          |
   *                       |          |
   *                      |          |
   *                      +----------+
   *                      ----width---
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *horizontal* side (this is the plot context's
  // m_integrationScopeRhombWidth).

  IntegrationScopeFeatures scope_features;

  // Top horizontal line
  QPointF point_1;
  scope_features = m_context.msp_integrationScope->getLeftMostTopPoint(point_1);

  // When the user rotates the horizontal rhomboid, at some point, if the
  // current drag point has the same y axis value as the start drag point, then
  // we say that the rhomboid is flattened on the x axis. In this case, we do
  // not draw anything as this is a purely unusable situation.

  if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
    {
      // qDebug() << "The horizontal rhomboid is flattened on the x axis.";

      mp_selectionRectangeLine1->setVisible(false);
      mp_selectionRectangeLine2->setVisible(false);
      mp_selectionRectangeLine3->setVisible(false);
      mp_selectionRectangeLine4->setVisible(false);

      return;
    }

  if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    qFatal("The rhomboid should be horizontal!");

  // At this point we can draw the rhomboid fine.

  if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
    qFatal("Failed to getLeftMostTopPoint.");
  QPointF point_2;
  if(!m_context.msp_integrationScope->getRightMostTopPoint(point_2))
    qFatal("Failed to getRightMostTopPoint.");

  // qDebug() << "For top line, two points:" << point_1 << "--" << point_2;

  mp_selectionRectangeLine1->start->setCoords(point_1);
  mp_selectionRectangeLine1->end->setCoords(point_2);

  // Only if we are drawing a selection rectangle for integration, do we set
  // arrow heads to the line.
  if(for_integration)
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
    }
  else
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
    }

  mp_selectionRectangeLine1->setVisible(true);

  // Right line
  if(!m_context.msp_integrationScope->getRightMostBottomPoint(point_1))
    qFatal("Failed to getRightMostBottomPoint.");
  mp_selectionRectangeLine2->start->setCoords(point_2);
  mp_selectionRectangeLine2->end->setCoords(point_1);
  mp_selectionRectangeLine2->setVisible(true);

  // qDebug() << "For right line, two points:" << point_2 << "--" << point_1;

  // Bottom horizontal line
  if(!m_context.msp_integrationScope->getLeftMostBottomPoint(point_2))
    qFatal("Failed to getLeftMostBottomPoint.");
  mp_selectionRectangeLine3->start->setCoords(point_1);
  mp_selectionRectangeLine3->end->setCoords(point_2);
  mp_selectionRectangeLine3->setVisible(true);

  // qDebug() << "For bottom line, two points:" << point_1 << "--" << point_2;

  // Left line
  if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
    qFatal("Failed to getLeftMostTopPoint.");
  mp_selectionRectangeLine4->end->setCoords(point_2);
  mp_selectionRectangeLine4->start->setCoords(point_1);
  mp_selectionRectangeLine4->setVisible(true);

  // qDebug() << "For left line, two points:" << point_2 << "--" << point_1;
}

void
BasePlotWidget::updateIntegrationScopeVerticalRhomb(bool for_integration)
{
  // We are handling a rhomboid scope, that is, a rectangle that
  // is tilted either to the left or to the right.

  // There are two kinds of rhomboid integration scopes: horizontal and
  // vertical.

  /*
   *                                       +3
   *                                     . |
   *                                   .   |
   *                                 .     |
   *                               .       +2
   *                             .       .
   *                           .       .
   *                         .       .
   *                      4+       .
   *                     | |     .
   *            height   | |   .
   *                     | | .
   *                      1+
   *
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *vertical* side (this is the plot context's
  // m_integrationScopeRhombHeight).

  IntegrationScopeFeatures scope_features;

  // Left vertical line
  QPointF point_1;
  scope_features = m_context.msp_integrationScope->getLeftMostTopPoint(point_1);

  // When the user rotates the vertical rhomboid, at some point, if the current
  // drag point is on the same x axis value as the start drag point, then we say
  // that the rhomboid is flattened on the y axis. In this case, we do not draw
  // anything as this is a purely unusable situation.

  if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
    {
      // qDebug() << "The vertical rhomboid is flattened on the y axis.";

      mp_selectionRectangeLine1->setVisible(false);
      mp_selectionRectangeLine2->setVisible(false);
      mp_selectionRectangeLine3->setVisible(false);
      mp_selectionRectangeLine4->setVisible(false);

      return;
    }

  if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    qFatal("The rhomboid should be vertical!");

  // At this point we can draw the rhomboid fine.

  QPointF point_2;
  if(!m_context.msp_integrationScope->getLeftMostBottomPoint(point_2))
    qFatal("Failed to getLeftMostBottomPoint.");

  // qDebug() << "For left vertical line, two points:" << point_1 << "--"
  //          << point_2;

  mp_selectionRectangeLine1->start->setCoords(point_1);
  mp_selectionRectangeLine1->end->setCoords(point_2);

  // Only if we are drawing a selection rectangle for integration, do we set
  // arrow heads to the line.
  if(for_integration)
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
    }
  else
    {
      mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
      mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
    }

  mp_selectionRectangeLine1->setVisible(true);

  // Lower oblique line
  if(!m_context.msp_integrationScope->getRightMostBottomPoint(point_1))
    qFatal("Failed to getRightMostBottomPoint.");
  mp_selectionRectangeLine2->start->setCoords(point_2);
  mp_selectionRectangeLine2->end->setCoords(point_1);
  mp_selectionRectangeLine2->setVisible(true);

  // qDebug() << "For lower oblique line, two points:" << point_2 << "--"
  //          << point_1;

  // Right vertical line
  if(!m_context.msp_integrationScope->getRightMostTopPoint(point_2))
    qFatal("Failed to getRightMostTopPoint.");
  mp_selectionRectangeLine3->start->setCoords(point_1);
  mp_selectionRectangeLine3->end->setCoords(point_2);
  mp_selectionRectangeLine3->setVisible(true);

  // qDebug() << "For right vertical line, two points:" << point_1 << "--"
  //          << point_2;

  // Upper oblique line
  if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
    qFatal("Failed to get the LeftMostTopPoint.");
  mp_selectionRectangeLine4->end->setCoords(point_2);
  mp_selectionRectangeLine4->start->setCoords(point_1);
  mp_selectionRectangeLine4->setVisible(true);

  // qDebug() << "For upper oblique line, two points:" << point_2 << "--"
  //          << point_1;
}

void
BasePlotWidget::updateIntegrationScopeRhomb(bool for_integration)
{
  //  qDebug();

  // if(for_integration)
  // qDebug() << "for_integration:" << for_integration;

  // We are handling a skewed rectangle (rhomboid), that is a rectangle that
  // is tilted either to the left or to the right.

  // There are two kinds of rhomboid integration scopes:

  /*
                          4+----------+3
                           |          |
                          |          |
                         |          |
                        |          |
                       |          |
                      |          |
                     |          |
                    1+----------+2
                     ----width---
 */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *horizontal* side (this is the plot context's
  // m_integrationScopeRhombWidth).

  // and


  /*
   *                                       +3
   *                                     . |
   *                                   .   |
   *                                 .     |
   *                               .       +2
   *                             .       .
   *                           .       .
   *                         .       .
   *                      4+       .
   *                     | |     .
   *            height   | |   .
   *                     | | .
   *                      1+
   *
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *vertical* side (this is the plot context's
  // m_integrationScopeRhombHeight).

  // qDebug() << "Before calling updateIntegrationScopeRhomb(), "
  //             "m_integrationScopeRhombWidth:"
  //          << m_context.m_integrationScopeRhombWidth
  //          << "and m_integrationScopeRhombHeight:"
  //          << m_context.m_integrationScopeRhombHeight;

  m_context.updateIntegrationScopeRhomb();

  // qDebug() << "After, m_integrationScopeRhombWidth:"
  //          << m_context.m_integrationScopeRhombWidth
  //          << "and m_integrationScopeRhombHeight:"
  //          << m_context.m_integrationScopeRhombHeight;

  // Now that the integration scope has been updated as a rhomboid,
  // use these newly set data to actually draw the integration
  // scope lines.

  // We thus need to first establish if we have a horiontal or a vertical
  // rhomboid scope. This information is located in
  // m_context.m_integrationScopeRhombWidth and
  // m_context.m_integrationScopeRhombHeight. If width > 0, height *has to be
  // 0*, which indicates a horizontal rhomb.Conversely, if height is > 0, then
  // the rhomb is vertical.

  if(m_context.m_integrationScopeRhombWidth > 0)
    // We are dealing with a horizontal scope.
    updateIntegrationScopeHorizontalRhomb(for_integration);
  else if(m_context.m_integrationScopeRhombHeight > 0)
    // We are dealing with a vertical scope.
    updateIntegrationScopeVerticalRhomb(for_integration);
  else
    qFatal("Cannot be both the width or height of rhomboid scope be 0.");
}

void
BasePlotWidget::updateIntegrationScopeDrawing(bool as_line_segment,
                                              bool for_integration)
{
  // qDebug() << "as_line_segment:" << as_line_segment;
  // qDebug() << "for_integration:" << for_integration;

  // We now need to construct the selection rectangle, either for zoom or for
  // integration.

  // There are two situations :
  //
  // 1. if the rectangle should look like a line segment
  //
  // 2. if the rectangle should actually look like a rectangle. In this case,
  // there are two sub-situations:
  //
  //    a. if the Alt modifier key is down, then the rectangle is rhomboid.
  //
  //    b. otherwise the rectangle is conventional.

  if(as_line_segment)
    {
      // qDebug() << "Updating the integration scope to an IntegrationScope.";
      updateIntegrationScope(for_integration);
    }
  else
    {
      if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
        {
          // qDebug()
          //   << "Updating the integration scope to an IntegrationScopeRect.";
          updateIntegrationScopeRect(for_integration);
        }
      else if(m_context.m_keyboardModifiers & Qt::AltModifier)
        {
          // The user might use the Alt modifier, but if no rhomboid side has
          // been defined using the S key, then we do not do any rhomboid
          // selection because we do not know the side size of that rhomboid.

          if(!m_context.m_integrationScopeRhombHeight &&
             !m_context.m_integrationScopeRhombWidth)
            updateIntegrationScopeRect(for_integration);
          else
            // qDebug()
            //   << "Updating the integration scope to an
            //   IntegrationScopeRhomb.";
            updateIntegrationScopeRhomb(for_integration);
        }
    }

  // Depending on the kind of IntegrationScope, (normal, rect or rhomb)
  // we have to measure things in different ways. We now set in the context
  // a number of parameters that will be used by its user.

  QPointF point;
  double height;
  std::vector<QPointF> points;

  // Integration scope values are sorted:
  // Line scope: point is left and width is right.x - left.x
  // Rect scope: point is bottom left.
  // Rhomb scope: points 1->4 are bottom left->bottom right->top right->top left
  //              width is 2.x - 1.x.

  if(m_context.msp_integrationScope->getPoints(points))
    {
      // We have defined a IntegrationScopeRhomb.

      if(!m_context.msp_integrationScope->getLeftMostPoint(point))
        qFatal("Failed to get LeftMost point.");
      m_context.m_xRegionRangeStart = point.x();

      if(!m_context.msp_integrationScope->getRightMostPoint(point))
        qFatal("Failed to get RightMost point.");
      m_context.m_xRegionRangeEnd = point.x();
    }
  else if(m_context.msp_integrationScope->getHeight(height))
    {
      // We have defined a IntegrationScopeRect.

      if(!m_context.msp_integrationScope->getPoint(point))
        qFatal("Failed to get point.");
      m_context.m_xRegionRangeStart = point.x();

      double width;

      if(!m_context.msp_integrationScope->getWidth(width))
        qFatal("Failed to get width.");

      m_context.m_xRegionRangeEnd = m_context.m_xRegionRangeStart + width;

      m_context.m_yRegionRangeStart = point.y();

      m_context.m_yRegionRangeEnd = point.y() + height;
    }
  else
    {
      // We have defined a IntegrationScope.

      if(!m_context.msp_integrationScope->getPoint(point))
        qFatal("Failed to get point.");
      m_context.m_xRegionRangeStart = point.x();

      double width;

      if(!m_context.msp_integrationScope->getWidth(width))
        qFatal("Failed to get width.");
      m_context.m_xRegionRangeEnd = m_context.m_xRegionRangeStart + width;
    }

  // At this point, draw the text describing the widths.

  // We want the x-delta on the bottom of the rectangle, inside it
  // and the y-delta on the vertical side of the rectangle, inside it.

  // Draw the selection width text
  drawXScopeSpanFeatures();
}

void
BasePlotWidget::hideSelectionRectangle(bool reset_values)
{
  mp_selectionRectangeLine1->setVisible(false);
  mp_selectionRectangeLine2->setVisible(false);
  mp_selectionRectangeLine3->setVisible(false);
  mp_selectionRectangeLine4->setVisible(false);

  if(reset_values)
    {
      resetSelectionRectangle();
    }
}

void
BasePlotWidget::resetSelectionRectangle()
{
  std::const_pointer_cast<IntegrationScopeBase>(m_context.msp_integrationScope)
    ->reset();
}

SelectionDrawingLines
BasePlotWidget::whatIsVisibleOfTheSelectionRectangle()
{
  // There are four lines that make the selection polygon. We want to know
  // which lines are visible.

  int current_selection_polygon =
    static_cast<int>(SelectionDrawingLines::NOT_SET);

  if(mp_selectionRectangeLine1->visible())
    {
      current_selection_polygon |=
        static_cast<int>(SelectionDrawingLines::TOP_LINE);
      // qDebug() << "current_selection_polygon:" <<
      // current_selection_polygon;
    }
  if(mp_selectionRectangeLine2->visible())
    {
      current_selection_polygon |=
        static_cast<int>(SelectionDrawingLines::RIGHT_LINE);
      // qDebug() << "current_selection_polygon:" <<
      // current_selection_polygon;
    }
  if(mp_selectionRectangeLine3->visible())
    {
      current_selection_polygon |=
        static_cast<int>(SelectionDrawingLines::BOTTOM_LINE);
      // qDebug() << "current_selection_polygon:" <<
      // current_selection_polygon;
    }
  if(mp_selectionRectangeLine4->visible())
    {
      current_selection_polygon |=
        static_cast<int>(SelectionDrawingLines::LEFT_LINE);
      // qDebug() << "current_selection_polygon:" <<
      // current_selection_polygon;
    }

  // qDebug() << "returning visibility:" << current_selection_polygon;

  return static_cast<SelectionDrawingLines>(current_selection_polygon);
}

bool
BasePlotWidget::isSelectionRectangleVisible()
{
  // Sanity check
  int check = 0;

  check += mp_selectionRectangeLine1->visible();
  check += mp_selectionRectangeLine2->visible();
  check += mp_selectionRectangeLine3->visible();
  check += mp_selectionRectangeLine4->visible();

  if(check > 0)
    return true;

  return false;
}

void
BasePlotWidget::setFocus()
{
  // qDebug() << "Setting focus to the QCustomPlot:" << this;

  QCustomPlot::setFocus();

  // qDebug() << "Emitting setFocusSignal().";

  emit setFocusSignal();
}

//! Redraw the background of the \p focusedPlotWidget plot widget.
void
BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
{
  if(focusedPlotWidget == nullptr)
    throw ExceptionNotPossible(
      "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
      "-- "
      "ERROR focusedPlotWidget cannot be nullptr.");

  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();
}

void
BasePlotWidget::updateContextXandYAxisRanges()
{
  m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
  m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);

  // qDebug() << "The new updated context: " << m_context.toString();
}

const BasePlotContext &
BasePlotWidget::getContext() const
{
  return m_context;
}


} // namespace pappso
