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


///////////////////////////// Qt include


///////////////////////////// Local includes
#include <minexpert/gui/MassSpecPlotWidget.hpp>
#include <minexpert/gui/MassSpecWnd.hpp>
#include <minexpert/nongui/AnalysisPreferences.hpp>
#include <minexpert/gui/MainWindow.hpp>
#include <minexpert/nongui/History.hpp>
#include <minexpert/nongui/MassDataIntegrator.hpp>


namespace msXpSmineXpert
{

/*/js/ Class: MassSpecPlotWidget
 * <comment>This class cannot be instantiated with the new operator. Objects
 * of this class are made available to the scripting environment under the
 * form of object variable names massSpecPlotWidget[index], with [index]
 * being 0 for the first object created and being incremented each time a new
 * MassSpecPlotWidget is created.</comment>
 */


//! Construct an initialized MassSpecPlotWidget instance.
/*!

  This function registers the key codes that, in conjunction to mouse button
  clicks trigger specific actions, like data integrations.

  \param parent parent widget.

  \param name name of the module.

  \param desc description of the wiget type.

  \param massSpecDataSet MassSpecDataSet holding the mass data.

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

  \param isMultiGraph tells if the widget to be instanciated is for displaying
  multiple graphs.

*/
MassSpecPlotWidget::MassSpecPlotWidget(QWidget *parent,
                                       const QString &name,
                                       const QString &desc,
                                       const MassSpecDataSet *massSpecDataSet,
                                       const QString &fileName,
                                       bool isMultiGraph)
  : AbstractPlotWidget{
      parent, name, desc, massSpecDataSet, fileName, isMultiGraph}
{
  AbstractPlotWidget::registerQtKeyCode(
    Qt::Key_R,
    "Press R while right mouse click-drag to integrate to a XIC chromatogram");
  AbstractPlotWidget::registerQtKeyCode(
    Qt::Key_D,
    "Press D while right mouse click-drag to integrate to a drift spectrum");
  AbstractPlotWidget::registerQtKeyCode(Qt::Key_I,
                                        "Press I while right mouse click-drag "
                                        "to integrate to a TIC intensity "
                                        "value");

  // Give the axes some labels:
  xAxis->setLabel("m/z");
  yAxis->setLabel("counts");
}


//! Destruct \c this MassSpecPlotWidget instance.
MassSpecPlotWidget::~MassSpecPlotWidget()
{
}


//! Create the contextual menu.
QMenu *
MassSpecPlotWidget::createContextMenu()
{
  AbstractPlotWidget::createContextMenu();

  // In the mass spectrum plot widget, exporting the data does not make
  // sense because we cannot filter the mass spectra for a valid m/z range.
  // So we need to hide the corresponding menu item.

  mpa_exportDataAction->setVisible(false);

  return mpa_contextMenu;
}


//! Deconvolute the mass peaks into charge and molecular mass.
bool
MassSpecPlotWidget::deconvolute()
{
  // There are two situations: when the user is deconvoluting on the
  // basis of the distance between two consecutive peaks of a same
  // isotopic cluster or when the user deconvolutes on the basis of two
  // different charged-stated peaks that belong to the same envelope.

  // We can tell the difference because in the first case the xDelta
  // should be less than 1. In the other case, of course the difference
  // is much greater than 1.

  // In order to do the deconvolutions, we need to know what is the tolerance
  // on the fraction part of the deconvoluted charge value. This value is set in
  // the parent window's double spin box.

  MassSpecWnd *wnd = dynamic_cast<MassSpecWnd *>(mp_parentWnd);

  m_chargeFracPartTolerance = wnd->chargeFracPartTolerance();

  // Note that m_xDelta is always fabs().

  if(m_xDelta >= 0 && m_xDelta <= 1.1)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "m_xDelta:" << m_xDelta
      //<< "trying isotope-based deconvolution.";

      return deconvoluteIsotopicCluster();
    }

  // If not deconvoluting on the basis of the isotopic cluster, then:

  // Let's get the spinbox value that says what is the span between the
  // two charge envelope peaks that are looked into:
  int span = wnd->massPeakSpan();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "span is:" << span;

  return deconvoluteChargedState(span);
}


//! Deconvolute the mass peaks into charge and molecular mass.
/*!

  This is one of two methods to deconvolute mass data into a charge value and
  a Mr value. The method implemented in this function is based on the charge
  state envelope offered by the mass spectrum (most often for polymers of a
  reasonable size).

  \param span value representing the number of peaks of the charge state
  envelope that are spanned by the user selection. Defaults to 1, that is, the
  span encompasses two \e consecutive mass peaks of a given charge state
  envelope.

  Set m_lastMz, m_lastCharge and m_lastMass.

  \return true if the deconvolution could be performed, false otherwise.
  */
bool
MassSpecPlotWidget::deconvoluteChargedState(int span)
{
  // We assume that we are dealing with two successive (if span is 1) mass
  // peaks belonging to a given charge state family.

  // We call span the number of peaks of a given charge state envelope
  // that separate the initial peak (lowerMz) from the last peak (upperMz).
  // That parameter defaults to 1.

  // Note that in this case, we cannot use the m_xRangeMin and m_xRangeMax
  // values, because they are sorted, and we need to know in which direction
  // the user had drug the mouse, because we want to provide the Mr value
  // for the peak currently under the mouse cursor, that is under
  // currentDragPoint.

  double startMz = m_startDragPoint.x();
  double curMz   = m_currentDragPoint.x();

  if(startMz == curMz)
    {
      m_lastCharge       = -1;
      m_lastMz           = qSNaN();
      m_lastTicIntensity = qSNaN();
      m_lastMass         = qSNaN();

      return false;
    }

  // We need to be aware that the status bar of the window that contains
  // this plot widget shows the cursor position realtime, and that cursor
  // position is the m_currentDragPoint.x value. Thus, we need to make the
  // calculations with the charge being the one of the polymer under the
  // cursor position. This is tricky because it changes when the user
  // switches drag senses: from left to right and right to left.
  // The way z is calculated always makes it the charge of the highest mz
  // value. So knowing this, depending on the drag sense we'll have to take
  // curMz and apply to it either z charge (left to right drag) or (z+span)
  // charge (right to left).

  // Make sure lower is actually lower, even if drag is from right to left.
  // This is only to have a single charge calculation.
  double lowerMz;
  double upperMz;

  if(startMz < curMz)
    {
      lowerMz = startMz;
      upperMz = curMz;
    }
  else
    {
      lowerMz = curMz;
      upperMz = startMz;
    }

  double chargeTemp = ((lowerMz * span) - span) / (upperMz - lowerMz);

  // Make a judicious roundup.

  double chargeIntPart;
  double chargeFracPart = modf(chargeTemp, &chargeIntPart);

  // When calculating the charge of the ion, very rarely does it provide a
  // perfect integer value. Most often (if deconvolution is for bona fide
  // peaks belonging to the same charge state envelope) that value is with
  // either a large fractional part or a very small fractional part. What we
  // test here, it that fractional part. If it is greater than
  // m_chargeFracPartTolerance, then we simply round up to the next integer
  // value (that is, chargeIntPart = 27 and chargeFracPart 0.995, then we
  // set charge to 28). If it is lesser or equal to (1 -
  // m_chargeFracPartTolerance /* that is >= 0.01 */, then we let
  // chargeIntPart unmodified (that is, chargeIntPart = 29 and
  // chargeFracPart 0.01, then we set charge to 29). If chargeFracPart is in
  // between (1 - m_chargeFracPartTolerance) and m_chargeFracPartTolerance,
  // then we consider that the peaks do not belong to the same charge state
  // envelope.

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "Charge:" << chargeIntPart
  //<< "Charge fractional part: " << chargeFracPart;


  if(chargeFracPart >= (1 - m_chargeFracPartTolerance /* that is >= 0.01 */) &&
     chargeFracPart <= m_chargeFracPartTolerance /* that is <= 0.99 */)
    {
      m_lastCharge       = -1;
      m_lastMz           = qSNaN();
      m_lastTicIntensity = qSNaN();
      m_lastMass         = qSNaN();

      // qDebug() << __FILE__ << __LINE__
      //<< "Not a charge state family peak,"
      //<< "returning from deconvoluteChargeState";

      return false;
    }

  if(chargeFracPart > m_chargeFracPartTolerance)
    m_lastCharge = chargeIntPart + 1;
  else
    m_lastCharge = chargeIntPart;

  // Now, to actually compute the molecular mass based on the charge and on
  // the currently displayed m/z value, we need to have some thinking:

  if(startMz < curMz)
    {
      // The drag was from left to right, that is curMz is greater than
      // startMz. Fine, the z value is effectively the charge of the ion at
      // curMz. Easy, no charge value modification here.

      // Now that we know that the values will be useful for the data analysis
      // stuff, store the actual values in the proper sorted order.
      m_keyRangeStart = startMz;
      m_keyRangeEnd   = curMz;
    }
  else
    {
      // The drag was from right to left, that is curMz is less than startMz.
      // So we want to show the charge of the curMz, that is, z + span.
      m_lastCharge = m_lastCharge + span;

      // Now that we know that the values will be useful for the data analysis
      // stuff, store the actual values in the proper sorted order.
      m_keyRangeStart = curMz;
      m_keyRangeEnd   = startMz;
    }

  m_lastMz   = curMz;
  m_lastMass = (curMz * m_lastCharge) - (m_lastCharge * m_protonMass);

  // qDebug() << __FILE__ << __LINE__
  //<< "startMz:" << QString("%1").arg(startMz, 0, 'f', 6)
  //<< "m_lastMz (curMz):" << QString("%1").arg(m_lastMz, 0, 'f', 6)
  //<< "m_lastMass:" << QString("%1").arg(m_lastMass, 0, 'f', 6)
  //<< "m_lastCharge:" << QString("%1").arg(m_lastCharge);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "returning true";

  return true;
}


//! Deconvolute the mass peaks into charge and molecular mass.
/*!

  This is one of two methods to deconvolute mass data into a charge value and
  a Mr value. The method implemented in this function is based on the distance
  that separates two immediately consecutive peaks of an isotopic cluster.
  This method can be used as long as the instrument produced data with a
  resolution sufficient to separate reasonably well the different peaks of an
  isotopic cluster.

  Set m_lastMz, m_lastCharge and m_lastMass.

  \return true if the deconvolution could be performed, false otherwise.
  */
bool
MassSpecPlotWidget::deconvoluteIsotopicCluster()
{

  // m_xRangeMin and m_xRangeMax and m_xDelta (in fabs() form) have been set
  // during mouve movement handling. Note that the range values *are
  // sorted*.

  double chargeTemp = 1 / m_xDelta;

  // Reset the value (mz) axis range data
  m_keyRangeStart = qSNaN();
  m_keyRangeEnd   = qSNaN();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "m_xRangeMin:" << m_xRangeMin
  //<< "m_xRangeMax:" << m_xRangeMax;

  if(m_xRangeMax == m_xRangeMin)
    {
      // qDebug() << __FILE__ << __LINE__
      //<< "Same m_xRangeMax and m_xRangeMin:"
      //<< "returning from deconvoluteIsotopicCluster";

      return false;
    }

  // Update the start and end range values for use in another function.
  m_keyRangeStart = m_xRangeMin;
  m_keyRangeEnd   = m_xRangeMax;

  // Make a judicious roundup.
  double chargeIntPart;
  double chargeFracPart = modf(chargeTemp, &chargeIntPart);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "m_xDelta:" << m_xDelta
  //<< "chargeTemp:" << chargeTemp
  //<< "chargeIntPart:" << chargeIntPart
  //<< "chargeFracPart:" << chargeFracPart
  //<< "m_chargeFracPartTolerance:" << m_chargeFracPartTolerance;

  if(chargeFracPart >= (1 - m_chargeFracPartTolerance) &&
     chargeFracPart <= m_chargeFracPartTolerance)
    {
      m_lastCharge       = -1;
      m_lastMz           = qSNaN();
      m_lastTicIntensity = qSNaN();
      m_lastMass         = qSNaN();
      m_keyRangeStart    = qSNaN();
      m_keyRangeEnd      = qSNaN();

      // qDebug() << __FILE__ << __LINE__
      //<< "Not in a isotopic cluster peak:"
      //<< "returning from deconvoluteIsotopicCluster";

      return false;
    }

  if(chargeFracPart > m_chargeFracPartTolerance)
    {
      m_lastCharge = chargeIntPart + 1;

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "chargeFracPart > m_chargeFracPartTolerance -> m_lastCharge = " <<
      // m_lastCharge;
    }
  else
    {
      m_lastCharge = chargeIntPart;

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "chargeFracPart <=  m_chargeFracPartTolerance -> m_lastCharge = " <<
      // m_lastCharge;
    }

  // Now that we have the charge in the form of an int, we can compute the
  // Mr of the lightest isotopic cluster peak (the one that has the lowest x
  // value). That value is stored in m_xRangeMin.

  m_lastMz = m_xRangeMin;

  m_lastMass = (m_lastMz * m_lastCharge) - (m_lastCharge * m_protonMass);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "returning true";

  return true;
}


bool
MassSpecPlotWidget::computeResolvingPower()
{

  // m_xRangeMin and m_xRangeMax and m_xDelta (in fabs() form) have been set
  // during mouve movement handling. Note that the range values *are
  // sorted*.

  if(!m_xDelta)
    {
      m_lastResolvingPower = std::numeric_limits<double>::min();
      return false;
    }

  // Resolving power is m/z / Delta(m/z), for singly-charged species.

  m_lastResolvingPower = (m_xRangeMin + (m_xDelta / 2)) / m_xDelta;

  return true;
}


//! Craft a string representing the data of the peak that was analysed.
/*!

  To do so, this functions get the data format string from the parent window's
  AnalysisPreferences instance. The format string is interpreted and the
  relevant values are substituted to craft a stanza (a paragraph) that matches
  the peak data at hand.

  \return a string containing the stanza.
  */
QString
MassSpecPlotWidget::craftAnalysisStanza(QCPGraph *theGraph,
                                        const QString &fileName)
{

  // qDebug() << __FILE__ << __LINE__
  //<< "Entering" << __FUNCTION__";

  // This function might be called for a graph that sits in a single-graph
  // plot widget or for a graph that sits in a multi-graph plot widget.
  // Depending on the parameters, we'll know what is the case. If the
  // parameters are not nullptr or empty, then the plot widget is a
  // multi-graph plot widget and we need to set the data in the analysis
  // record only for the graph
  // passed as parameter.

  QCPGraph *origGraph = nullptr;

  if(theGraph == nullptr)
    origGraph = graph();
  else
    origGraph = theGraph;

  QString destFileName;

  if(fileName.isEmpty())
    destFileName = m_fileName;
  else
    destFileName = fileName;


  // When the mouse moves over the plot widget, its position is recorded
  // real time. That position is both a m/z value and an i (intensity)
  // value. We cannot rely on the i value, because it is valid only the for
  // the last added graph. But the m/z value is the same whatever the graph
  // we are interested in.

  // Craft the analysis stanza:
  QString stanza;

  MassSpecWnd *wnd = dynamic_cast<MassSpecWnd *>(mp_parentWnd);
  if(wnd == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  const AnalysisPreferences *analPrefs = wnd->analysisPreferences();
  if(analPrefs == nullptr)
    return stanza;

  // The way we work here is that we get a format string that specifies how
  // the user wants to have the data formatted in the stanza. That format
  // string is located in a DataFormatStringSpecif object as the m_format
  // member. There is also a m_formatType member that indicates what is the
  // format that we request (mass spec, tic chrom or drift spec). That
  // format type member is an int that also is the key of the hash that is
  // located in the analPrefs: QHash<int, DataFormatStringSpecif *>
  // m_dataFormatStringSpecifHash.

  DataFormatStringSpecif *specif =
    analPrefs->m_dataFormatStringSpecifHash.value(FormatType::MASS_SPEC);

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

  // Handy local copy.
  QString formatString = specif->m_format;

  QChar prevChar = ' ';

  for(int iter = 0; iter < formatString.size(); ++iter)
    {
      QChar curChar = formatString.at(iter);

      // qDebug() << __FILE__ << __LINE__
      // << "Current char:" << curChar;

      if(curChar == '\\')
        {
          if(prevChar == '\\')
            {
              stanza += '\\';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '\\';
              continue;
            }
        }

      if(curChar == '%')
        {
          if(prevChar == '\\')
            {
              stanza += '%';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '%';
              continue;
            }
        }

      if(curChar == 'n')
        {
          if(prevChar == '\\')
            {
              stanza += QString("\n");

              // Because a newline only works if it is followed by something,
              // and
              // if the user wants a termination newline, then we need to
              // duplicate that new line char if we are at the end of the
              // string.

              if(iter == formatString.size() - 1)
                {
                  // This is the last character of the line, then, duplicate
                  // the
                  // newline so that it actually creates a new line in the
                  // text.

                  stanza += QString("\n");
                }

              prevChar = ' ';
              continue;
            }
        }

      if(prevChar == '%')
        {
          // The current character might have a specific signification.
          if(curChar == 'f')
            {
              QFileInfo fileInfo(destFileName);
              if(fileInfo.exists())
                stanza += fileInfo.fileName();
              else
                stanza += "Untitled";
              prevChar = ' ';
              continue;
            }
          if(curChar == 'X')
            {
              stanza += QString("%1").arg(m_lastMz, 0, 'g', 6);
              prevChar = ' ';
              continue;
            }
          if(curChar == 'Y')
            {
              // We need to get the value of the intensity for the graph.
              double intensity = getYatX(m_lastMz, origGraph);

              if(!intensity)
                qDebug() << __FILE__ << __LINE__
                         << "Warning, intensity for m/z " << m_lastMz
                         << "is zero.";
              else
                stanza += QString("%1").arg(intensity, 0, 'g', 6);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'x')
            {
              stanza += QString("%1").arg(m_xDelta, 0, 'g', 6);
              prevChar = ' ';
              continue;
            }
          if(curChar == 'y')
            {
              stanza += "this is the mass spec count delta y";

              prevChar = ' ';
              continue;
            }
          if(curChar == 'z')
            {
              stanza += QString("%1").arg(m_lastCharge);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'M')
            {
              stanza += QString("%1").arg(m_lastMass, 0, 'f', 6);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'I')
            {
              stanza += QString("%1").arg(m_lastTicIntensity, 0, 'g', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 's')
            {
              stanza += QString("%1").arg(m_keyRangeStart, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'e')
            {
              stanza += QString("%1").arg(m_keyRangeEnd, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          // At this point the '%' is not followed by any special character
          // above, so we skip them both from the text. If the '%' is to be
          // printed, then it needs to be escaped.

          continue;
        }
      // End of
      // if(prevChar == '%')

      // The character prior this current one was not '%' so we just append
      // the current character.
      stanza += curChar;
    }
  // End of
  // for (int iter = 0; iter < pattern.size(); ++iter)

  return stanza;
}


//! Mouse movement handler.
/*!

  This function is responsible for triggering the deconvolution functions
  depending on the type (left/right) mouse button that might be pressed.

*/
void
MassSpecPlotWidget::mouseMoveHandler(QMouseEvent *event)
{
  // This function is in charge of setting the *ordered* m_xRangeMin/Max and
  // m_yRangeMin/Max and fabs(m_xDelta) and fabs(m_yDelta) values, which
  // thus are up-to-data for use here.

  AbstractPlotWidget::mouseMoveHandler(event);

  // If the mouse move originated by clicking initially in any of the axes, then
  // we do not want to do anything other than doing what the base class does. In
  // particular, we do not want to perform any deconvolution!

  if(m_clickWasOnXAxis || m_clickWasOnYAxis)
    return;

  // if(m_isMultiGraph)
  // qDebug() << __FILE__ << __LINE__
  // << "Multi-graph";
  // else
  // qDebug() << __FILE__ << __LINE__
  // << "Not multi-graph";

  // if the is text is available that means that the base class function has
  // done the work.
  if(!hasItem(m_xDeltaText))
    {
      event->accept();
      return;
    }

  // If we are here, that means that we have something
  // derived-class-specific to do.
  if(event->buttons() == Qt::LeftButton)
    {
      // Get the message that is already displayed in the parent main window,
      // as the base class mouse move handler displays the coordinates of the
      // moving cursor. But we want to add the ion specifs.

      // Handy cast pointer.
      QMainWindow *parentWnd = static_cast<QMainWindow *>(mp_parentWnd);
      QString currentMessage = parentWnd->statusBar()->currentMessage();

      // We know that we are willing to inform the user on the xDelta and
      // thus on the charge of the ion of which the distance between peaks
      // of the corresponding isotopic cluster is being measure if there is
      // the m_xDeltaText item associated to the plot.

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< QDateTime().currentDateTime() << "now trying a deconvolution";

      if(deconvolute())
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "should print deconvolution data";

          currentMessage += QString(" ; range [%1-%2]: z = %3 ; Mr = %4)")
                              .arg(m_keyRangeStart, 0, 'f', 3)
                              .arg(m_keyRangeEnd, 0, 'f', 3)
                              .arg(m_lastCharge)
                              .arg(m_lastMass, 0, 'f', 6);
        }

      if(computeResolvingPower())
        currentMessage +=
          QString(" ; Res = %1)").arg(m_lastResolvingPower, 0, 'f', 0);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< currentMessage;

      parentWnd->statusBar()->showMessage(currentMessage);
    }

  event->accept();
  return;
}


//! Handler for the mouse release event.
/*!

  This function checks for the pressed keyboard key code and, depending on
  that key code, triggers specific actions, like integrating data to drift
  spectrum or to retention time (XIC) or to single TIC intensity value.

*/
void
MassSpecPlotWidget::mouseReleaseHandledEvent(QMouseEvent *event)
{
  if(m_isMultiGraph)
    // All the actions below can only be performed if the plot widget has a
    // knowledge of the mass spec data set. Thus, if *this plot widget is a
    // multi-graph plot widget we need to return immediately.
    return;

  if(m_pressedKeyCode == Qt::Key_D && event->button() == Qt::RightButton)
    {
      integrateToDt(xRangeMin(), xRangeMax());
    }

  if(m_pressedKeyCode == Qt::Key_R && event->button() == Qt::RightButton)
    {
      integrateToRt(xRangeMin(), xRangeMax());
    }

  if(m_pressedKeyCode == Qt::Key_I && event->button() == Qt::RightButton)
    {
      integrateToTicIntensity(xRangeMin(), xRangeMax());
    }
}


//! Perform a ranged [\p lower -- \p upper] data integration to a drift
//! spectrum.
void
MassSpecPlotWidget::integrateToDt(double lower, double upper)
{
  // Get the pointer to the x-axis (key axis).

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

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

  double rangeEnd = upper;

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

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::MZ_TO_DT, rangeStart, rangeEnd);

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

  emit newDriftSpectrum(mp_massSpecDataSet,
                        "Calculating drift spectrum.",
                        localHistory,
                        m_plottingColor);
}


/*/js/
 * MassSpecPlotWidget.jsIntegrateToDt(lower, upper)
 *
 * Starts an integration of mass spectra data to a drift spectrum limiting the
 * data range specified with the numerical arguments.
 *
 * lower, upper: retention time values limiting the integration. If no values
 * are provided, there is no limitation to the integration.
 *
 * This function creates a new plot widget to display the obtained results.
 */
void
MassSpecPlotWidget::jsIntegrateToDt(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return;

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

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

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

  double rangeEnd = upper;

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

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::MZ_TO_DT, rangeStart, rangeEnd);

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

  MassSpecWnd *wnd = dynamic_cast<MassSpecWnd *>(mp_parentWnd);

  MainWindow *mainWindow = static_cast<MainWindow *>(wnd->parent());

  AbstractMultiPlotWnd *targetWnd = mainWindow->mp_driftSpecWnd;

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

  static_cast<DriftSpecWnd *>(targetWnd)->jsNewDriftSpectrum(
    this,
    mp_massSpecDataSet,
    "Calculating drift spectrum.",
    localHistory,
    m_plottingColor);
}


//! Perform a ranged [\p lower -- \p upper] data integration to a TIC
//! chromatogram.
void
MassSpecPlotWidget::integrateToRt(double lower, double upper)
{
  // Get the pointer to the x-axis (key axis).

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

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

  double rangeEnd = upper;

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

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::MZ_TO_RT, rangeStart, rangeEnd);

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

  // The signal is emitted, but the connection to the receiver exists only
  // if this plot widget is NOT multi-graph. So, even if mp_massSpecDataSet
  // is nullptr, there is no risk.
  emit newTicChromatogram(mp_massSpecDataSet,
                          "Calculating extracted ion chromatogram.",
                          localHistory,
                          m_plottingColor);
}


/*/js/
 * MassSpecPlotWidget.jsIntegrateToRt(lower, upper)
 *
 * Starts an integration of mass spectra data to a XIC chromatogram limiting the
 * data range specified with the numerical arguments.
 *
 * lower, upper: retention time values limiting the integration. If no values
 * are provided, there is no limitation to the integration.
 *
 * This function creates a new plot widget to display the obtained results.
 */
void
MassSpecPlotWidget::jsIntegrateToRt(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return;

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

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

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

  double rangeEnd = upper;

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

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::MZ_TO_RT, rangeStart, rangeEnd);

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

  MassSpecWnd *wnd = dynamic_cast<MassSpecWnd *>(mp_parentWnd);

  MainWindow *mainWindow = static_cast<MainWindow *>(wnd->parent());

  AbstractMultiPlotWnd *targetWnd = mainWindow->mp_massSpecWnd;

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

  static_cast<MassSpecWnd *>(targetWnd)->jsNewMassSpectrum(
    this,
    mp_massSpecDataSet,
    "Calculating drift spectrum.",
    localHistory,
    m_plottingColor);
}


//! Perform a ranged [\p lower -- \p upper] data integration to a single TIC
//! intensity value.
void
MassSpecPlotWidget::integrateToTicIntensity(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return;

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

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

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

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

  // Reset the axis range data because we may return prematurely and we want
  // to craft a stanza with proper values (even if they are qINan()).
  m_keyRangeStart = qSNaN();
  m_keyRangeEnd   = qSNaN();

  // Also reset the other datum that might hold invalid values.
  m_lastTicIntensity = qSNaN();

  if(rangeStart == rangeEnd)
    {
      return;
    }

  // Slot for sorting stuff.
  double tempVal;

  // Set the values in sorted order for later stanza crafting.
  if(rangeStart > rangeEnd)
    {
      tempVal    = rangeStart;
      rangeStart = rangeEnd;
      rangeStart = tempVal;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::MZ_TO_TIC_INT, rangeStart, rangeEnd);

  // At this point store the range data so that the user may use it in
  // analysis reporting.
  m_keyRangeStart = rangeStart;
  m_keyRangeEnd   = rangeEnd;

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

  // Sanity check. It is not possible that this plot widget is not
  // multi-graph and that at the same time, the mass spec data set pointer
  // is nullptr.
  if(!m_isMultiGraph && mp_massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Capture immediately the text that is located in the status bar
  // because if the computation is long that text will have disappeared
  // and we won't be able to show it in the TIC int text.

  QMainWindow *parentWnd = static_cast<QMainWindow *>(mp_parentWnd);
  QString currentMessage = parentWnd->statusBar()->currentMessage();

  if(currentMessage.isEmpty())
    currentMessage = QString("range [%1-%2]")
                       .arg(m_keyRangeStart, 0, 'f', 3)
                       .arg(m_keyRangeEnd, 0, 'f', 3);
  else
    currentMessage += QString(" ; range [%1-%2]")
                        .arg(m_keyRangeStart, 0, 'f', 3)
                        .arg(m_keyRangeEnd, 0, 'f', 3);

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Emitting ticIntensitySignal";

  emit ticIntensitySignal(mp_massSpecDataSet, currentMessage, localHistory);
}


/*/js/
 * MassSpecPlotWidget.jsIntegrateToTicIntensity(lower, upper)
 *
 * Starts an integration of mass spectra data to a TIC intensity single value
 * limiting the data range specified with the numerical arguments.
 *
 * lower, upper: retention time values limiting the integration. If no values
 * are provided, there is no limitation to the integration.
 *
 * The TIC intensity value is returned
 */
double
MassSpecPlotWidget::jsIntegrateToTicIntensity(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return 0;

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

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

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

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

  // Reset the axis range data because we may return prematurely and we want
  // to craft a stanza with proper values (even if they are qINan()).
  m_keyRangeStart = qSNaN();
  m_keyRangeEnd   = qSNaN();

  // Also reset the other datum that might hold invalid values.
  m_lastTicIntensity = qSNaN();

  if(rangeStart == rangeEnd)
    {
      return false;
    }

  // Slot for sorting stuff.
  double tempVal;

  // Set the values in sorted order for later stanza crafting.
  if(rangeStart > rangeEnd)
    {
      tempVal    = rangeStart;
      rangeStart = rangeEnd;
      rangeStart = tempVal;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::MZ_TO_TIC_INT, rangeStart, rangeEnd);

  // At this point store the range data so that the user may use it in
  // analysis reporting.
  m_keyRangeStart = rangeStart;
  m_keyRangeEnd   = rangeEnd;

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

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

  // Capture immediately the text that is located in the status bar
  // because if the computation is long that text will have disappeared
  // and we won't be able to show it in the TIC int text.

  QString currentMessage = parentWnd->statusBar()->currentMessage();

  QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));

  // Sanity check. It is not possible that this plot widget is not
  // multi-graph and that at the same time, the mass spec data set pointer
  // is nullptr.

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

  MassDataIntegrator integrator(mp_massSpecDataSet, localHistory);

  integrator.integrateToTicIntensity();

  m_lastTicIntensity = integrator.ticIntensity();

  QApplication::restoreOverrideCursor();

  if(currentMessage.isEmpty())
    parentWnd->statusBar()->showMessage(QString("range [%1-%2]: TIC I = %3")
                                          .arg(m_keyRangeStart, 0, 'f', 3)
                                          .arg(m_keyRangeEnd, 0, 'f', 3)
                                          .arg(m_lastTicIntensity, 0, 'g', 3));
  else
    parentWnd->statusBar()->showMessage(currentMessage +
                                        QString(" ; range [%1-%2]: TIC I = %3")
                                          .arg(m_keyRangeStart, 0, 'f', 3)
                                          .arg(m_keyRangeEnd, 0, 'f', 3)
                                          .arg(m_lastTicIntensity, 0, 'g', 3));

  return m_lastTicIntensity;
}


/*/js/
 * MassSpecPlotWidget.massSpectrum(lower, upper)
 *
 * Creates a new <MassSpectrum> object limiting the data to the lower and
 * upper range limits. The data used to craft the new MassSpectrum are this
 * plot widget data. That is, the <MassSpectrum> object is not created by
 * looking into the internal mass spectral data set.
 *
 * lower, upper: m/z values limiting the integration. If no values
 * are provided, there is no limitation to the integration and all the points
 * in this plot widget are used.
 *
 * Return a new MassSpectrum object.
 */
//! Return a MassSpectrum crafted from the plot data.
msXpSlibmass::MassSpectrum
MassSpecPlotWidget::massSpectrum(double lower, double upper)
{
  msXpSlibmass::MassSpectrum massSpectrum;

  // Iterate in the map and for each map item craft a MassPeak that is
  // appended to the returned MassSpectrum.

  if(m_isMultiGraph)
    return msXpSlibmass::MassSpectrum();

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

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

  for(auto iter = beginIt; iter != endIt; ++iter)
    {
      double mz = iter->key;

      if(!qIsNaN(lower))
        {
          if(mz < lower)
            continue;
        }

      if(!qIsNaN(upper))
        {

          // plot data are sorted, so we can break for this condition.

          if(mz > upper)
            break;
        }

      // At this point, we are in the range, if one was defined.

      msXpSlibmass::DataPoint *dataPoint =
        new msXpSlibmass::DataPoint(mz, iter->value);

      massSpectrum.append(dataPoint);
    }

  return massSpectrum;
}


/*/js/
 * MassSpecPlotWidget.newPlot(trace)
 *
 * Create a new MassSpecPlotWidget using <Trace> trace for the initialization
 * of the data.
 *
 * This function creates a new plot widget to display the <Trace> data.
 */

//! Create a new plot. This function is useful in the scripting environment.
void
MassSpecPlotWidget::newPlot()
{

  // We only work on this if *this plot widget is *not* multigraph

  if(m_isMultiGraph)
    return;

  MainWindow *mainWindow = static_cast<MainWindow *>(mp_parentWnd->parent());
  QScriptContext *ctx =
    mainWindow->mp_scriptingWnd->m_scriptEngine.currentContext();

  int argCount = ctx->argumentCount();
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "argCount:" << argCount;

  if(argCount != 1)
    {
      ctx->throwError(
        "Syntax error: function newPlot() takes one Trace object as argument.");
      return;
    }

  QScriptValue arg = ctx->argument(0);
  QVariant variant = arg.data().toVariant();

  if(!variant.isValid())
    {
      ctx->throwError(
        "Syntax error: function newPlot() takes one Trace object as argument.");
      return;
    }

  if(variant.userType() == QMetaType::type("msXpSlibmass::Trace"))
    {
      msXpSlibmass::Trace trace(qscriptvalue_cast<msXpSlibmass::Trace>(arg));

      static_cast<MassSpecWnd *>(mp_parentWnd)
        ->newPlot(this,
                  mp_massSpecDataSet,
                  QVector<double>::fromList(trace.keyList()),
                  QVector<double>::fromList(trace.valList()),
                  "Trace from scripting environment.",
                  m_history,
                  m_plottingColor);
    }
  else
    {
      ctx->throwError(
        "Syntax error: function newPlot() takes one Trace object as argument.");
      return;
    }
}


//! Key release event handler.
/*!

  Depending on the type of plot widget (multigraph or not), the actions
  triggered in this function will differ.

  For example, the space key will trigger the crafting of an analysis stanza
  (see craftAnalysisStanza()). The 'L' key triggers the printout of the
  currently pointed graph point data to the console window. The TAB key will
  trigger the cycling of the selected item in the open spectra dialog window
  (see OpenSpectraDlg).

*/
void
MassSpecPlotWidget::keyReleaseEvent(QKeyEvent *event)
{
  MassSpecWnd *parentWnd = static_cast<MassSpecWnd *>(mp_parentWnd);
  MainWindow *mainWindow = static_cast<MainWindow *>(parentWnd->parent());

  // First, check if the space bar was pressed.
  if(event->key() == Qt::Key_Space)
    {
      // The handling of the stanza creation is different in these two
      // situations:
      // 1. the plot widget where this event occurred is a conventional
      // mono-graph plot widget that sits in the lower part of the window. In
      // this case, the filename of the spectrum data file is available.
      // 2. the plot widget is a multi-graph plot widget that sits in the
      // upper part of the multi plot window. In that case, it is more
      // difficult to know what is the file that initially contained the data
      // plotted. We want to know what graph corresponds to what list widget
      // item that is selected (or not) in the open mass spectra dialog window
      // (see MainWindow).

      QString stanza;
      if(m_isMultiGraph)
        {

          // The event occurred in the multi-graph plot widget where many graphs
          // are displayed. We need to craft a stanza for each graph that
          // replicates the data of a plot that is proxied in the list widget of
          // the OpenSpectraDlg. Only handle the spectra of which the list
          // widget item is currently selected and for which the corresponding
          // mass multi-graph plot is visible.

          for(int iter = 0; iter < graphCount(); ++iter)
            {
              QCPGraph *iterGraph = graph(iter);

              // Only work on this graph if it is visible:
              if(!iterGraph->visible())
                continue;

              // Get a handle to the conventional plot widget of which this
              // multi-graph plot widget's graph is a replica.
              AbstractPlotWidget *massSpecPlotWidget =
                parentWnd->monoGraphPlot(iterGraph);

              // Now use that pointer to get a handle to the tic chromatogram
              // plot
              // widget whence the data in this plot widget came:

              // // Old version:
              // AbstractPlotWidget *ticChromPlotWidget =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.key(massSpecPlotWidget);

              // New version:
              DataPlotWidgetRelation *p_rootRelation = Q_NULLPTR;
              AbstractPlotWidget *ticChromPlotWidget =
                mainWindow->m_dataPlotWidgetRelationer.rootPlotWidget(
                  massSpecPlotWidget, &p_rootRelation);

              if(ticChromPlotWidget == nullptr)
                {
                  qFatal(
                    "Fatal error at %s@%d -- %s. "
                    "Cannot be that a multiGraph plot widget has no root plot "
                    "widget."
                    "Program aborted.",
                    __FILE__,
                    __LINE__,
                    __FUNCTION__);
                }

              // At this point, we know what plot widget in the Tic chrom window
              // is at the origin of the graph currently iterated into. We need
              // to
              // know if that plot widget has a corresponding item in the open
              // spectrum dialog list widget that is currently selected.

              if(mainWindow->mp_openSpectraDlg->isPlotWidgetSelected(
                   ticChromPlotWidget))
                {

                  // This means that we have to get the filename out of this tic
                  // chrom plot widget's mass spec data set:

                  QString fileName =
                    static_cast<TicChromPlotWidget *>(ticChromPlotWidget)
                      ->massSpecDataSet()
                      ->fileName();

                  stanza = craftAnalysisStanza(iterGraph, fileName);

                  if(stanza.isEmpty())
                    {
                      parentWnd->statusBar()->showMessage(
                        "Failed to craft an analysis stanza. "
                        "Please set the analysis preferences.");

                      event->accept();

                      return;
                    }
                  else
                    {
                      // qDebug() << __FILE__ << __LINE__
                      // << "Stanza: " << stanza;

                      emit recordAnalysisStanza(stanza, m_plottingColor);
                    }
                }
            }
          // End of
          // for(int iter = 0; iter < graphCount(); ++iter)
        }
      // End of
      // if(m_isMultiGraph)
      else
        {
          // We are in a plot widget that is a single-graph plot widget, the
          // situation is easier:

          // stanza = craftAnalysisStanza();
          stanza = craftAnalysisStanza();

          if(stanza.isEmpty())
            {
              parentWnd->statusBar()->showMessage(
                "Failed to craft an analysis stanza. "
                "Please set the analysis preferences.");

              event->accept();
              return;
            }
          else
            {

              // Send the stanza to the console window, so that we can make a
              // copy
              // paste.

              emit recordAnalysisStanza(stanza, m_plottingColor);
            }
        }

      event->accept();
      return;
    }
  // End of
  // if(event->key() == Qt::Key_Space)
  else if(event->key() == Qt::Key_Tab)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "MassSpecPlotWidget::keyReleaseEvent
      // AbstractPlotWidgetevent->key() == Qt::Key_Tab";

      if(!m_isMultiGraph)
        {
          // We do not want to handle the TAB key because it will try to cycle
          // into the
          // various plot widgets of the lower part of the window.

          // qDebug() << __FILE__ << __LINE__
          // << "Mass spec plot widget is not multi-graph";

          event->accept();
          return;
        }

      // When this key event occurs in a multigraph plot widget, that means
      // that the user wants to cycle in the graphs, most probably because she
      // is in an analysis procedure.
      //
      // The OpenSpectraDlg list widget's items reflect all the mass specra
      // opened. For each open spectrum, there is a corresponding list widget
      // item, colored the same as the corresponding tic chrom plot widget
      // that show the TIC chromatogram.
      //
      // Note that there might be list widget items that we are not going to
      // deal with because they have no mass spec plot widget (and thus not
      // multi-graph plot widget graph neither) because the user has opened
      // the mass file but not yet performed any mass spectrum combination.
      //
      // Also, the TAB key cycling only works when one list widget item is
      // selected. If there are more than one item selected, then deselect all
      // the item but the first of the selected ones, to seed the cycling
      // conditions.

      int iCount  = mainWindow->mp_openSpectraDlg->itemCount();
      int siCount = mainWindow->mp_openSpectraDlg->selectedItemCount();

      AbstractPlotWidget *ticChromPlotWidget;

      if(!siCount)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Not a single list widget item is selected. There are"
          // << iCount << "items in the list widget";

          // Not a single list widget item is selected. We want to select the
          // first one that has a corresponding graph in this multi-graph plot
          // widget.
          for(int iter = 0; iter < iCount; ++iter)
            {
              ticChromPlotWidget =
                mainWindow->mp_openSpectraDlg->plotWidgetAt(iter);

              // Get to the corresponding mass spec plot widget.
              // Because a single tic chrom plot widget might generated many
              // different mass spec plot widgets, we get a list:
              // QList<AbstractPlotWidget *>massSpecPlotWidgetList =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.values(ticChromPlotWidget);

              QList<AbstractPlotWidget *> massSpecPlotWidgetList;
              mainWindow->m_dataPlotWidgetRelationer.targets(
                ticChromPlotWidget, &massSpecPlotWidgetList);

              // As long as there is at least one item, we are ok.
              if(massSpecPlotWidgetList.size())
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "List widget at index " << iter << "will be
                  // selected.";

                  // Select that item right away.
                  mainWindow->mp_openSpectraDlg->deselectAllPlotWidgetsButOne(
                    ticChromPlotWidget);
                  break;
                }
            }
        }
      else if(siCount == 1)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Only one item is selected in the list widget. There are"
          // << iCount << "items in the list widget";

          // There is only one item that is selected. Just get to the next
          // one,
          // if possible, or cycle back to the next possible starting from the
          // top.
          int siIndex;
          ticChromPlotWidget =
            mainWindow->mp_openSpectraDlg->firstSelectedPlotWidget(&siIndex);


          // qDebug() << __FILE__ << __LINE__
          // << "The item that is selected is at index " << siIndex;

          // The item that is selected is at index siIndex. Now, iterate in
          // the
          // various items of the list widget, starting at index siIndex + 1,
          // or
          // 0 and check if the iterated item has a corresponding mass spec
          // window plot widget. If so, just select it.

          if(iCount == 1)
            {

              // qDebug() << __FILE__ << __LINE__
              // << "The is only one item in the list widget, thus nothing to
              // do.";

              // There is one item selected and only one item. Nothing to do.
              event->accept();
              return;
            }

          // There are multiple items, so iterate into each of them and see if
          // we can select it, if it has a corresponding plot widget here.
          // Start iterating at the next index, since we want to cycle through
          // the various items.

          // If the currently selected item is the last of the list widget,
          // then
          // loop over to the first.
          if(++siIndex > iCount - 1)
            siIndex = 0;

          // qDebug() << __FILE__ << __LINE__
          // << "Starting iteration in the list widget at index " << siIndex;

          int iter       = siIndex;
          bool hasLooped = false;

          while(true)
            {
              // We cannot use the for loop below, because we want to loop
              // over
              // the end of the widget list if siIndex is in the middle of it,
              // for example.
              // for(int iter = siIndex; iter < iCount; ++iter)

              // qDebug() << __FILE__ << __LINE__
              // << "Iterating with iter: " << iter;

              ticChromPlotWidget =
                mainWindow->mp_openSpectraDlg->plotWidgetAt(iter);

              // Get to the corresponding mass spec plot widget.
              // Because a single tic chrom plot widget might generated many
              // different mass spec plot widgets, we get a list:
              // QList<AbstractPlotWidget *>massSpecPlotWidgetList =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.values(ticChromPlotWidget);

              QList<AbstractPlotWidget *> massSpecPlotWidgetList;
              mainWindow->m_dataPlotWidgetRelationer.targets(
                ticChromPlotWidget, &massSpecPlotWidgetList);

              // As long as there is at least one item, we are ok.
              if(massSpecPlotWidgetList.size())
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "The item at index " << iter << "is going to be
                  // selected.";

                  // Select that item right away.
                  mainWindow->mp_openSpectraDlg->deselectAllPlotWidgetsButOne(
                    ticChromPlotWidget);
                  break;
                }

              // qDebug() << __FILE__ << __LINE__
              // << "Iterated in list widget item index " << iter;

              if(++iter >= iCount)
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "iter :" << iter << "is >= iCount, reset to 0";

                  iter = 0;

                  // We will need to know that we looped over the last widget
                  // list
                  // item, such that we can test if we are going past the
                  // first
                  // index that we iterated over.
                  hasLooped = true;

                  continue;
                }

              // But we do not want to loop forever:
              if(hasLooped && iter >= siIndex)
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "We looped already and iter is:" << iter << "so we
                  // break the loop.";

                  break;
                }
            }
          // End of
          // while(true)

          // qDebug() << __FILE__ << __LINE__
          // << "Out of for loop iterating in the list widget items.";

          event->accept();
          return;
        }
      else
        {
          // The number of selected items is more than one. We want to only
          // have
          // one item selected, that has a corresponding mass spec plot
          // widget.
          // Then, next TAB key strokes will cycle through the list widget
          // items.
          int siIndex =
            mainWindow->mp_openSpectraDlg->firstSelectedPlotWidgetIndex();

          // If the currently selected item is the last of the list widget,
          // then
          // loop over to the first.
          if(++siIndex >= iCount - 1)
            siIndex = 0;

          for(int iter = siIndex; iter < iCount; ++iter)
            {
              ticChromPlotWidget =
                mainWindow->mp_openSpectraDlg->plotWidgetAt(iter);

              // Get to the corresponding mass spec plot widget.
              // Because a single tic chrom plot widget might generated many
              // different mass spec plot widgets, we get a list:
              // QList<AbstractPlotWidget *>massSpecPlotWidgetList =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.values(ticChromPlotWidget);

              QList<AbstractPlotWidget *> massSpecPlotWidgetList;
              mainWindow->m_dataPlotWidgetRelationer.targets(
                ticChromPlotWidget, &massSpecPlotWidgetList);

              // As long as there is at least one item, we are ok.
              if(massSpecPlotWidgetList.size())
                {
                  // Select that item right away.
                  mainWindow->mp_openSpectraDlg->deselectAllPlotWidgetsButOne(
                    ticChromPlotWidget);
                  break;
                }
            }

          event->accept();
          return;
        }
      // End of else that is, there are more than one list widget item
      // selected.
    }
  // End of else if(event->key() == Qt::Key_Tab)
  else if(event->key() == Qt::Key_L)
    {
      // The user wants to copy the current cursor location to the console
      // window, such that it remains available after having moved the cursor.
      // The handling of the text creation is different in these two
      // situations:
      //
      // 1. the plot widget where this event occurred is a conventional
      // mono-graph plot widget that sits in the lower part of the window. In
      // this case, the color to be used to display the data label in the
      // console window is easily gotten from the graph's pen.
      //
      // 2. the plot widget is a multi-graph plot widget that sits in the
      // upper part of the multi plot window. In that case, it is more
      // difficult to know what is the proper color to use, because we need to
      // go to the open spectra dialog and get the list of selected files that
      // initially contained the data plotted. We want to know what graph
      // corresponds to what list widget item that is selected (or not) in the
      // open mass spectra dialog window (see MainWindow).

      QString label;
      if(m_isMultiGraph)
        {

          // The event occurred in the multi-graph plot widget where many graphs
          // are displayed. We need to craft a label for each graph that
          // replicates the data of a plot that is proxied in the list widget of
          // the OpenSpectraDlg. Only handle the spectra of which the list
          // widget item is currently selected and for which the corresponding
          // mass multi-graph plot is visible.

          for(int iter = 0; iter < graphCount(); ++iter)
            {
              QCPGraph *iterGraph = graph(iter);

              // Only work on this graph if it is visible:

              if(!iterGraph->visible())
                continue;

              // Get a handle to the conventional plot widget of which this
              // multi-graph plot widget's graph is a replica.

              AbstractPlotWidget *massSpecPlotWidget =
                parentWnd->monoGraphPlot(iterGraph);

              // Now use that pointer to get a handle to the tic chromatogram
              // plot
              // widget whence the data in this plot widget came:
              // AbstractPlotWidget
              // *ticChromPlotWidget =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.key(massSpecPlotWidget);

              DataPlotWidgetRelation *p_rootRelation = Q_NULLPTR;
              AbstractPlotWidget *ticChromPlotWidget =
                mainWindow->m_dataPlotWidgetRelationer.rootPlotWidget(
                  massSpecPlotWidget, &p_rootRelation);

              if(ticChromPlotWidget == nullptr)
                {
                  qFatal(
                    "Fatal error at %s@%d -- %s. "
                    "Cannot be that a multiGraph plot widget has no root plot "
                    "widget."
                    "Program aborted.",
                    __FILE__,
                    __LINE__,
                    __FUNCTION__);
                }

              // At this point, we know what plot widget in the Tic chrom window
              // is the root plot of the graph currently iterated into. We need
              // to
              // know if that plot widget has a corresponding item in the open
              // spectrum dialog list widget that is currently selected.

              if(mainWindow->mp_openSpectraDlg->isPlotWidgetSelected(
                   ticChromPlotWidget))
                {
                  // We can craft the label text to send to the console
                  // window, and
                  // that, with the proper color.

                  label = QString("MZ(%1,%2)\n")
                            .arg(m_lastMousedPlotPoint.x())
                            .arg(m_lastMousedPlotPoint.y());

                  if(label.isEmpty())
                    {
                      parentWnd->statusBar()->showMessage(
                        "Failed to craft a text label.");

                      event->accept();
                      return;
                    }
                  else
                    {
                      // Send the label to the console window, so that we can
                      // make a copy
                      // paste.
                      mainWindow->logConsoleMessage(label, m_plottingColor);
                    }
                }
            }
          // End of
          // for(int iter = 0; iter < graphCount(); ++iter)
        }
      // End of
      // if(m_isMultiGraph)
      else
        {
          // We are in a plot widget that is a single-graph plot widget, the
          // situation is easier:

          label = QString("MZ(%1,%2)\n")
                    .arg(m_lastMousedPlotPoint.x())
                    .arg(m_lastMousedPlotPoint.y());

          if(label.isEmpty())
            {
              parentWnd->statusBar()->showMessage(
                "Failed to craft a text label. ");

              event->accept();
              return;
            }
          else
            {
              // Send the label to the console window, so that we can make a
              // copy
              // paste.

              mainWindow->logConsoleMessage(label, m_plottingColor);
            }
        }

      event->accept();
      return;
    }
  // End of
  // else if(event->key() == Qt::Key_L)

  // Now let the base class do its work if needed.
  AbstractPlotWidget::keyReleaseEvent(event);
}


//! Return the arbitrary integration type that this plot widget can handle
int
MassSpecPlotWidget::arbitraryIntegrationType()
{
  return IntegrationType::MZ_TO_XXX;
}


} // namespace msXpSmineXpert
