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


/////////////////////// Stdlib includes
#include <cmath>


/////////////////////// Qt includes
#include <QMouseEvent>
#include <QScrollBar>
#include <QGraphicsItem>
#include <QTransform>


/////////////////////// libmass includes
#include <libXpertMass/PolChemDef.hpp>
#include <libXpertMass/Coordinates.hpp>
#include <libXpertMass/CrossLink.hpp>


/////////////////////// Local includes
#include "SequenceEditorGraphicsView.hpp"
#include "MonomerCodeEvaluator.hpp"
#include "ChemEntVignette.hpp"
#include "ChemEntVignetteRenderer.hpp"


namespace MsXpS
{

namespace massxpert
{


SequenceEditorGraphicsView::SequenceEditorGraphicsView(
  SequenceEditorWnd *editorWnd)
  : mp_editorWnd(editorWnd)
{
  m_kbdShiftDown = false;
  m_kbdCtrlDown  = false;
  m_kbdAltDown   = false;

  m_ongoingMouseMultiSelection    = false;
  m_ongoingKeyboardMultiSelection = false;

  m_sequenceDrawn = false;

  m_requestedVignetteSize = 32;

  m_xScaleFactor = 1;
  m_yScaleFactor = 1;

  // These are pointers, and have to be 0ed.
  mpa_cursorRenderer = 0;
  mpa_cursorVignette = 0;

  mpa_selection = new SequenceSelection(this);

  mpa_monomerCodeEvaluator = 0;

  m_lastClickedVignette = 0;

  m_leftMargin = 64;

  // m_columns is often used a fractional denominator, cannot be 0.
  m_columns = 1;

  setMouseTracking(true);
  setFocusPolicy(Qt::StrongFocus);

  QScrollBar *vScrollBar = verticalScrollBar();
  connect(vScrollBar,
          SIGNAL(actionTriggered(int)),
          this,
          SLOT(vScrollBarActionTriggered(int)));

  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  // setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
}


SequenceEditorGraphicsView::~SequenceEditorGraphicsView()
{
  // Make sure we first remove all selection items from the view.
  delete mpa_selection;
  delete mpa_cursorRenderer;
  delete mpa_monomerCodeEvaluator;

  return;
}


int
SequenceEditorGraphicsView::requestedVignetteSize()
{
  return m_requestedVignetteSize;
}


int
SequenceEditorGraphicsView::columns()
{
  return m_columns;
}


int
SequenceEditorGraphicsView::rows()
{
  return m_rows;
}


int
SequenceEditorGraphicsView::leftMargin()
{
  return m_leftMargin;
}


void
SequenceEditorGraphicsView::setPolymer(libXpertMass::Polymer *polymer)
{
  Q_ASSERT(polymer);

  mp_polymer = polymer;
}


const libXpertMass::Polymer *
SequenceEditorGraphicsView::polymer() const
{
  return mp_polymer;
}


void
SequenceEditorGraphicsView::setEditorWnd(SequenceEditorWnd *editorWnd)
{
  Q_ASSERT(editorWnd);

  mp_editorWnd = editorWnd;
}


void
SequenceEditorGraphicsView::setMonomerCodeEvaluator(
  MonomerCodeEvaluator *evaluator)
{
  Q_ASSERT(evaluator);

  mpa_monomerCodeEvaluator = evaluator;
}


void
SequenceEditorGraphicsView::updateColumns()
{
  QRect viewRect = rect();

  m_columns = static_cast<int>(
    (viewRect.width() - m_leftMargin - m_requestedVignetteSize) /
    m_requestedVignetteSize);

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "viewRect.width:" << viewRect.width()
  // 	      << "columns:" << m_columns;
}


void
SequenceEditorGraphicsView::updateVScrollBar()
{
  QScrollBar *scrollBar = verticalScrollBar();
  scrollBar->setSingleStep(m_requestedVignetteSize);

  QRect viewRect = QWidget::rect();

  scrollBar->setPageStep(viewRect.height());
}


void
SequenceEditorGraphicsView::updateSceneRect()
{
  QRect viewRect = rect();

  scene()->setSceneRect(
    0, 0, viewRect.width(), ++m_rows * m_requestedVignetteSize);
}


int
SequenceEditorGraphicsView::vignetteIndex(const QPointF &point)
{
  QPointF local = point;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Enter in vignetteIndex"
  // 	      << point;

  if(static_cast<int>(local.x()) < m_leftMargin)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 	      << "static_cast<int>(local.x()) < m_leftMargin";

      return -1;
    }

  if(local.x() > m_leftMargin + m_columns * m_requestedVignetteSize)
    {
      // 	  qDebug() << __FILE__ << __LINE__
      // 		    << "local.x() > m_leftMargin + "
      // 	    "m_columns * m_requestedVignetteSize";

      return -1;
    }

  if(local.y() < 0)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 	      << "local.y() < 0";

      return -1;
    }

  if(local.y() > m_rows * m_requestedVignetteSize)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 	      << "local.y() > m_rows * m_requestedVignetteSize"
      // 		  << "local.y():" << local.y()
      // 		  << "m_rows * m_requestedVignetteSize"
      // 		  << m_rows * m_requestedVignetteSize;

      return -1;
    }

  int row    = static_cast<int>(local.y() / m_requestedVignetteSize);
  int yCount = row * m_columns;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "x--y:"
  // 	      << local.x() << "--" << local.y();

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "m_requestedVignetteSize: m_columns:"
  // 	      << m_requestedVignetteSize << m_columns;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "row:" << row;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "yCount:" << yCount;


  double xRatio = (local.x() - m_leftMargin) / m_requestedVignetteSize;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "xRatio:" << xRatio;

  double intPart;

  double fracPart = modf(xRatio, &intPart);

  //       qDebug() << __FILE__ << __LINE__
  // 	      << "intPart -- fracPart" << intPart << fracPart;


  if(fracPart > 0.9)
    ++intPart;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "intPart:" << intPart;

  int idx = static_cast<int>(intPart + yCount);

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "idx:" << idx;

  if(idx < 0)
    return -1;

  if(idx > mp_polymer->size())
    return -1;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Exit from vignetteIndex";

  return idx;
}


QPointF
SequenceEditorGraphicsView::vignetteLocation(int index,
                                             MxtCardinalDirection cardDir)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Enter in vignetteLocation with index:" << index;

  int localIndex = 0;

  if(index < 0)
    localIndex = 0;
  else if(index > mp_polymer->size())
    localIndex = mp_polymer->size() - 1;
  else
    localIndex = index;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "localIndex:" << localIndex;

  Q_ASSERT(m_columns);

  // row is an index
  double rowIndex = localIndex / m_columns;

  // column is a position
  int column = localIndex - (static_cast<int>(rowIndex) * m_columns);

  //   qDebug() << "localIndex -- column -- rowIndex"
  //  	    << localIndex << "--" << column << "--" << rowIndex;

  double x = 0;
  double y = 0;


  if(cardDir == CENTER)
    {
      x = m_leftMargin + ((column + 0.5) * m_requestedVignetteSize);
      y = rowIndex * m_requestedVignetteSize + (m_requestedVignetteSize / 2);
    }
  else if(cardDir == NORTH_WEST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize);
      y = rowIndex * m_requestedVignetteSize;
    }
  else if(cardDir == NORTH)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          (m_requestedVignetteSize / 2);
      y = rowIndex * m_requestedVignetteSize;
    }
  else if(cardDir == NORTH_EAST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          m_requestedVignetteSize;
      y = rowIndex * m_requestedVignetteSize;
    }
  else if(cardDir == EAST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          m_requestedVignetteSize;
      y = rowIndex * m_requestedVignetteSize + (m_requestedVignetteSize / 2);
    }
  else if(cardDir == SOUTH_EAST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          m_requestedVignetteSize;
      y = rowIndex * m_requestedVignetteSize + m_requestedVignetteSize;
    }
  else if(cardDir == SOUTH)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          (m_requestedVignetteSize / 2);
      y = rowIndex * m_requestedVignetteSize + m_requestedVignetteSize;
    }
  else if(cardDir == SOUTH_WEST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize);
      y = rowIndex * m_requestedVignetteSize + m_requestedVignetteSize;
    }
  else if(cardDir == WEST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          (m_requestedVignetteSize / 2);
      y = rowIndex * m_requestedVignetteSize + (m_requestedVignetteSize / 2);
    }


  if(localIndex >= mp_polymer->size() - 1)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "vignetteLocation:"
      // 		  << "m_columns:" << m_columns
      // 		  << "m_rows:" << m_rows;

      // In this case, IF the vignette is located at the right
      // margin of the last row(that is the sequence vignettes
      // cover a perfect rectangle and the vignette is the bottom
      // right one), then y cannot be greater than
      //
      //(m_rows * m_requestedVignetteSize).
      //
      // The point is that when selecting a sequence with the
      // keyboard, the selection goes right to the beginning of the
      // next line, and thus, if asking CENTER, y becomes too
      // big(see && column == 0 below to indicate that the cursor
      // passes to next line).
      //
      // We thus have to track the case of the vignette at 'index'
      // being at the right bottom corner.
      //
      // Note that this is only meaningful if the vignette is for
      // the last monomer in the polymer sequence, otherwise it
      // could not be the last vignette in the sequence and located
      // at the right bottom corner of the sequence rendering area.

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "vignetteLocation"
      // 		  << "rowIndex:" << rowIndex
      // 		  << "column:" << column;

      if(rowIndex == m_rows && column == 0)
        {
          // The vignette is actually at the last row of the display
          // and at the last column. That is, it is located at the
          // bottom right corner of a rectangular sequence display
          // area.

          if(cardDir == CENTER)
            {
              y = m_rows * m_requestedVignetteSize;

              // 		qDebug() << "vignetteLocation - QPointF's y set to:" << y;
            }
        }
    }


  QPointF pointF(x, y);

  //  qDebug() << "vignettePosition:" << pointF;

  return pointF;
}


void
SequenceEditorGraphicsView::setSelection(int start,
                                         int end,
                                         bool multiRegionSelection,
                                         bool multiSelectionRegion)
{
  int localStart;
  int localEnd;

  if(start >= end)
    {
      localStart = end;
      localEnd   = start;
    }
  else
    {
      localStart = start;
      localEnd   = end;
    }

  if(localStart < 0)
    localStart = 0;

  if(localEnd >= mp_polymer->size())
    localEnd = mp_polymer->size();

  if(!multiRegionSelection)
    mpa_selection->deselectRegions();

  mpa_selection->selectRegion(
    localStart, localEnd, multiRegionSelection, multiSelectionRegion);

  m_lastClickedVignette = localEnd + 1;

  positionCursor();

  QScrollBar *vScrollBar = verticalScrollBar();

  int visibleRows = rect().height() / m_requestedVignetteSize;
  int lines       = m_lastClickedVignette / m_columns;

  vScrollBar->setValue((lines + 1) * m_requestedVignetteSize -
                       visibleRows * m_requestedVignetteSize);

  mp_editorWnd->updateSelectedSequenceMasses();

  // Finally update the selection clipboard
  mp_editorWnd->clipboardCopy(QClipboard::Selection);
}


void
SequenceEditorGraphicsView::setSelection(
  const libXpertMass::Coordinates &coordinates,
  bool multiRegionSelection,
  bool multiSelectionRegion)
{
  setSelection(coordinates.start(),
               coordinates.end(),
               multiRegionSelection,
               multiSelectionRegion);
}


void
SequenceEditorGraphicsView::setSelection(
  const libXpertMass::CoordinateList &coordinateList,
  bool multiRegionSelection,
  bool multiSelectionRegion)
{
  for(int iter = 0; iter < coordinateList.size(); ++iter)
    {
      libXpertMass::Coordinates *coordinates = coordinateList.at(iter);

      setSelection(coordinates->start(),
                   coordinates->end(),
                   multiRegionSelection,
                   multiSelectionRegion);
    }
}


void
SequenceEditorGraphicsView::resetSelection()
{
  mpa_selection->deselectRegions();

  m_selectionFirstIndex  = -1;
  m_selectionSecondIndex = -1;

  m_selectionFirstPoint  = QPointF(0, 0);
  m_selectionSecondPoint = QPointF(0, 0);

  mp_editorWnd->updateSelectedSequenceMasses();
}


void
SequenceEditorGraphicsView::resetSelectionButLastRegion()
{
  // Deselect all the selected regions, but the lastly selected
  // region.
  mpa_selection->deselectRegionsButLast();

  if(mpa_selection->regionSelectionCount())
    {
      // If there was actually one selection left, then set the cursor
      // to be at the end of that selection.

      positionCursor(*(mpa_selection->regionSelectionList().last()));
    }
}


void
SequenceEditorGraphicsView::resetMultiSelectionRegionsButFirstSelection()
{
  // Deselect all the selected regions, but the lastly selected
  // region.
  mpa_selection->deselectMultiSelectionRegionsButFirstSelection();
}


bool
SequenceEditorGraphicsView::selectionIndices(libXpertMass::CoordinateList *coordList)
{
  // The coordList should be empty. If not we empty it right away.

  if(coordList == nullptr)
    qFatal("Programming error. Pointer cannot be nullptr.");

  if(!coordList->isEmpty())
    coordList->empty();

  // At this point we should ask the selection to provide the info.

  bool res = mpa_selection->selectionIndices(coordList);

  if(!res)
    {
      // There is no real selection, thus, all we have to do is
      // construct a fake libXpertMass::Coordinates with the pseudo-selection,
      // that is a pseudo-region from start to last clicked
      // vignette. Remember, however, that the lastClickedVignette
      // index is one monomer vignette right of the actual vignette
      //(because the selection mark drawing functions require
      // this).
      libXpertMass::Coordinates *coordinates =
        new libXpertMass::Coordinates(0, lastClickedVignette() - 1);

      if(coordList)
        coordList->append(coordinates);
    }

  // However, return the real result so that caller knows if there
  // was a real selection or if the returned libXpertMass::Coordinates instance is
  // for the pseudo-selection from start of sequence to the last
  // clicked vignette.

  return res;
}


void
SequenceEditorGraphicsView::setOngoingMouseMultiSelection(bool value)
{
  m_ongoingMouseMultiSelection = value;
}


// SCENE-DRAWING FUNCTIONS /////////////////////////////////////////////
bool
SequenceEditorGraphicsView::renderCursor()
{
  //   qDebug() << __FILE__ << __LINE__ << "Entering renderCursor()";

  QString filePath =
    mp_editorWnd->polChemDefRendering()->getPolChemDef()->getXmlDataDirPath() +
    "/cursor.svg";

  //   qDebug() << __FILE__ << __LINE__ << "filePath" << filePath;

  mpa_cursorRenderer = new QSvgRenderer(filePath);

  if(!mpa_cursorRenderer->isValid())
    return false;

  mpa_cursorVignette = new QGraphicsSvgItem();

  mpa_cursorVignette->setSharedRenderer(mpa_cursorRenderer);

  scene()->addItem(mpa_cursorVignette);

  QRectF itemRectF = mpa_cursorVignette->boundingRect();

  // QMatrix(double m11, double m12, double m21, double m22, double
  // dx, double dy). A QMatrix object contains a 3 x 3 matrix. The
  // dx and dy elements specify horizontal and vertical
  // translation. The m11 and m22 elements specify horizontal and
  // vertical scaling. And finally, the m21 and m12 elements
  // specify horizontal and vertical shearing.

  double xScale = m_requestedVignetteSize / itemRectF.width();
  double yScale = m_requestedVignetteSize / itemRectF.height();

  //       qDebug() << "xScale - yScale:"
  // 		<< xScale << "-" << yScale;

  // Deprecated since 5.14.2 or earlier.
  // QMatrix matrix(xScale, 0, 0, yScale, 0, 0);

  QTransform matrix(xScale, 0, 0, yScale, 0, 0);

  mpa_cursorVignette->setTransform(matrix);

  scene()->update();

  //   qDebug() << __FILE__ << __LINE__ << "Exiting renderCursor()";

  return true;
}


bool
SequenceEditorGraphicsView::scaleCursor()
{
  if(!mpa_cursorVignette)
    return renderCursor();

  QRectF itemRectF = mpa_cursorVignette->boundingRect();

  // QMatrix(double m11, double m12, double m21, double m22, double
  // dx, double dy). A QMatrix object contains a 3 x 3 matrix. The
  // dx and dy elements specify horizontal and vertical
  // translation. The m11 and m22 elements specify horizontal and
  // vertical scaling. And finally, the m21 and m12 elements
  // specify horizontal and vertical shearing.

  double xScale = m_requestedVignetteSize / itemRectF.width();
  double yScale = m_requestedVignetteSize / itemRectF.height();

  //       qDebug() << "xScale - yScale:"
  // 		<< xScale << "-" << yScale;

  // Deprecated since 5.14.2 or earlier.
  // QMatrix matrix(xScale, 0, 0, yScale, 0, 0);

  QTransform matrix(xScale, 0, 0, yScale, 0, 0);

  mpa_cursorVignette->setTransform(matrix);

  scene()->update();

  return true;
}


bool
SequenceEditorGraphicsView::positionCursor(const QPointF &pos,
                                           MxtCursorLinePosition linePos)
{
  if(!mpa_cursorVignette)
    if(!renderCursor())
      return false;

  if(linePos == END_OF_LINE)
    {
      if(pos.x() == m_leftMargin && pos.y() >= m_requestedVignetteSize)
        {
          // Do not go to the left of first vignette of next row simply
          // because we are right of the last vignette in the row. That's
          // not intuitive.
          mpa_cursorVignette->setPos(m_leftMargin +
                                       m_columns * m_requestedVignetteSize -
                                       (m_requestedVignetteSize / 2),
                                     pos.y() - m_requestedVignetteSize);
          return true;
        }
    }

  mpa_cursorVignette->setPos(pos.x() - (m_requestedVignetteSize / 2), pos.y());

  return true;
}


bool
SequenceEditorGraphicsView::positionCursor(double x,
                                           double y,
                                           MxtCursorLinePosition linePos)
{
  return positionCursor(QPointF(x, y), linePos);
}


bool
SequenceEditorGraphicsView::positionCursor(int index,
                                           MxtCursorLinePosition linePos)
{
  return positionCursor(vignetteLocation(index), linePos);
}


bool
SequenceEditorGraphicsView::positionCursor(MxtCursorLinePosition linePos)
{
  return positionCursor(vignetteLocation(m_lastClickedVignette), linePos);
}


bool
SequenceEditorGraphicsView::positionCursor(
  const RegionSelection &regionSelection,
  [[maybe_unused]] MxtCursorLinePosition linePos)
{
  // We should place the cursor at the m_endIndex of the
  // regionSelection. Note that because we want the cursor to be
  // located RIGHT of the last vignette of the selection, we
  // increment the endIndex by one.
  m_lastClickedVignette = regionSelection.endIndex() + 1;
  return positionCursor();
}


ChemEntVignette *
SequenceEditorGraphicsView::renderVignette(const libXpertMass::Monomer &monomer)
{
  ChemEntVignetteRenderer *monomerRenderer = 0;
  ChemEntVignetteRenderer *modifRenderer   = 0;

  Q_ASSERT(mp_polymer);
  PolChemDefRendering *polChemDefRendering =
    mp_editorWnd->polChemDefRendering();
  Q_ASSERT(polChemDefRendering);

  QString monomerCode = monomer.code();
  Q_ASSERT(!monomerCode.isEmpty());

  // qDebug() << "Asking renderer for monomer code:" << monomerCode;

  monomerRenderer = polChemDefRendering->chemEntVignetteRenderer(monomerCode);

  if(!monomerRenderer)
    {
      //       qDebug() << __FILE__ << __LINE__
      // 		<< "Newing renderer for monomer code:" << monomerCode;

      monomerRenderer =
        polChemDefRendering->newMonomerVignetteRenderer(monomerCode);

      if(!monomerRenderer)
        {
          qDebug() << "Failed to make svg renderer for monomer." << monomerCode;
          abort();
        }

      //       qDebug() << __FILE__ << __LINE__
      // 		<< "Got new renderer:" << monomerRenderer;
    }
  else
    {
      //       qDebug() << __FILE__ << __LINE__
      // 		<< "Got renderer from hash:" << monomerRenderer;
    }

  // Ok, we have done the main work.

  ChemEntVignette *monomerVignette = new ChemEntVignette();
  monomerVignette->setSharedRenderer(monomerRenderer);
  monomerVignette->setZValue(1);

  scene()->addItem(monomerVignette);

  // Now, make sure that we test if the monomer is modified, if so
  // render the modif(s).

  if(monomer.isModified())
    {
      // Iterate in the list of modifications...
      for(int iter = 0; iter < monomer.modifList()->size(); ++iter)
        {
          libXpertMass::Modif *modif = monomer.modifList()->at(iter);

          QString modifName = modif->name();

          // qDebug() << __FILE__ << __LINE__
          // << "Asking renderer for modif name:" << modifName;

          modifRenderer =
            polChemDefRendering->chemEntVignetteRenderer(modifName);

          if(!modifRenderer)
            {
              // qDebug() << __FILE__ << __LINE__
              // << "Newing renderer for modif name:" << modifName;

              modifRenderer =
                polChemDefRendering->newModifVignetteRenderer(modifName);

              if(!modifRenderer)
                {
                  qDebug() << "Failed to make svg renderer for modif."
                           << modifName;

                  abort();
                }

              // qDebug() << __FILE__ << __LINE__
              // << "Got new renderer:" << modifRenderer;
            }
          else
            {
              // qDebug() << __FILE__ << __LINE__
              // << "Got renderer from hash:" << modifRenderer;
            }

          // At this point we actually have a modifRenderer for the
          // currently iterated modif.
          ChemEntVignette *modifVignette = new ChemEntVignette(monomerVignette);
          modifVignette->setSharedRenderer(modifRenderer);

          modifVignette->setName(QString("MODIF"));
          modifVignette->setOwner(modif);
          modifVignette->setZValue(2);
        }
    }

  // Finally we can update the scene.

  scene()->update(monomerVignette->boundingRect());

  return monomerVignette;
}


bool
SequenceEditorGraphicsView::modifyVignetteAt(int index, libXpertMass::Modif *modif)
{
  Q_ASSERT(mp_polymer);
  PolChemDefRendering *polChemDefRendering =
    mp_editorWnd->polChemDefRendering();
  Q_ASSERT(polChemDefRendering);

  Q_ASSERT(index >= 0 && index < m_monomerVignetteList.size());

  Q_ASSERT(modif);

  QString modif_ptr_str(QString("%1").arg((quintptr)modif));

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "modif ptr str:" << modif_ptr_str
  //<< "modif ptr:" << modif;

  // Get a pointer to the vignette to modify at index 'index'.
  ChemEntVignette *monomerVignette = m_monomerVignetteList.at(index);
  Q_ASSERT(monomerVignette);

  QString name = modif->name();
  Q_ASSERT(!name.isEmpty());

  // Use the entity name to create a renderer for that entity.
  ChemEntVignetteRenderer *renderer =
    polChemDefRendering->chemEntVignetteRenderer(name);

  if(!renderer)
    {
      // A renderer for that modification did not exist already. We
      // have to create one proper.
      renderer = polChemDefRendering->newModifVignetteRenderer(name);

      if(!renderer)
        {
          qDebug() << "Failed to make svg renderer for modif:" << name;

          abort();
        }
    }

  ChemEntVignette *modifVignette = new ChemEntVignette(monomerVignette);
  modifVignette->setSharedRenderer(renderer);

  modifVignette->setName(QString("MODIF"));
  modifVignette->setOwner(modif);

  modifVignette->setZValue(2);

  scene()->update(monomerVignette->boundingRect());

  return true;
}


bool
SequenceEditorGraphicsView::unmodifyVignetteAt(int index, libXpertMass::Modif *modif)
{
  Q_ASSERT(modif);

  Q_ASSERT(index >= 0 && index < m_monomerVignetteList.size());

  ChemEntVignette *monomerVignette = m_monomerVignetteList.at(index);
  Q_ASSERT(monomerVignette);

  QList<QGraphicsItem *> childrenList =
    static_cast<QGraphicsItem *>(monomerVignette)->childItems();

  for(int iter = 0; iter < childrenList.size(); ++iter)
    {
      ChemEntVignette *item =
        static_cast<ChemEntVignette *>(childrenList.at(iter));

      if(item->name() == "MODIF" && item->owner() == modif)
        {
          scene()->removeItem(item);

          delete(item);
        }
    }

  return true;
}


bool
SequenceEditorGraphicsView::crossLinkVignetteAt(
  int index, libXpertMass::CrossLinker *crossLinker)
{
  Q_ASSERT(mp_polymer);
  PolChemDefRendering *polChemDefRendering =
    mp_editorWnd->polChemDefRendering();
  Q_ASSERT(polChemDefRendering);

  Q_ASSERT(index >= 0 && index < m_monomerVignetteList.size());

  Q_ASSERT(crossLinker);

  // Get a pointer to the vignette to crossLink at index 'index'.
  ChemEntVignette *monomerVignette = m_monomerVignetteList.at(index);
  Q_ASSERT(monomerVignette);

  QString name = crossLinker->name();
  Q_ASSERT(!name.isEmpty());

  // Use the crossLinker name to create a renderer for that entity.
  ChemEntVignetteRenderer *renderer =
    polChemDefRendering->chemEntVignetteRenderer(name);

  if(!renderer)
    {
      // A renderer for that crossLink did not exist already. We
      // have to create one proper.
      renderer = polChemDefRendering->newCrossLinkerVignetteRenderer(name);

      if(!renderer)
        {
          qDebug() << "Failed to make svg renderer for cross-link:" << name;

          abort();
        }
    }

  ChemEntVignette *crossLinkVignette = new ChemEntVignette(monomerVignette);
  crossLinkVignette->setSharedRenderer(renderer);

  crossLinkVignette->setName(QString("CROSS_LINK"));
  crossLinkVignette->setOwner(crossLinker);

  crossLinkVignette->setZValue(2);

  scene()->update(monomerVignette->boundingRect());

  return true;
}


bool
SequenceEditorGraphicsView::uncrossLinkVignetteAt(
  int index, libXpertMass::CrossLinker *crossLinker)
{
  Q_ASSERT(crossLinker);

  Q_ASSERT(index >= 0);
  Q_ASSERT(index < m_monomerVignetteList.size());

  ChemEntVignette *monomerVignette = m_monomerVignetteList.at(index);
  Q_ASSERT(monomerVignette);

  QList<QGraphicsItem *> childrenList =
    static_cast<QGraphicsItem *>(monomerVignette)->childItems();

  for(int iter = 0; iter < childrenList.size(); ++iter)
    {
      ChemEntVignette *item =
        static_cast<ChemEntVignette *>(childrenList.at(iter));

      if(item->name() == "CROSS_LINK" && item->owner() == crossLinker)
        {
          scene()->removeItem(item);

          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Removed cross-linker from scene:"
          // 		      << crossLinker->name();

          delete(item);
        }
    }

  return true;
}


bool
SequenceEditorGraphicsView::removeVignetteAt(int index)
{
  Q_ASSERT(index >= 0 && index < m_monomerVignetteList.size());

  ChemEntVignette *monomerVignette = m_monomerVignetteList.takeAt(index);
  Q_ASSERT(monomerVignette);

  scene()->removeItem(monomerVignette); // And all the children.

  delete(monomerVignette);

  return true;
}


bool
SequenceEditorGraphicsView::renderVignettes()
{
  int iter = 0;

  mp_editorWnd->statusBar()->showMessage(tr("Rendering vignettes"));

  if(mp_polymer->size())
    mp_editorWnd->progressBar()->setRange(1, mp_polymer->size());
  else
    mp_editorWnd->progressBar()->setRange(1, 1);

  for(iter = 0; iter < mp_polymer->size(); ++iter)
    {
      mp_editorWnd->progressBar()->setValue(iter + 1);

      const libXpertMass::Monomer *monomer = mp_polymer->monomerList().at(iter);

      if(monomer == nullptr)
        qFatal("Monomer pointer cannot be nullptr.");

      // qDebug() << "Rendering vignette for monomer:" << monomer->name();

      mp_editorWnd->statusBar()->showMessage(tr("Rendering vignettes: "
                                                "%1 - %2")
                                               .arg(monomer->name())
                                               .arg(iter + 1));

      ChemEntVignette *monomerVignette = renderVignette(*monomer);

      Q_ASSERT(monomerVignette);

      m_monomerVignetteList.append(monomerVignette);

      //       qDebug() << "Rendered vignette for monomer:"
      // 		<< monomer->name().toAscii();
    }

  // At this point we have to render all the crossLinks of the
  // sequence... We know the polymer sequence has the list of
  // crossLinks.

  const libXpertMass::CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

  for(int iter = 0; iter < crossLinkList.size(); ++iter)
    {
      libXpertMass::CrossLink *crossLink = crossLinkList.at(iter);

      // For each monomer in the cross-link, make a vignette.

      for(int jter = 0; jter < crossLink->monomerList()->size(); ++jter)
        {
          const libXpertMass::Monomer *monomer = crossLink->monomerList()->at(jter);

          int index = mp_polymer->monomerIndex(monomer);

          if(!crossLinkVignetteAt(index, crossLink))
            {
              qDebug() << "Failed to render vignette for cross-linker:"
                       << crossLink->name();

              abort();
            }
        }
    }

  mp_editorWnd->statusBar()->showMessage(tr("Done rendering vignettes"));

  mp_editorWnd->progressBar()->setValue(1);
  mp_editorWnd->statusBar()->clearMessage();

  return true;
}


bool
SequenceEditorGraphicsView::scaleVignette(ChemEntVignette *vignette)
{
  Q_ASSERT(vignette);

  QRectF itemRectF = vignette->boundingRect();

  // QMatrix(double m11, double m12, double m21, double m22, double
  // dx, double dy). A QMatrix object contains a 3 x 3 matrix. The
  // dx and dy elements specify horizontal and vertical
  // translation. The m11 and m22 elements specify horizontal and
  // vertical scaling. And finally, the m21 and m12 elements
  // specify horizontal and vertical shearing.

  double xScale = m_requestedVignetteSize / itemRectF.width();
  double yScale = m_requestedVignetteSize / itemRectF.height();

  //       qDebug() << "xScale - yScale:"
  // 		<< xScale << "-" << yScale;

  // Deprecated since 5.14.2 or earlier.
  // QMatrix matrix(xScale, 0, 0, yScale, 0, 0);

  QTransform matrix(xScale, 0, 0, yScale, 0, 0);

  vignette->setTransform(matrix);

  return true;
}


bool
SequenceEditorGraphicsView::scaleVignettes()
{

  if(m_monomerVignetteList.size() < 1)
    return true;

  mp_editorWnd->statusBar()->showMessage(tr("Scaling vignettes"));

  int vignetteListSize = m_monomerVignetteList.size();

  if(vignetteListSize)
    mp_editorWnd->progressBar()->setRange(1, vignetteListSize);
  else
    mp_editorWnd->progressBar()->setRange(1, 1);

  for(int iter = 0; iter < vignetteListSize; ++iter)
    {
      mp_editorWnd->progressBar()->setValue(iter + 1);

      mp_editorWnd->statusBar()->showMessage(tr("Scaling vignettes: "
                                                "%1")
                                               .arg(iter + 1));

      ChemEntVignette *monomerVignette = m_monomerVignetteList.at(iter);
      scaleVignette(monomerVignette);
    }

  scene()->update();

  mp_editorWnd->statusBar()->showMessage(tr("Done scaling vignettes"));
  mp_editorWnd->progressBar()->setValue(1);

  return true;
}


bool
SequenceEditorGraphicsView::positionVignette(ChemEntVignette *vignette,
                                             int index)
{
  Q_ASSERT(vignette);
  Q_ASSERT(index >= 0 && index < mp_polymer->size());

  // row is an index
  double rowIndex = index / m_columns;

  // column is a position
  int column = index - (static_cast<int>(rowIndex) * m_columns);

  double x = 0;
  double y = 0;

  x = m_leftMargin + (column * m_requestedVignetteSize);
  y = rowIndex * m_requestedVignetteSize;

  vignette->setPos(x, y);

  return true;
}


bool
SequenceEditorGraphicsView::positionVignettes()
{
  double x = m_leftMargin;
  double y = 0;

  while(!m_labelList.isEmpty())
    {
      QGraphicsTextItem *label = m_labelList.takeFirst();

      scene()->removeItem(label);
      delete(label);
    }

  int vignetteListSize = m_monomerVignetteList.size();

  if(vignetteListSize < 1)
    {
      return true;
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Enter positionVignettes:"
  // 	      << "m_leftMargin:" << m_leftMargin
  // 	      << "Number of vignettes in the list:" << vignetteListSize;

  m_rows = 0;

  updateColumns();

  // Vignette positioning proper.

  int column = 0;

  for(int iter = 0; iter < vignetteListSize; ++iter)
    {
      ChemEntVignette *monomerVignette = m_monomerVignetteList.at(iter);

      if(column < m_columns)
        {
          if(!iter)
            {
              // We are at the beginning of first line.
              QString text = QString("%1").arg(iter + 1);

              QGraphicsTextItem *label = new QGraphicsTextItem(text, 0);

              // Now add the new item to the scene.
              scene()->addItem(label);

              label->setPos(0, y);

              // 		qDebug() << __FILE__ << __LINE__
              // 			  << "Positioned label at:" << QPointF(0,y);

              m_labelList.append(label);
            }

          monomerVignette->setPos(x, y);
        }
      else
        {
          column = 0;
          m_rows++;

          x = m_leftMargin;
          y += m_requestedVignetteSize;

          monomerVignette->setPos(x, y);

          // We are at the beginning of a new line.
          QString text = QString("%1").arg(iter + 1);

          QGraphicsTextItem *label = new QGraphicsTextItem(text, 0);

          scene()->addItem(label);

          label->setPos(0, y);

          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Positioned label at:" << QPointF(0,y);

          m_labelList.append(label);
        }

      x += m_requestedVignetteSize;
      ++column;
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Exit positionVignettes.";

  return true;
}


int
SequenceEditorGraphicsView::drawSequence(bool reset)
{
  // We have a pointer to the polymer for which we have to draw the
  // sequence in the scene. This is the first time the sequence is
  // drawn. So we must go through the creation of all the monomer
  // graphics items. However, we might have already a polymer
  // chemistry definition that holds a number of usable vignette renderer
  // instances.

  setCursor(Qt::WaitCursor);

  Q_ASSERT(mp_polymer);

  // qDebug() << "The sequence text is:" << *(mp_polymer->monomerText());

  if(reset)
    {
      // We are asked to reset all the GraphicsSvgItem in the scene.

      QList<QGraphicsItem *> itemList = scene()->items();

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

      m_monomerVignetteList.clear();
      m_labelList.clear();
    }

  bool ret = renderVignettes();
  Q_ASSERT(ret);

  ret = scaleVignettes();
  if(ret == 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  mp_editorWnd->statusBar()->showMessage(tr("Positioning vignettes"));
  ret = positionVignettes();
  if(ret == 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  mp_editorWnd->statusBar()->showMessage(tr("Done positioning vignettes"));

  renderCursor();
  m_selectionFirstIndex  = -1;
  m_selectionFirstPoint  = QPointF(0, 0);
  m_selectionSecondIndex = -1;
  m_selectionSecondPoint = QPointF(0, 0);
  positionCursor(0);

  updateSceneRect();

  updateVScrollBar();

  m_sequenceDrawn = true;

  setFocus();

  mp_editorWnd->updateWholeSequenceMasses();
  mp_editorWnd->updateSelectedSequenceMasses();

  mp_editorWnd->statusBar()->clearMessage();
  mp_editorWnd->statusBar()->showMessage(tr("Done drawing sequence"), 3000);
  mp_editorWnd->progressBar()->setValue(1);

  setCursor(Qt::ArrowCursor);

  return 0;
}


bool
SequenceEditorGraphicsView::updateSequence()
{
  setCursor(Qt::WaitCursor);

  positionVignettes();

  positionCursor();

  updateSceneRect();

  updateVScrollBar();

  setCursor(Qt::ArrowCursor);

  return true;
}


// POLYMER SEQUENCE-MODIFYING FUNCTIONS ///////////////////////////////
int
SequenceEditorGraphicsView::insertMonomerAtPoint(
  const libXpertMass::Monomer *monomer, bool freeze)
{
  Q_ASSERT(monomer);

  int res = insertMonomerAt(monomer, m_lastClickedVignette, freeze);
  mp_editorWnd->setWindowModified(true);
  mp_editorWnd->updateWindowTitle();

  return res;
}


int
SequenceEditorGraphicsView::insertMonomerAt(const libXpertMass::Monomer *monomer,
                                            int index,
                                            bool freeze)
{
  Q_ASSERT(monomer);

  Q_ASSERT(index > -1 && index <= mp_polymer->size());

  ChemEntVignette *monomerVignette = renderVignette(*monomer);

  Q_ASSERT(monomerVignette);

  bool ret = false;
  ret      = mp_polymer->insertMonomerAt(monomer, index);
  if(ret == 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  m_monomerVignetteList.insert(index, monomerVignette);
  scaleVignette(monomerVignette);

  ++m_lastClickedVignette;

  if(!freeze)
    {
      mp_editorWnd->setWindowModified(true);
      mp_editorWnd->updateWindowTitle();

      mp_editorWnd->updateWholeSequenceMasses();
      mp_editorWnd->updateSelectedSequenceMasses();

      updateSequence();

      QPointF pointF = vignetteLocation(m_lastClickedVignette);
      centerOn(pointF);

      scene()->update();
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "hScrollBar.";
  QScrollBar *hScrollBar = horizontalScrollBar();
  hScrollBar->setMinimum(0);
  hScrollBar->setValue(0);

  // Return the number of monomers added.
  return 1;
}


int
SequenceEditorGraphicsView::insertSequenceAtPoint(libXpertMass::Sequence &sequence)
{
  bool freeze = sequence.monomerList().size() > 10 ? true : false;
  int count   = 0;

  while(!sequence.monomerList().isEmpty())
    {
      // Reuse the monomer, we take it out of the list and we use it
      // to put into the sequence.
      const libXpertMass::Monomer *monomer =
        const_cast<QList<const libXpertMass::Monomer *> &>(sequence.monomerList())
          .takeFirst();

      insertMonomerAtPoint(monomer, freeze);
      ++count;
    }

  if(freeze)
    {
      if(!mp_editorWnd->isWindowModified())
        {
          mp_editorWnd->setWindowModified(true);
          mp_editorWnd->updateWindowTitle();
        }

      mp_editorWnd->updateWholeSequenceMasses();
      mp_editorWnd->updateSelectedSequenceMasses();

      updateSequence();

      QPointF pointF = vignetteLocation(m_lastClickedVignette);
      centerOn(pointF);

      scene()->update();
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "hScrollBar.";
  QScrollBar *hScrollBar = horizontalScrollBar();
  hScrollBar->setMinimum(0);
  hScrollBar->setValue(0);

  return count;
}


bool
SequenceEditorGraphicsView::prepareMonomerRemovalAt(int index)
{
  int polymerSize = mp_polymer->size();
  if(index >= polymerSize)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
  if(index <= -1)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // We have to check if the monomer is cross-linked, if so first
  // uncross-link all its partners.

  //     qDebug() << __FILE__ << __LINE__ << "Going to call at() with value"
  // 	      << index;

  const libXpertMass::Monomer *monomer = mp_polymer->at(index);

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "prepareMonomerRemovalAt:" << index
  // 	      << "Monomer is:" << monomer->name();

  const libXpertMass::CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

  QList<int> theList;

  // One monomer might be involved in more than one cross-link. Get
  // the list of the cross-links in which the monomer is involved.

  if(!crossLinkList.crossLinksInvolvingMonomer(monomer, &theList))
    return true;

  // At this point we know that the monomer is involved at least in
  // one crossLink. For each of the cross-links involving the
  // monomer, make sure we uncross-link all its partners.

  for(int iter = 0; iter < theList.size(); ++iter)
    {
      libXpertMass::CrossLink *crossLink = crossLinkList.at(theList.at(iter));

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "Monomer is involved in cross-link:" << crossLink->name()
      // 		  << "at index of crossLinkList:" << iter;

      for(int jter = 0; jter < crossLink->monomerList()->size(); ++jter)
        {
          const libXpertMass::Monomer *iterMonomer =
            crossLink->monomerList()->at(jter);

          int monomerIndex = mp_polymer->monomerIndex(iterMonomer);

          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Uncross-linking monomer:" << iterMonomer->name()
          // 		      << "at:" << monomerIndex;

          bool result = uncrossLinkVignetteAt(monomerIndex, crossLink);

          if(!result)
            {
              // 		qDebug() << __FILE__ << __LINE__
              // 			  << "Failed to uncross-link monomer:"
              // 			  << iterMonomer->name()
              // 			  << "at index:" << monomerIndex;

              return false;
            }
          else
            {
              // 		qDebug() << __FILE__ << __LINE__
              // 			  << "Successfully uncross-linked monomer";
            }
        }
    }

  return true;
}


int
SequenceEditorGraphicsView::removeMonomerAt(int index, bool freeze)
{
  int polymerSize = mp_polymer->size();

  Q_ASSERT(index < polymerSize);
  Q_ASSERT(index > -1);

  if(polymerSize < 1)
    return 0;

  bool result = prepareMonomerRemovalAt(index);
  if(result == 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  result = mp_polymer->removeMonomerAt(index);
  if(result == 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // Get the pointer to the corresponding monomer vignette, so that we
  // can remove it from the scene.

  removeVignetteAt(index);

  if(!freeze)
    {
      if(!mp_editorWnd->isWindowModified())
        {
          mp_editorWnd->setWindowModified(true);
          mp_editorWnd->updateWindowTitle();
        }

      mp_editorWnd->updateWholeSequenceMasses();
      mp_editorWnd->updateSelectedSequenceMasses();
    }

  // Return the number of monomers removed.
  return 1;
}


int
SequenceEditorGraphicsView::removeSelectedOligomer()
{
  // We only can remove a selected oligomer in non-multi region
  // selection mode. However, just to make sure, we use the lastly
  // selected region.

  libXpertMass::CoordinateList coordList;

  bool selectionPresent = selectionIndices(&coordList);

  if(!selectionPresent)
    return 0;

  libXpertMass::Coordinates *coord = coordList.last();

  return removeSequenceRegion(coord->start(), coord->end());
}


int
SequenceEditorGraphicsView::removeSequenceRegion(int start, int end)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "removeSequenceRegion with indices start--end:"
  // 	      << start << "--" << end;

  int count      = 0;
  int localStart = 0;
  int localEnd   = 0;

  if(start > end)
    {
      localStart = end;
      localEnd   = start;
    }
  else
    {
      localStart = start;
      localEnd   = end;
    }

  Q_ASSERT(localStart > -1);
  Q_ASSERT(localEnd < mp_polymer->size());

  // If the number of monomers to remove is greater than 10, then
  // freeze the sequence display.
  bool freeze = abs(localEnd - localStart) > 10 ? true : false;

  int iter = localEnd;

  while(iter >= localStart)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "Going to remove monomer at:" << iter;

      count += removeMonomerAt(iter, freeze);
      --iter;
    }

  resetSelection();

  m_lastClickedVignette = localStart;

  mp_editorWnd->updateWholeSequenceMasses();
  mp_editorWnd->updateSelectedSequenceMasses();

  mp_editorWnd->setWindowModified(true);
  mp_editorWnd->updateWindowTitle();

  updateSequence();

  QPointF pointF = vignetteLocation(m_lastClickedVignette);
  centerOn(pointF);

  // Return the number of monomers removed.
  return count;
}


////////////////////////////// SLOTS ///////////////////////////////
void
SequenceEditorGraphicsView::vScrollBarActionTriggered(int action)
{
  // Actions are QAbstractSlider::SliderSingleStepAdd,
  // SliderSingleStepSub, SliderPageStepAdd, SliderPageStepSub,
  // SliderToMinimum, SliderToMaximum, and SliderMove.

  QScrollBar *vScrollBar = verticalScrollBar();
  int curPos             = vScrollBar->sliderPosition();

  if(action == QAbstractSlider::SliderSingleStepAdd)
    vScrollBar->setSliderPosition(curPos + m_requestedVignetteSize);
  else if(action == QAbstractSlider::SliderPageStepAdd)
    vScrollBar->setSliderPosition(curPos + rect().height());

  if(action == QAbstractSlider::SliderSingleStepSub)
    vScrollBar->setSliderPosition(curPos - m_requestedVignetteSize);
  else if(action == QAbstractSlider::SliderPageStepSub)
    vScrollBar->setSliderPosition(curPos - rect().height());
}


bool
SequenceEditorGraphicsView::requestVignetteSize(int size)
{
  // Be careful that this function can be called by the parent
  // sequence editor window, while the sequence editor is being
  // initialized(readSettings()). If the sequence was never drawn,
  // we just return avec having set the m_requestedVignetteSize value
  // that will be used during the sequence drawing.

  m_requestedVignetteSize = size;

  if(!m_sequenceDrawn)
    return true;

  if(!scaleVignettes())
    return false;

  if(!positionVignettes())
    return false;

  scaleCursor();

  positionCursor();

  mpa_selection->reselectRegions();

  updateSceneRect();

  updateVScrollBar();

  QPointF pointF = vignetteLocation(m_lastClickedVignette);
  centerOn(pointF);

  return true;
}


int
SequenceEditorGraphicsView::lastClickedVignette()
{
  return m_lastClickedVignette;
}


//////////////////// EVENT HANDLING FUNCTIONS ///////////////////

void
SequenceEditorGraphicsView::focusOutEvent([[maybe_unused]] QFocusEvent *event)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "focusOutEvent";

  m_kbdShiftDown                  = false;
  m_kbdCtrlDown                   = false;
  m_kbdAltDown                    = false;
  m_ongoingKeyboardMultiSelection = false;
}


void
SequenceEditorGraphicsView::resizeEvent(QResizeEvent *event)
{
  // Be careful that this function can be called during setting up of
  // the widget, even if no sequence was drawn, and thus a number of
  // variables are not properly initialized. Thus, if no sequence was
  // drawn, we have to return.

  if(!m_sequenceDrawn)
    return;

  QGraphicsView::resizeEvent(event);

  positionVignettes();

  mpa_selection->reselectRegions();

  positionCursor();

  updateSceneRect();

  updateVScrollBar();

  QPointF pointF = vignetteLocation(m_lastClickedVignette);
  centerOn(pointF);
}


void
SequenceEditorGraphicsView::paintEvent(QPaintEvent *event)
{
  QGraphicsView::paintEvent(event);
}


void
SequenceEditorGraphicsView::mousePressEvent(QMouseEvent *event)
{
  mp_editorWnd->getsFocus();

  // We have to map to the whole scene coordinates
  QPointF pointF = mapToScene(event->pos());

  int index = vignetteIndex(pointF);

  if(index <= -1)
    {
      event->accept();
      return;
    }

  if(index > mp_polymer->size())
    {
      event->accept();
      return;
    }

  m_lastClickedVignette = index;

  bool multiRegionSelection = mp_editorWnd->isMultiRegionSelection();
  bool multiSelectionRegion = mp_editorWnd->isMultiSelectionRegion();

  if(event->buttons() & Qt::LeftButton)
    {
      if(!m_kbdCtrlDown || !multiRegionSelection)
        mpa_selection->deselectRegions();

      if(m_kbdShiftDown)
        {
          // Usually, in text editors, when the cursor is somewhere
          // and that the user presses the shift key while clicking
          // the mouse in some other place, it is considered that the
          // user wanted to select the text between the first cursor
          // position and the newly pointed position.  Thus we set the
          // second selection index to the present value.

          m_selectionSecondPoint = pointF;

          // Immediately draw the selection rectangle and set the
          // pressed position to be the next round's first selection
          // point.

          // qDebug() << __FILE__ << __LINE__
          //          << "m_selectionFirstPoint: "
          //          << m_selectionFirstPoint
          //          << "m_selectionSecondPoint: "
          //          << m_selectionSecondPoint;


          mpa_selection->selectRegion(m_selectionFirstPoint,
                                      m_selectionSecondPoint,
                                      multiRegionSelection,
                                      multiSelectionRegion);
        }

      m_selectionFirstPoint  = pointF;
      m_selectionSecondPoint = pointF;

      positionCursor(index);

      // Not required, really, seems.
      // m_mouseDragging = true;
    }

  if(event->buttons() & Qt::MiddleButton)
    {
      mpa_selection->deselectRegions();

      mp_editorWnd->clipboardPaste(QClipboard::Selection);
    }

  //   qDebug() <<__FILE__ << __LINE__
  //      << QTime::currentTime()
  //      << "mousePressEvent"
  // 	    << "selectionFirstIndex:" << m_selectionFirstIndex
  // 	    << "selectionSecondIndex:" << m_selectionSecondIndex
  // 	    << "m_selectionFirstPoint" << m_selectionFirstPoint
  // 	    << "m_selectionSecondPoint" << m_selectionSecondPoint;

  mp_editorWnd->updateSelectedSequenceMasses();

  event->accept();
}


void
SequenceEditorGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
  mp_editorWnd->getsFocus();

  // We have to map to the whole scene coordinates
  QPointF pointF = mapToScene(event->pos());

  int index = vignetteIndex(pointF);

  if(index > -1)
    m_lastClickedVignette = index;
  else
    {
      event->accept();
      return;
    }

  m_ongoingMouseMultiSelection = false;

  //   qDebug() <<__FILE__ << __LINE__
  // << "m_ongoingMouseMultiSelection set to:" <<
  //       m_ongoingMouseMultiSelection;


  //   qDebug() << "mouseReleaseEvent"
  // 	    << QTime::currentTime()
  //      << "selectionFirstIndex:" << m_selectionFirstIndex
  // 	    << "selectionSecondIndex:" << m_selectionSecondIndex
  // 	    << "m_selectionFirstPoint" << m_selectionFirstPoint
  // 	    << "m_selectionSecondPoint" << m_selectionSecondPoint;

  //    mpa_selection->debugSelectionPutStdErr();

  event->accept();
}

void
SequenceEditorGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
  mp_editorWnd->getsFocus();

  // If there is no sequence, then nothing to do.
  if(!mp_polymer->size())
    {
      event->accept();
      return;
    }

  // We have to map to the whole scene coordinates
  QPointF pointF = mapToScene(event->pos());

  int index = vignetteIndex(pointF);

  //     qDebug() << __FILE__ << __LINE__
  // 	      << QTime::currentTime()
  // 	      << "mouseMoveEvent" << "index:" << index;

  if(index < 0)
    {
      event->accept();
      return;
    }

  if(index > mp_polymer->size())
    {
      event->accept();
      return;
    }

  if(index + 1 < mp_polymer->size())
    mp_editorWnd->updateMonomerPosition(index + 1);
  else
    mp_editorWnd->updateMonomerPosition(mp_polymer->size());

  bool multiRegionSelection = mp_editorWnd->isMultiRegionSelection();
  bool multiSelectionRegion = mp_editorWnd->isMultiSelectionRegion();

  if(event->buttons() & Qt::LeftButton)
    {
      m_selectionSecondPoint = pointF;


      ////////////// Debugging ///////////////
      // 	int firstPointIndex = vignetteIndex(m_selectionFirstPoint);
      // 	int secondPointIndex = vignetteIndex(m_selectionSecondPoint);

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "selectRegion with indices:"
      // 		  << firstPointIndex << m_selectionFirstPoint
      // 		  << secondPointIndex << m_selectionSecondPoint;
      ////////////// Debugging ///////////////


      if(!m_kbdCtrlDown)
        {
          mpa_selection->deselectRegions();

          mpa_selection->selectRegion(m_selectionFirstPoint,
                                      m_selectionSecondPoint,
                                      multiRegionSelection,
                                      multiSelectionRegion);
        }
      else
        {
          // We should be incrementing the last selected region, and
          // not adding larger regions on top of the ones previously
          // selected. Thus remove the lastly selected region and
          // update the selected region.

          if(m_ongoingMouseMultiSelection)
            {
              mpa_selection->deselectLastRegion();
            }

          mpa_selection->selectRegion(m_selectionFirstPoint,
                                      m_selectionSecondPoint,
                                      multiRegionSelection,
                                      multiSelectionRegion);

          m_ongoingMouseMultiSelection = true;
        }

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "positioning cursor at index:" << index;

      positionCursor(index);

      if(event->pos().y() >= rect().height() - (m_requestedVignetteSize / 4))
        vScrollBarActionTriggered(QAbstractSlider::SliderSingleStepAdd);
      else if(event->pos().y() <= (m_requestedVignetteSize / 4))
        vScrollBarActionTriggered(QAbstractSlider::SliderSingleStepSub);

      mp_editorWnd->updateSelectedSequenceMasses();

      // Finally update the selection clipboard
      mp_editorWnd->clipboardCopy(QClipboard::Selection);
    }

  event->accept();
}

} // namespace massxpert

} // namespace MsXpS
