/* 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 includes
#include <QMouseEvent>
#include <QScrollBar>
#include <QGraphicsItem>
#include <QTransform>


/////////////////////// Local includes
#include "SequenceEditorGraphicsView.hpp"
#include "MonomerCodeEvaluator.hpp"
#include "../nongui/ChemEntVignette.hpp"
#include "../nongui/ChemEntVignetteRenderer.hpp"
#include "../nongui/PolChemDef.hpp"
#include "../nongui/Coordinates.hpp"
#include <cmath>


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(Polymer *polymer)
  {
    Q_ASSERT(polymer);

    mp_polymer = polymer;
  }


  const 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 Coordinates &coordinates,
                                           bool multiRegionSelection,
                                           bool multiSelectionRegion)
  {
    setSelection(coordinates.start(),
                 coordinates.end(),
                 multiRegionSelection,
                 multiSelectionRegion);
  }


  void
  SequenceEditorGraphicsView::setSelection(const CoordinateList &coordinateList,
                                           bool multiRegionSelection,
                                           bool multiSelectionRegion)
  {
    for(int iter = 0; iter < coordinateList.size(); ++iter)
      {
        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(CoordinateList *coordList)
  {
    // The coordList should be empty. If not we empty it right away.

    if(coordList && !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 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).
        Coordinates *coordinates =
          new 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 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->polChemDef()->dirPath() + "/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 Monomer &monomer)
  {
    ChemEntVignetteRenderer *monomerRenderer = 0;
    ChemEntVignetteRenderer *modifRenderer   = 0;

    QSize momentSize(0, 0);

    Q_ASSERT(mp_polymer);
    PolChemDef *polChemDef = mp_editorWnd->polChemDef();
    Q_ASSERT(polChemDef);


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

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

    monomerRenderer = polChemDef->chemEntVignetteRenderer(monomerCode);

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

        monomerRenderer = polChemDef->newMonomerVignetteRenderer(monomerCode);

        if(!monomerRenderer)
          {
            qDebug() << __FILE__ << __LINE__
                     << "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)
          {
            Modif *modif = monomer.modifList()->at(iter);

            QString modifName = modif->name();

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

            modifRenderer = polChemDef->chemEntVignetteRenderer(modifName);

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

                modifRenderer = polChemDef->newModifVignetteRenderer(modifName);

                if(!modifRenderer)
                  {
                    qDebug()
                      << __FILE__ << __LINE__
                      << "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, Modif *modif)
  {
    Q_ASSERT(mp_polymer);
    PolChemDef *polChemDef = mp_editorWnd->polChemDef();
    Q_ASSERT(polChemDef);

    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 =
      polChemDef->chemEntVignetteRenderer(name);

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

        if(!renderer)
          {
            qDebug() << __FILE__ << __LINE__
                     << "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, 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,
                                                  CrossLinker *crossLinker)
  {
    Q_ASSERT(mp_polymer);
    PolChemDef *polChemDef = mp_editorWnd->polChemDef();
    Q_ASSERT(polChemDef);

    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 =
      polChemDef->chemEntVignetteRenderer(name);

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

        if(!renderer)
          {
            qDebug() << __FILE__ << __LINE__
                     << "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,
                                                    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 Monomer *monomer = mp_polymer->monomerList().at(iter);

        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 CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

    for(int iter = 0; iter < crossLinkList.size(); ++iter)
      {
        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 Monomer *monomer = crossLink->monomerList()->at(jter);

            int index = mp_polymer->monomerIndex(monomer);

            if(!crossLinkVignetteAt(index, crossLink))
              {
                qDebug() << __FILE__ << __LINE__
                         << "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);

    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 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 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(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 Monomer *monomer =
          const_cast<QList<const 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 Monomer *monomer = mp_polymer->at(index);

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

    const 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)
      {
        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 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.

    CoordinateList coordList;

    bool selectionPresent = selectionIndices(&coordList);

    if(!selectionPresent)
      return 0;

    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::MidButton)
      {
        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
