/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 <QMessageBox>
#include <QFileDialog>
#include <QSettings>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/trace/trace.h>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/PkaPhPi.hpp>
#include <MsXpS/libXpertMassCore/PkaPhPiDataParser.hpp>
#include <MsXpS/libXpertMassCore/PolChemDef.hpp>
#include <MsXpS/libXpertMassCore/Polymer.hpp>
#include <MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp>
#include <MsXpS/libXpertMassCore/IsotopicClusterGenerator.hpp>
#include <MsXpS/libXpertMassCore/IsotopicClusterShaper.hpp>
#include <MsXpS/libXpertMassCore/IsotopicData.hpp>
#include <MsXpS/libXpertMassCore/MassPeakShaper.hpp>
#include <MsXpS/libXpertMassCore/MassPeakShaperConfig.hpp>
#include <MsXpS/libXpertMassCore/CrossLink.hpp>
#include <MsXpS/libXpertMassCore/MonomerDictionary.hpp>


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>
#include <MsXpS/libXpertMassGui/DecimalPlacesOptionsDlg.hpp>


/////////////////////// Local includes
#include "../nongui/globals.hpp"
#include "SequenceEditorWnd.hpp"
#include "ProgramWindow.hpp"
#include "Application.hpp"
#include "SequenceEditorGraphicsView.hpp"
#include "SequenceEditorFindDlg.hpp"
#include "MonomerModificationDlg.hpp"
#include "PolymerModificationDlg.hpp"
#include "MonomerCrossLinkDlg.hpp"
#include "CleavageDlg.hpp"
#include "FragmentationDlg.hpp"
#include "MassSearchDlg.hpp"
#include "CalculatorWnd.hpp"
#include "MzCalculationDlg.hpp"
#include "CompositionsDlg.hpp"
#include "PkaPhPiDlg.hpp"
#include "ui_SequenceEditorWnd.h"

namespace MsXpS
{

namespace MassXpert
{


SequenceEditorWnd::SequenceEditorWnd(ProgramWindow *parent,
                                     const QString &application_name,
                                     const QString &description)

  : AbstractMainTaskWindow(
      parent, "SequenceEditorWnd", application_name, description),
    mp_calcOptions(new libXpertMassCore::CalcOptions(this)),
    mp_ui(new Ui::SequenceEditorWnd)
{
  if(!initialize())
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to pre-initialize the polymer "
           "sequence editor window."),
        QMessageBox::Ok);
    }

  readSettings();

  show();
}

SequenceEditorWnd::~SequenceEditorWnd()
{
  qDebug() << "Sequence editor window being destructed.";

  delete mpa_resultsString;

  while(!m_propList.isEmpty())
    delete m_propList.takeFirst();
}

void
SequenceEditorWnd::writeSettings()
{
  QSettings settings(
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  settings.setValue("geometry", saveGeometry());
  settings.setValue("vignetteSize",
                    mpa_editorGraphicsView->requestedVignetteSize());

  settings.setValue("hSplitterSize", mp_ui->hSplitter->saveState());
  settings.setValue("vSplitterSize", mp_ui->vSplitter->saveState());

  settings.setValue("calcEngineMonomersToolBox",
                    mp_ui->calcEngineMonomersToolBox->currentIndex());

  settings.endGroup();
}

void
SequenceEditorWnd::readSettings()
{
  QSettings settings(
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);

  restoreGeometry(settings.value("geometry").toByteArray());
  int vignetteSize = settings.value("vignetteSize", 32).toInt();
  mpa_editorGraphicsView->requestVignetteSize(vignetteSize);
  mp_ui->vignetteSizeSpinBox->setValue(vignetteSize);

  mp_ui->vSplitter->restoreState(settings.value("vSplitterSize").toByteArray());
  mp_ui->hSplitter->restoreState(settings.value("hSplitterSize").toByteArray());

  mp_ui->calcEngineMonomersToolBox->setCurrentIndex(
    settings.value("calcEngineMonomersToolBox").toInt());

  settings.endGroup();
}

void
SequenceEditorWnd::closeEvent(QCloseEvent *event)
{
  // We are asked to close the window even if it has unsaved data.
  if(m_forciblyClose || maybeSave())
    {
      writeSettings();

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

void
SequenceEditorWnd::focusInEvent([[maybe_unused]] QFocusEvent *event)
{
  mp_parentWnd->setLastFocusedSeqEdWnd(this);
}

void
SequenceEditorWnd::focusOutEvent([[maybe_unused]] QFocusEvent *event)
{
  mp_parentWnd->setLastFocusedSeqEdWnd(0);
}

void
SequenceEditorWnd::createActions()
{
  // File/Close
  closeAct = new QAction(tr("&Close"), this);
  closeAct->setShortcut(tr("Ctrl+W"));
  closeAct->setStatusTip(tr("Closes the sequence"));
  connect(closeAct, SIGNAL(triggered()), this, SLOT(close()));

  // File/Save
  saveAct = new QAction(tr("&Save"), this);
  saveAct->setShortcut(tr("Ctrl+S"));
  saveAct->setStatusTip(tr("Saves the sequence"));
  connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));

  // File/SaveAs
  saveAsAct = new QAction(tr("Save&as"), this);
  saveAsAct->setShortcut(tr("Ctrl+Alt+S"));
  saveAsAct->setStatusTip(tr("Saves the sequence in another file"));
  connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));


  // File/ImportRaw
  importRawAct = new QAction(tr("&Import raw"), this);
  importRawAct->setShortcut(tr("Ctrl+I"));
  importRawAct->setStatusTip(tr("Imports a raw text file"));
  connect(importRawAct, SIGNAL(triggered()), this, SLOT(importRaw()));


  // File/ExportClipboard
  exportClipboardAct = new QAction(tr("Export to &clipboard"), this);
  exportClipboardAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_C));
  exportClipboardAct->setStatusTip(tr("Export as text to the clipboard"));
  connect(
    exportClipboardAct, SIGNAL(triggered()), this, SLOT(exportClipboard()));

  // File/ExportFile
  exportFileAct = new QAction(tr("Export to &file"), this);
  exportFileAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_F));
  exportFileAct->setStatusTip(tr("Export as text to file"));
  connect(exportFileAct, SIGNAL(triggered()), this, SLOT(exportFile()));

  // File/ExportSelectFile
  exportSelectFileAct = new QAction(tr("&Select export file"), this);
  exportSelectFileAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_S));
  exportSelectFileAct->setStatusTip(tr("Select file to export as text to"));
  connect(
    exportSelectFileAct, SIGNAL(triggered()), this, SLOT(exportSelectFile()));


  // Edit/Copy to Clipboard
  clipboardCopyAct = new QAction(tr("&Copy"), this);
  clipboardCopyAct->setShortcut(tr("Ctrl+C"));
  clipboardCopyAct->setStatusTip(
    tr("Copy the selected "
       "region of the sequence"));
  connect(clipboardCopyAct, SIGNAL(triggered()), this, SLOT(clipboardCopy()));

  // Edit/Cut
  clipboardCutAct = new QAction(tr("C&ut"), this);
  clipboardCutAct->setShortcut(tr("Ctrl+X"));
  clipboardCutAct->setStatusTip(
    tr("Cut the selected "
       "region of the sequence"));
  connect(clipboardCutAct, SIGNAL(triggered()), this, SLOT(clipboardCut()));

  // Edit/Paste
  clipboardPasteAct = new QAction(tr("&Paste"), this);
  clipboardPasteAct->setShortcut(tr("Ctrl+V"));
  clipboardPasteAct->setStatusTip(
    tr("Copy the selected "
       "region of the sequence"));
  connect(clipboardPasteAct, SIGNAL(triggered()), this, SLOT(clipboardPaste()));

  // Edit/Find Sequence
  findSequenceAct = new QAction(tr("&Find sequence"), this);
  findSequenceAct->setShortcut(tr("Ctrl+F"));
  findSequenceAct->setStatusTip(
    tr("Find a sequence "
       "in the polymer sequence"));
  connect(findSequenceAct, SIGNAL(triggered()), this, SLOT(findSequence()));


  // Chemistry/ModifyMonomer
  modifMonomerAct = new QAction(tr("Modify &monomer(s)"), this);
  modifMonomerAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_M));
  modifMonomerAct->setStatusTip(tr("Modifies monomer(s)"));
  connect(modifMonomerAct, SIGNAL(triggered()), this, SLOT(modifMonomer()));

  // Chemistry/ModifyPolymer
  modifPolymerAct = new QAction(tr("Modify &polymer"), this);
  modifPolymerAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_P));
  modifPolymerAct->setStatusTip(tr("Modifies the polymer"));
  connect(modifPolymerAct, SIGNAL(triggered()), this, SLOT(modifPolymer()));

  // Also connect that SLOT to the two buttons for left and right end
  // modif.

  connect(mp_ui->leftEndModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(modifLeftEnd()));

  connect(mp_ui->rightEndModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(modifRightEnd()));

  // Chemistry/CrossLinkMonomer
  crossLinkMonomersAct = new QAction(tr("Cross-&link monomers"), this);
  crossLinkMonomersAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_M));
  crossLinkMonomersAct->setStatusTip(tr("Cross-link monomers"));
  connect(
    crossLinkMonomersAct, SIGNAL(triggered()), this, SLOT(crossLinkMonomers()));

  // Chemistry/Cleave
  cleaveAct = new QAction(tr("&Cleave"), this);
  cleaveAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_K));
  cleaveAct->setStatusTip(tr("Cleaves the polymer"));
  connect(cleaveAct, SIGNAL(triggered()), this, SLOT(cleave()));

  // Chemistry/Fragment
  fragmentAct = new QAction(tr("Fra&gment"), this);
  fragmentAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_G));
  fragmentAct->setStatusTip(tr("Fragments the polymer"));
  connect(fragmentAct, SIGNAL(triggered()), this, SLOT(fragment()));

  // Chemistry/MassSearch
  massSearchAct = new QAction(tr("Search &masses"), this);
  massSearchAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_S));
  massSearchAct->setStatusTip(tr("Search oligomers based on mass"));
  connect(massSearchAct, SIGNAL(triggered()), this, SLOT(massSearch()));

  // Chemistry/mzCalculation
  mzCalculationAct = new QAction(tr("Compute m/z &ratios"), this);
  mzCalculationAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_Z));
  mzCalculationAct->setStatusTip(tr("Compute ion charge families"));
  connect(mzCalculationAct, SIGNAL(triggered()), this, SLOT(mzCalculation()));

  // Chemistry/compositions
  compositionsAct = new QAction(tr("Determine compositions"), this);
  compositionsAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_D, Qt::CTRL | Qt::Key_C));
  compositionsAct->setStatusTip(tr("Determine compositions"));
  connect(compositionsAct, SIGNAL(triggered()), this, SLOT(compositions()));

  // Chemistry/isoelectricPoint
  pkaPhPiAct = new QAction(tr("pKa pH pI"), this);
  pkaPhPiAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_P));
  pkaPhPiAct->setStatusTip(tr("pKa pH pI"));
  connect(pkaPhPiAct, SIGNAL(triggered()), this, SLOT(pkaPhPi()));

  // Options/decimalPlaces
  decimalPlacesOptionsAct = new QAction(tr("Decimal places"), this);
  decimalPlacesOptionsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
  decimalPlacesOptionsAct->setStatusTip(tr("Decimal places"));
  connect(decimalPlacesOptionsAct,
          SIGNAL(triggered()),
          this,
          SLOT(decimalPlacesOptions()));

  // Calculator (preseed with whole sequence masses)
  newCalculatorWholeSequenceMassesAct =
    new QAction(tr("New calculator (whole seq. masses)"), this);

  newCalculatorWholeSequenceMassesAct->setStatusTip(
    tr("Start new calculator preseeded with whole sequence masses"));

  connect(newCalculatorWholeSequenceMassesAct,
          SIGNAL(triggered()),
          this,
          SLOT(newCalculatorWholeSequenceMasses()));

  // Calculator (preseed with selected sequence masses)
  newCalculatorSelectedSequenceMassesAct =
    new QAction(tr("New calculator (selected seq. masses)"), this);

  newCalculatorSelectedSequenceMassesAct->setStatusTip(
    tr("Start new calculator preseeded with selected sequence masses"));

  connect(newCalculatorSelectedSequenceMassesAct,
          SIGNAL(triggered()),
          this,
          SLOT(newCalculatorSelectedSequenceMasses()));
}

void
SequenceEditorWnd::createMenus()
{
  fileMenu = menuBar()->addMenu(tr("&File"));
  fileMenu->addAction(closeAct);
  fileMenu->addSeparator();
  fileMenu->addAction(saveAct);
  fileMenu->addAction(saveAsAct);
  fileMenu->addSeparator();

  // libXpertMassCore::Sequence importers menu
  fileImportMenu = fileMenu->addMenu(tr("&Import"));
  fileImportMenu->addAction(importRawAct);

  fileMenu->addSeparator();
  fileMenu->addAction(exportClipboardAct);
  fileMenu->addAction(exportFileAct);
  fileMenu->addAction(exportSelectFileAct);

  editMenu = menuBar()->addMenu(tr("&Edit"));
  editMenu->addAction(clipboardCopyAct);

  editMenu->addAction(clipboardCutAct);
  editMenu->addAction(clipboardPasteAct);
  editMenu->addSeparator();
  editMenu->addAction(findSequenceAct);

  chemistryMenu = menuBar()->addMenu(tr("&Chemistry"));
  chemistryMenu->addAction(modifMonomerAct);
  chemistryMenu->addAction(modifPolymerAct);
  chemistryMenu->addAction(crossLinkMonomersAct);
  chemistryMenu->addAction(cleaveAct);
  chemistryMenu->addAction(fragmentAct);
  chemistryMenu->addAction(massSearchAct);
  chemistryMenu->addAction(mzCalculationAct);
  chemistryMenu->addAction(compositionsAct);
  chemistryMenu->addAction(pkaPhPiAct);

  optionsMenu = menuBar()->addMenu(tr("&Options"));
  optionsMenu->addAction(decimalPlacesOptionsAct);

  calculatorMenu = menuBar()->addMenu(tr("&Calculator"));
  calculatorMenu->addAction(newCalculatorWholeSequenceMassesAct);
  calculatorMenu->addAction(newCalculatorSelectedSequenceMassesAct);
}

bool
SequenceEditorWnd::populateAvailableMonomerCodesList(bool reset)
{
  QListWidgetItem *item = 0;

  const libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp =
    mup_polChemDefRendering->getPolChemDefCstSPtr();

  if(pol_chem_def_csp == nullptr)
    return true;

  if(reset)
    {
      while(mp_ui->vignetteListWidget->count())
        {
          item = mp_ui->vignetteListWidget->takeItem(0);
          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << item->text().toAscii();

          delete item;
        }
    }

  for(const libXpertMassCore::MonomerCstSPtr monomer_csp :
      pol_chem_def_csp->getMonomersCstRef())
    {
      QString text = monomer_csp->getCode() + "=" + monomer_csp->getName();
      mp_ui->vignetteListWidget->addItem(text);
    }

  // It would be interesting to know which item is double-clicked.

  connect(mp_ui->vignetteListWidget,
          SIGNAL(itemDoubleClicked(QListWidgetItem *)),
          this,
          SLOT(vignetteListWidgetItemDoubleClicked(QListWidgetItem *)));

  return true;
}

// Before the creation of the polymer chemistry definition/polymer
// relationship.
bool
SequenceEditorWnd::initialize()
{
  mp_ui->setupUi(this);

  setWindowIcon(qApp->windowIcon());

  // Update the window title because the window title element in m_ui might be
  // either erroneous or empty.
  setWindowTitle(
    QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription));

  // The results-exporting menus. ////////////////////////////////
  mpa_resultsString = new QString();
  //////////////////////////////////// The results-exporting menus.

  createActions();
  createMenus();

  setAttribute(Qt::WA_DeleteOnClose);
  statusBar()->setSizeGripEnabled(true);

  setFocusPolicy(Qt::StrongFocus);

  mp_ui->ionizationChargeSpinBox->setRange(1, 1000000000);
  mp_ui->ionizationLevelSpinBox->setRange(0, 1000000000);

  // By default, selected regions behave as oligomers, the way one
  // expects the system to behave when selecting oligomers that are
  // cross-linked, for example.
  mp_ui->regionSelectionOligomerRadioButton->click();

  mp_ui->incompleteCrossLinkWarningLabel->setText(
    tr("Not accounting for cross-links"));

  mpa_editorGraphicsView = new SequenceEditorGraphicsView(this);
  mpa_editorGraphicsView->setParent(this);
  //    mpa_editorGraphicsView->setAlignment(Qt::AlignLeft);

  QVBoxLayout *layout = new QVBoxLayout(mp_ui->graphicsViewFrame);

  layout->addWidget(static_cast<QWidget *>(mpa_editorGraphicsView));

  mp_progressBar = new QProgressBar;
  statusBar()->addPermanentWidget(mp_progressBar);

  // We want to be able to start a drag with the mass values...
  mp_ui->monoWholeMassLineEdit->setDragEnabled(true);
  mp_ui->avgWholeMassLineEdit->setDragEnabled(true);

  mp_ui->regionSelectionOligomerRadioButton->setChecked(true);
  mp_calcOptions->setSelectionType(
    libXpertMassCore::Enums::SelectionType::OLIGOMERS);

  mp_ui->multiRegionSelectionCheckBox->setChecked(true);
  mp_ui->multiSelectionRegionCheckBox->setChecked(false);

  // Set the pointer to this window as text in the corresponding
  // line edit widget.

  QString text = QString("%1").arg((quintptr)this);
  mp_ui->thisWndLineEdit->setText(text);


  // The mass spectrum synthesis
  // The mass spectrum synthesis

  QStringList comboBoxItemList;

  // The color button
  connect(mp_ui->colorSelectorPushButton,
          &QPushButton::clicked,
          this,
          &SequenceEditorWnd::traceColorPushButtonClicked);

  comboBoxItemList.insert((int)MassSpectrumSynthesisActions::LOAD_ISOTOPIC_DATA,
                          "Load isotopic data from file");

  comboBoxItemList.insert(
    (int)MassSpectrumSynthesisActions::CONFIGURE_MASS_PEAK_SHAPER,
    "Configure the mass peak shaper");

  comboBoxItemList.insert(
    (int)MassSpectrumSynthesisActions::SYNTHESIZE_MASS_SPECTRA,
    "Synthesize the mass spectra");

  comboBoxItemList.insert(
    (int)MassSpectrumSynthesisActions::COPY_MASS_SPECTRUM_TO_CLIPBOARD,
    "Mass spectrum not yet available");

  comboBoxItemList.insert(
    (int)MassSpectrumSynthesisActions::SAVE_MASS_SPECTRUM_TO_FILE,
    "Mass spectrum not yet available");

  mp_ui->massSpectrumSynthesisComboBox->addItems(comboBoxItemList);

  connect(mp_ui->massSpectrumSynthesisComboBox,
          &QComboBox::activated,
          this,
          &SequenceEditorWnd::massSpectrumSynthesisMenuActivated);

  setWindowModified(false);

  return true;
}

bool
SequenceEditorWnd::postInitialize()
{
  Q_ASSERT(msp_polymer);

  connect(msp_polymer.get(),
          SIGNAL(crossLinksPartiallyEncompassedSignal(int)),
          this,
          SLOT(crossLinksPartiallyEncompassedSlot(int)));

  mpa_editorGraphicsScene = new QGraphicsScene(this);
  mpa_editorGraphicsView->setPolymer(msp_polymer);
  mpa_editorGraphicsView->setScene(mpa_editorGraphicsScene);

  MonomerCodeEvaluator *evaluator = new MonomerCodeEvaluator(
    msp_polymer, this, mp_ui->codeLineEdit, mp_ui->codeErrorLineEdit);
  mpa_editorGraphicsView->setMonomerCodeEvaluator(evaluator);

  mp_ui->sequenceNameLineEdit->setText(msp_polymer->getName());
  updateWindowTitle();

  updatePolymerEndsModifs();

  statusBar()->showMessage(tr("Ready."));

  if(!populateAvailableMonomerCodesList())
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to populate the monomer code list.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  // Will, in particular, setup the polymer's Ionizer to
  // the default ionization of the PolChemDef.
  populateCalculationOptions();

  mp_ui->vignetteListWidget->setSelectionMode(
    QAbstractItemView::MultiSelection);


  ////// Connection of the SIGNALS and SLOTS //////
  connect(mp_ui->vignetteSizeSpinBox,
          SIGNAL(editingFinished()),
          this,
          SLOT(vignetteSizeChanged()));

  connect(mp_ui->sequenceNameLineEdit,
          SIGNAL(textChanged(const QString &)),
          this,
          SLOT(nameLineEditChanged(const QString &)));

  connect(mp_ui->leftCapCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(mp_ui->rightCapCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(mp_ui->leftModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(leftModifOptionsChanged()));

  connect(mp_ui->forceLeftModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(forceLeftModifOptionsChanged()));

  connect(mp_ui->rightModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(rightModifOptionsChanged()));

  connect(mp_ui->forceRightModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(forceRightModifOptionsChanged()));

  connect(mp_ui->multiRegionSelectionCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(multiRegionSelectionOptionChanged(int)));

  connect(mp_ui->multiSelectionRegionCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(multiSelectionRegionOptionChanged(int)));

  connect(mp_ui->regionSelectionOligomerRadioButton,
          SIGNAL(toggled(bool)),
          this,
          SLOT(regionSelectionOligomerOptionChanged(bool)));

  connect(mp_ui->regionSelectionResChainRadioButton,
          SIGNAL(toggled(bool)),
          this,
          SLOT(regionSelectionResChainOptionChanged(bool)));

  connect(mp_ui->monomerModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(monomerModifOptionChanged(int)));

  connect(mp_ui->monomerCrossLinkCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(monomerCrossLinkOptionChanged(int)));

  connect(mp_ui->ionizationFormulaLineEdit,
          SIGNAL(editingFinished()),
          this,
          SLOT(calculationOptionsChanged()));

  connect(mp_ui->ionizationChargeSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(mp_ui->ionizationLevelSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(mp_ui->selectionLineEdit,
          SIGNAL(textEdited(const QString &)),
          this,
          SLOT(coordinatesManuallyEdited(const QString &)));


  ////// Connection of the SIGNALS and SLOTS //////

  m_postInitialized = true;

  qDebug() << "Setting the postinitialization to true.";

  return true;
}

bool
SequenceEditorWnd::initializeIonizerFromPolChemDef()
{
  qDebug()
    << "Setting up the Polymer ionizer with the PolChemDef ionizer:"
    << msp_polymer->getPolChemDefCstSPtr()->getIonizerCstRef().toString();

  msp_polymer->setIonizer(
    msp_polymer->getPolChemDefCstSPtr()->getIonizerCstRef());

  mp_ui->ionizationFormulaLineEdit->setText(
    msp_polymer->getIonizerCstRef().getFormulaCstRef().getActionFormula(
      true /*with_title*/));

  mp_ui->ionizationChargeSpinBox->setValue(
    msp_polymer->getIonizerCstRef().getNominalCharge());

  mp_ui->ionizationLevelSpinBox->setValue(
    msp_polymer->getIonizerCstRef().getLevel());

  qDebug() << "Now asking for ionizer validation.";
  libXpertMassCore::ErrorList error_list;
  bool ok = msp_polymer->getIonizerCstRef().validate(&error_list);

  if(!ok)
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to setup the Polymer Ionizer.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  qDebug() << "Done performing the Polymer Ionizer validation with outcome:"
           << ok;

  qDebug() << "The Polymer Ionizer validated successfully:"
           << msp_polymer->getIonizerCstRef().toString();

  return true;
}

QProgressBar *
SequenceEditorWnd::progressBar()
{
  return mp_progressBar;
}

bool
SequenceEditorWnd::openSequence(QString &sequence_file_path)
{
  // We get the filePath of the sequence file.

  if(sequence_file_path.isEmpty() || !QFile::exists(sequence_file_path))
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Filepath is empty, or file does not exist.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  QString pol_chem_def_name =
    libXpertMassCore::Polymer::xmlPolymerFileGetPolChemDefName(
      sequence_file_path);

  if(pol_chem_def_name.isEmpty())
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to get the polymer chemistry definition name.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  // qDebug() << "The name of the polymer chemistry definition in the polymer"
  //             "sequence file is:" << pol_chem_def_name;

  bool ok = preparePolChemDefRendering(pol_chem_def_name);

  if(!ok)
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to prepare the polymer chemistry definition.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  const UserSpec &userSpec = mp_parentWnd->getUserSpec();

  // qDebug() << "Allocating new Polymer.";

  msp_polymer = libXpertMassCore::Polymer::createSPtr(
    mup_polChemDefRendering->getPolChemDefCstSPtr(),
    "NOT_SET",
    "NOT_SET",
    userSpec.userName());

  // qDebug() << "Going to read the sequence file.";

  if(!readFile(sequence_file_path))
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to load the polymer file.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  // qDebug() << "Successfully read sequence file:" << sequence_file_path;

  // Because we could read the polymer sequence, the polymer chemisty definition
  // has to be ok. Thus we can initialize all the PolChemDef-related data in
  // the call below.
  if(!postInitialize())
    return false;

  initialNonIonizedWholePolymerSequenceMassCalculation(/*deep*/ true);
  initialNonIonizedSelectedPolymerSequenceMassCalculation(/*deep*/ true);

  if(mpa_editorGraphicsView->drawSequence() == -1)
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to draw the polymer sequence.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  focusInEvent(0);

  // And now abide by the ionization settings required by the
  // PolChemDef, which is what the two functions below do.
  updateWholeSequenceMasses(/*deep*/ true);
  updateSelectedSequenceMasses(/*deep*/ true);

  return true;
}

bool
SequenceEditorWnd::newSequence(QString &pol_chem_def_file_path)
{
  // We get the filePath of the polymer chemistry definition file.

  libXpertMassCore::PolChemDefSpecSPtr pol_chem_def_spec_sp =
    mp_parentWnd->getPolChemDefSpecByFilePath(pol_chem_def_file_path);

  if(pol_chem_def_spec_sp == nullptr)
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to find the corresponding polymer chemistry "
           "filename.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  // qDebug() << "The polymer chemistry definition specification has name:"
  //<< polChemDefSpec->name();

  if(!preparePolChemDefRendering(pol_chem_def_spec_sp->getName()))
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to prepare the polymer chemistry definition.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  msp_polymer = libXpertMassCore::Polymer::createSPtr(
    mup_polChemDefRendering->getPolChemDefCstSPtr(),
    "NOT_SET",
    "NOT_SET",
    mp_parentWnd->getUserSpec().userName());

  if(!postInitialize())
    return false;

  if(mpa_editorGraphicsView->drawSequence() == -1)
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to draw the polymer sequence.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  focusInEvent(0);

  return true;
}

bool
SequenceEditorWnd::readFile(const QString &file_path)
{
  qDebug() << "Now reading/rendering XML file:" << file_path;

  return msp_polymer->renderXmlPolymerFile(file_path);
}

int
SequenceEditorWnd::setIndexRanges(
  const QString &text, libXpertMassCore::Enums::LocationType location_type)
{
  qDebug() << "Setting index ranges from text:" << text;

  libXpertMassCore::IndexRangeCollection new_index_range_collection(
    text, location_type);

  if(!new_index_range_collection.size())
    {
      qDebug() << "Failed to create IndexRangeCollection with text:" << text;
      return -1;
    }

  // Make sure the coordinates can fit the current selection model.

  bool isMultiRegion    = mp_ui->multiRegionSelectionCheckBox->checkState();
  bool isMultiSelection = mp_ui->multiSelectionRegionCheckBox->checkState();

  if(new_index_range_collection.size() > 1 && !isMultiRegion)
    return -1;

  if(new_index_range_collection.overlap() && !isMultiSelection)
    return -1;

  // At this point, we should be able to change the coordinates of
  // the selection according to the user's wishes.

  qDebug() << "Now resetting the selection.";

  mpa_editorGraphicsView->resetSelection();
  mpa_editorGraphicsView->setSelection(
    new_index_range_collection, isMultiRegion, isMultiSelection);

  return new_index_range_collection.size();
}

bool
SequenceEditorWnd::preparePolChemDefRendering(const QString &name)
{
  // Is a polymer definition already available, or shall we have to
  // load one first ?
  libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp =
    mp_parentWnd->getPolChemDefByName(name);

  if(pol_chem_def_csp == nullptr)
    {
      // qDebug() << "The returned libXpertMassCore::PolChemDefCstSPtr is
      // nullptr."
      //<< "We must first create the polymer chemistry definition.";

      // No polymer chemistry definition by that name is currently
      // loaded in memory.

      libXpertMassCore::PolChemDefSpecSPtr pol_chem_def_spec_sp =
        mp_parentWnd->getPolChemDefSpecByName(name);

      if(pol_chem_def_spec_sp == nullptr)
        {
          // No polymer chemistry definition by that name is
          // registered to the system. We cannot go further.

          QMessageBox::warning(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            tr("%1@%2\n"
               "Failed to get the polymer chemistry "
               "definition specification name: %3.")
              .arg(__FILE__)
              .arg(__LINE__)
              .arg(name),
            QMessageBox::Ok);

          return false;
        }

      // qDebug()
      //<< "The polymer chemistry definition has name:" << name
      //<< "Allocating a brand new polymer chemistry definiton instance.";

      // polChemDefSpec should provide data to create a new polymer
      // chemistry definition object.
      // We'll have to load one and thus create one that
      //  is not const !

      libXpertMassCore::PolChemDefSPtr pol_chem_def_sp =
        std::make_shared<libXpertMassCore::PolChemDef>(*pol_chem_def_spec_sp);

      if(pol_chem_def_sp == nullptr)
        {
          // No polymer chemistry definition by that name is
          // registered to the system. We cannot go further.

          QMessageBox::warning(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            tr("%1@%2\n"
               "Failed to allocate polymer chemistry "
               "definition for name: %3.")
              .arg(__FILE__)
              .arg(__LINE__)
              .arg(name),
            QMessageBox::Ok);

          pol_chem_def_sp.reset();
          return false;
        }

      qDebug() << "Allocated brand new PolChemDef*: " << pol_chem_def_sp.get()
               << "Now rendering the definition from file.";

      if(!libXpertMassCore::PolChemDef::renderXmlPolChemDefFile(
           pol_chem_def_sp))
        {
          // The pol_chem_def_sp will be handled automagically at scope exit.

          QMessageBox::warning(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            tr("%1@%2\n"
               "Failed to render the polymer chemistry"
               " definition xml file for name %3.")
              .arg(__FILE__)
              .arg(__LINE__)
              .arg(name),
            QMessageBox::Ok);

          pol_chem_def_sp.reset();
          return false;
        }

      qDebug() << "Successfully rendered the polymer chemistry definition.";

      // Now cast it to const:
      pol_chem_def_csp = pol_chem_def_sp;
    }

  //  At this point we know we have a pol_chem_def_csp,  either because it
  //  existed before or because we have allocated it right above.

  // We still have to initialize the Monomer/Modif/CrossLinker specifications...
  // For this we need a PolChemDefRendering instance to store the specs in.

  mup_polChemDefRendering =
    std::make_unique<PolChemDefRendering>(pol_chem_def_csp);

  QString dictionary =
    pol_chem_def_csp->getXmlDataDirPath() + "/monomer_dictionary";

  if(!MonomerSpec::parseFile(dictionary,
                             mup_polChemDefRendering->getMonomerSpecsRef()))
    {
      // The pol_chem_def_sp will be handled automagically at scope exit.

      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to parse the monomer dictionary file.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      pol_chem_def_csp.reset();
      return false;
    }

  dictionary =
    pol_chem_def_csp->getXmlDataDirPath() + "/modification_dictionary";

  if(!ModifSpec::parseFile(dictionary,
                           mup_polChemDefRendering->getModifSpecsRef()))
    {
      // The pol_chem_def_sp will be handled automagically at scope exit.

      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to parse the modification dictionary file.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      pol_chem_def_csp.reset();
      return false;
    }

  dictionary =
    pol_chem_def_csp->getXmlDataDirPath() + "/cross_linker_dictionary";

  if(!CrossLinkerSpec::parseFile(
       dictionary, mup_polChemDefRendering->getCrossLinkerSpecsRef()))
    {
      // The pol_chem_def_sp will be handled automagically at scope exit.

      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "Failed to parse the cross-linker dictionary file.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      pol_chem_def_csp.reset();
      return false;
    }

  // Finally we can add this libXpertMassCore::PolChemDef to the application
  // list of such objects.

  mp_parentWnd->getPolChemDefsRef().push_back(pol_chem_def_csp);

  qDebug() << "Successfully rendered the full PolChemDef.";

  return true;
}

void
SequenceEditorWnd::populateCalculationOptions()
{
  const libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp =
    mup_polChemDefRendering->getPolChemDefCstSPtr();

  if(pol_chem_def_csp == nullptr)
    return;

  if(!initializeIonizerFromPolChemDef())
    qFatal() << "Failed to initialize Ionizer.";

  // qDebug() << CAP_NONE << CAP_LEFT
  // << CAP_RIGHT << CAP_BOTH;

  mp_ui->leftCapCheckBox->setChecked(static_cast<bool>(
    mp_calcOptions->getCapType() & libXpertMassCore::Enums::CapType::LEFT));
  mp_ui->rightCapCheckBox->setChecked(static_cast<bool>(
    mp_calcOptions->getCapType() & libXpertMassCore::Enums::CapType::RIGHT));

  mp_ui->monomerModifCheckBox->setChecked(
    static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::MODIF));

  mp_ui->leftModifCheckBox->setChecked(
    static_cast<bool>(mp_calcOptions->getPolymerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF));

  mp_ui->rightModifCheckBox->setChecked(static_cast<bool>(
    mp_calcOptions->getPolymerEntities() &
    libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF));
}

void
SequenceEditorWnd::calculationOptionsChanged()
{
  //   qDebug() << " calculationOptionsChanged";

  // CAPPING
  int value = 0;

  if(mp_ui->leftCapCheckBox->checkState() == Qt::Checked)
    value |=
      static_cast<std::underlying_type_t<libXpertMassCore::Enums::CapType>>(
        libXpertMassCore::Enums::CapType::LEFT);

  if(mp_ui->rightCapCheckBox->checkState() == Qt::Checked)
    value |=
      static_cast<std::underlying_type_t<libXpertMassCore::Enums::CapType>>(
        libXpertMassCore::Enums::CapType::RIGHT);

  mp_calcOptions->setCapType(
    static_cast<libXpertMassCore::Enums::CapType>(value));

  //   qDebug() << "capping: " << mp_calcOptions->capping();
  //   qDebug() << "polymer entities: " << mp_calcOptions->getPolymerEntities();

  // IONIZATION RULE

  QString text = mp_ui->ionizationFormulaLineEdit->text();

  if(!text.isEmpty())
    {
      const libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp =
        mup_polChemDefRendering->getPolChemDefCstSPtr();

      libXpertMassCore::Formula formula(text);

      libXpertMassCore::ErrorList error_list;

      if(!formula.validate(pol_chem_def_csp->getIsotopicDataCstSPtr(),
                           &error_list))
        {
          QMessageBox::warning(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            QString("Ionization rule formula is not valid with errors: %1")
              .arg(libXpertMassCore::Utils::joinErrorList(error_list)),
            QMessageBox::Ok);

          mp_ui->ionizationFormulaLineEdit->setFocus();

          return;
        }

      msp_polymer->getIonizerRef().setFormula(text);
      //       qDebug() << "ionization formula: " <<
      //       msp_polymer->getIonizerCstRef().getFormulaCstRef();
    }

  msp_polymer->getIonizerRef().setNominalCharge(
    mp_ui->ionizationChargeSpinBox->value());
  //   qDebug() << "ionization charge: " <<
  //   msp_polymer->getIonizerCstRef().getNominalCharge();

  msp_polymer->getIonizerRef().setLevel(mp_ui->ionizationLevelSpinBox->value());
  //   qDebug() << "ionization level: " <<
  //   msp_polymer->getIonizerCstRef().getLevel();

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses();
}

// returns -1 in case of error, otherwise returns the number of
// coordinates effectively present in the coordinateList
int
SequenceEditorWnd::coordinatesManuallyEdited(const QString &text)
{
  // The user is editing new selection coordinates, so make the
  // corresponding selection in the sequence editor window.

  QString orig_range_text = mp_ui->selectionLineEdit->text();
  QString new_range_text  = text;

  // qDebug() << "New manually set IndexRange text:" << text;

  libXpertMassCore::IndexRangeCollection orig_index_range_collection;
  mpa_editorGraphicsView->selectionIndices(orig_index_range_collection);

  int res = setIndexRanges(text);

  if(res == -1)
    mp_ui->selectionLineEdit->setText(orig_range_text);

  return res;
}

void
SequenceEditorWnd::leftModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int entities = static_cast<
    std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
    mp_calcOptions->getPolymerEntities());

  if(mp_ui->leftModifCheckBox->checkState() == Qt::Checked)
    {
      entities |= static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF);
    }
  else
    {
      entities &= ~static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF);

      if(mp_ui->forceLeftModifCheckBox->checkState() == Qt::Checked)
        {
          mp_ui->forceLeftModifCheckBox->toggle();
          entities &= ~static_cast<
            std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
            libXpertMassCore::Enums::ChemicalEntity::FORCE_LEFT_END_MODIF);
        }
    }

  mp_calcOptions->setPolymerEntities(
    static_cast<libXpertMassCore::Enums::ChemicalEntity>(entities));

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}

void
SequenceEditorWnd::forceLeftModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int entities = static_cast<
    std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
    mp_calcOptions->getPolymerEntities());

  if(mp_ui->forceLeftModifCheckBox->checkState() == Qt::Checked)
    {
      if(mp_ui->leftModifCheckBox->checkState() != Qt::Checked)
        {
          mp_ui->leftModifCheckBox->toggle();
          entities |= static_cast<
            std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
            libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF);
        }

      entities |= static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::FORCE_LEFT_END_MODIF);
    }
  else
    {
      entities &= ~static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::FORCE_LEFT_END_MODIF);
    }

  mp_calcOptions->setPolymerEntities(
    static_cast<libXpertMassCore::Enums::ChemicalEntity>(entities));

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}

void
SequenceEditorWnd::rightModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int entities = static_cast<
    std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
    mp_calcOptions->getPolymerEntities());

  if(mp_ui->rightModifCheckBox->checkState() == Qt::Checked)
    {
      entities |= static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF);
    }
  else
    {
      entities &= ~static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF);

      if(mp_ui->forceRightModifCheckBox->checkState() == Qt::Checked)
        {
          mp_ui->forceRightModifCheckBox->toggle();
          entities &= ~static_cast<
            std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
            libXpertMassCore::Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF);
        }
    }

  mp_calcOptions->setPolymerEntities(
    static_cast<libXpertMassCore::Enums::ChemicalEntity>(entities));

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}

void
SequenceEditorWnd::forceRightModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int entities = static_cast<
    std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
    mp_calcOptions->getPolymerEntities());

  if(mp_ui->forceRightModifCheckBox->checkState() == Qt::Checked)
    {
      if(mp_ui->rightModifCheckBox->checkState() != Qt::Checked)
        {
          mp_ui->rightModifCheckBox->toggle();
          entities |= static_cast<
            std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
            libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF);
        }

      entities |= static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF);
    }
  else
    {
      entities &= ~static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF);
    }

  mp_calcOptions->setPolymerEntities(
    static_cast<libXpertMassCore::Enums::ChemicalEntity>(entities));

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}

void
SequenceEditorWnd::setMultiRegionSelection(bool is_multi_region_selection)
{
  mp_ui->multiRegionSelectionCheckBox->setChecked(is_multi_region_selection);
}

void
SequenceEditorWnd::setMultiSelectionRegion(bool is_multi_selection_region)
{
  if(is_multi_selection_region)
    {
      if(mp_ui->multiRegionSelectionCheckBox->checkState() == Qt::Checked)
        mp_ui->multiSelectionRegionCheckBox->setChecked(true);
    }
  else
    mp_ui->multiSelectionRegionCheckBox->setChecked(false);
}

void
SequenceEditorWnd::multiRegionSelectionOptionChanged(int checkState)
{
  if(checkState == Qt::Unchecked)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "multiRegionSelectionOptionChanged: unchecked";

      // No multi-region selection... We remove all selections but
      // the last one.
      mpa_editorGraphicsView->resetSelectionButLastRegion();
      mpa_editorGraphicsView->setOngoingMouseMultiSelection(false);

      // Note that if no multi region selections are allowed,
      // multi-selection regions should be disallowed also. But we
      // do not want to uncheck the checkbox, we just inactivate it.

      mp_ui->multiSelectionRegionCheckBox->setEnabled(false);
    }
  else
    {
      // Note that if multi region selections are allowed,
      // multi-selection regions should be possible also.

      mp_ui->multiSelectionRegionCheckBox->setEnabled(true);
    }

  updateSelectedSequenceMasses(false);
}

bool
SequenceEditorWnd::isMultiRegionSelection()
{
  return mp_ui->multiRegionSelectionCheckBox->isChecked();
}

void
SequenceEditorWnd::multiSelectionRegionOptionChanged(int checkState)
{
  if(checkState == Qt::Unchecked)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "multiSelectionRegionOptionChanged: unchecked";

      // No multi-selection regions... We remove all selections but
      // the first one.
      mpa_editorGraphicsView->resetMultiSelectionRegionsButFirstSelection();
      mpa_editorGraphicsView->setOngoingMouseMultiSelection(false);
    }

  updateSelectedSequenceMasses(false);
}

bool
SequenceEditorWnd::isMultiSelectionRegion()
{
  return mp_ui->multiSelectionRegionCheckBox->isChecked();
}

void
SequenceEditorWnd::regionSelectionOligomerOptionChanged(bool checked)
{
  if(checked)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "regionSelectionOligomerOptionChanged checked";

      mp_calcOptions->setSelectionType(
        libXpertMassCore::Enums::SelectionType::OLIGOMERS);
    }
  else
    mp_calcOptions->setSelectionType(
      libXpertMassCore::Enums::SelectionType::RESIDUAL_CHAINS);

  updateSelectedSequenceMasses(false);
}

void
SequenceEditorWnd::regionSelectionResChainOptionChanged(bool checked)
{
  if(checked)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "regionSelectionResChainOptionChanged checked";

      mp_calcOptions->setSelectionType(
        libXpertMassCore::Enums::SelectionType::RESIDUAL_CHAINS);
    }
  else
    mp_calcOptions->setSelectionType(
      libXpertMassCore::Enums::SelectionType::OLIGOMERS);

  updateSelectedSequenceMasses(false);
}

void
SequenceEditorWnd::monomerModifOptionChanged(int checkState)
{
  libXpertMassCore::Enums::ChemicalEntity monomer_chemical_entities =
    mp_calcOptions->getMonomerEntities();

  int compounded_monomer_chemical_entities =
    static_cast<int>(monomer_chemical_entities);

  qDebug() << "Starting configuration with monomer entities:"
           << libXpertMassCore::chemicalEntityMap[monomer_chemical_entities]
           << "and as int:" << compounded_monomer_chemical_entities;

  if(checkState == Qt::Checked)
    {
      qDebug() << "The modifs *should* be accounted for.";

      compounded_monomer_chemical_entities |= static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::MODIF);
    }
  else
    {
      qDebug() << "The modifs should *not* be accounted for.";

      compounded_monomer_chemical_entities &= ~static_cast<
        std::underlying_type_t<libXpertMassCore::Enums::ChemicalEntity>>(
        libXpertMassCore::Enums::ChemicalEntity::MODIF);
    }

  // Finally update the mp_calcOptions monomer entities value.

  mp_calcOptions->setMonomerEntities(
    static_cast<libXpertMassCore::Enums::ChemicalEntity>(
      compounded_monomer_chemical_entities));

  qDebug()
    << "Configured monomer entities:"
    << libXpertMassCore::chemicalEntityMap[mp_calcOptions->getMonomerEntities()]
    << "and as int:" << compounded_monomer_chemical_entities;

  updateWholeSequenceMasses(true);
  updateSelectedSequenceMasses(true);
}

void
SequenceEditorWnd::monomerCrossLinkOptionChanged(int checkState)
{
  libXpertMassCore::Enums::ChemicalEntity monomer_chemical_entities =
    mp_calcOptions->getMonomerEntities();

  int compounded_monomer_chemical_entities =
    static_cast<int>(monomer_chemical_entities);

  qDebug() << "Starting configuration with monomer entities:"
           << libXpertMassCore::chemicalEntityMap[monomer_chemical_entities]
           << "and as int:" << compounded_monomer_chemical_entities;

  if(checkState == Qt::Checked)
    {
      qDebug() << "The cross-links *should* be accounted for.";

      compounded_monomer_chemical_entities |=
        static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER);

      // When cross-links are to be accounted for, the multi-selection
      // region feature has to be inactivated.

      mp_ui->multiSelectionRegionCheckBox->setChecked(false);
    }
  else
    {
      qDebug() << "The cross-links should *not* be accounted for.";

      compounded_monomer_chemical_entities &= ~static_cast<int>(
        libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER);

      mp_ui->incompleteCrossLinkWarningLabel->setText(
        tr("Not accounting for cross-links"));
    }

  // Finally update the mp_calcOptions monomer entities value.

  mp_calcOptions->setMonomerEntities(
    static_cast<libXpertMassCore::Enums::ChemicalEntity>(
      compounded_monomer_chemical_entities));

  qDebug()
    << "Configured monomer entities:"
    << libXpertMassCore::chemicalEntityMap[mp_calcOptions->getMonomerEntities()]
    << "and as int:" << static_cast<int>(mp_calcOptions->getMonomerEntities());

  updateWholeSequenceMasses(true);
  updateSelectedSequenceMasses(true);
}

PolChemDefRenderingCstRPtr
SequenceEditorWnd::getPolChemDefRenderingCstRPtr() const
{
  return mup_polChemDefRendering.get();
}

PolChemDefRenderingRPtr
SequenceEditorWnd::getPolChemDefRenderingRPtr()
{
  return mup_polChemDefRendering.get();
}

libXpertMassCore::PolymerQSPtr
SequenceEditorWnd::getPolymerSPtr()
{
  return msp_polymer;
}

libXpertMassCore::Polymer *
SequenceEditorWnd::getPolymer()
{
  return msp_polymer.get();
}

QList<libXpertMassCore::Prop *> *
SequenceEditorWnd::propList()
{
  return &m_propList;
}

void
SequenceEditorWnd::setCalcOptions(
  const libXpertMassCore::CalcOptions &calc_options)
{
  mp_calcOptions->initialize(calc_options);
  // qDebug() << "Setting calculation options:";

  QString index_ranges_as_text =
    mp_calcOptions->getIndexRangeCollectionCstRef().indicesAsText();
  mp_ui->selectionLineEdit->setText(index_ranges_as_text);

  bool res                   = false;
  Qt::CheckState check_state = Qt::Unchecked;

  res         = static_cast<bool>(mp_calcOptions->getCapType() &
                          libXpertMassCore::Enums::CapType::LEFT);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->leftCapCheckBox->setCheckState(check_state);

  res         = static_cast<bool>(mp_calcOptions->getCapType() &
                          libXpertMassCore::Enums::CapType::RIGHT);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->rightCapCheckBox->setCheckState(check_state);

  res =
    static_cast<bool>(mp_calcOptions->getPolymerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->leftModifCheckBox->setCheckState(check_state);

  res = static_cast<bool>(
    mp_calcOptions->getPolymerEntities() &
    libXpertMassCore::Enums::ChemicalEntity::FORCE_LEFT_END_MODIF);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->forceLeftModifCheckBox->setCheckState(check_state);

  res =
    static_cast<bool>(mp_calcOptions->getPolymerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->rightModifCheckBox->setCheckState(check_state);

  res = static_cast<bool>(
    mp_calcOptions->getPolymerEntities() &
    libXpertMassCore::Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->forceRightModifCheckBox->setCheckState(check_state);


  res = mp_calcOptions->getSelectionType() ==
        libXpertMassCore::Enums::SelectionType::OLIGOMERS;
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->regionSelectionOligomerRadioButton->setChecked(check_state);

  res = mp_calcOptions->getSelectionType() ==
        libXpertMassCore::Enums::SelectionType::RESIDUAL_CHAINS;
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->regionSelectionResChainRadioButton->setChecked(check_state);

  res         = static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                          libXpertMassCore::Enums::ChemicalEntity::MODIF);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->monomerModifCheckBox->setCheckState(check_state);

  res =
    static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER);
  check_state = (res ? Qt::Checked : Qt::Unchecked);
  mp_ui->monomerCrossLinkCheckBox->setChecked(check_state);
}

// This one works with QJSEngine
void
SequenceEditorWnd::setCalcOptions(
  const libXpertMassCore::CalcOptions *calc_options_p)
{
  return setCalcOptions(*calc_options_p);
}

libXpertMassCore::CalcOptions *
SequenceEditorWnd::getCalcOptions()
{
  qDebug() << "Going to return member CalcOptions:"
           << mp_calcOptions->toString();

  return mp_calcOptions;
}

const libXpertMassCore::CalcOptions &
SequenceEditorWnd::getCalcOptionsCstRef() const
{
  qDebug() << "Going to return member CalcOptions:"
           << mp_calcOptions->toString();

  return *mp_calcOptions;
}

const libXpertMassCore::Ionizer &
SequenceEditorWnd::getIonizerCstRef() const
{
  return msp_polymer->getIonizerCstRef();
}

libXpertMassCore::Ionizer &
SequenceEditorWnd::getIonizerRef()
{
  return msp_polymer->getIonizerRef();
}

libXpertMassCore::Ionizer *
SequenceEditorWnd::getIonizer()
{
  return msp_polymer->getIonizer();
}

void
SequenceEditorWnd::clearCompletionsListSelection()
{
  mp_ui->vignetteListWidget->clearSelection();
}

void
SequenceEditorWnd::completionsListSelectAt(int index)
{
  if(index == -1)
    {
      mp_ui->vignetteListWidget->selectAll();
      return;
    }

  QListWidgetItem *item = mp_ui->vignetteListWidget->item(index);
  item->setSelected(true);
}

void
SequenceEditorWnd::setWindowModified(bool isModified)
{
  emit polymerSequenceModifiedSignal();

  QWidget::setWindowModified(isModified);
}

void
SequenceEditorWnd::updateWindowTitle()
{
  if(msp_polymer->getFilePath().isEmpty())
    setWindowTitle(
      tr("%1 - %2[*]")
        .arg(QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription))
        .arg(tr("Untitled")));
  else
    {
      QFileInfo fileInfo(msp_polymer->getFilePath());

      setWindowTitle(
        tr("%1 - %2[*]")
          .arg(
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription))
          .arg(fileInfo.fileName()));
    }
}

void
SequenceEditorWnd::getsFocus()
{
  focusInEvent(0);
}

void
SequenceEditorWnd::updateMonomerPosition(int value)
{
  QString str;

  if(value > 0)
    str.setNum(value);
  else
    str = tr("N/A");

  mp_ui->monomerPositionLineEdit->setText(str);
}

void
SequenceEditorWnd::initialNonIonizedWholePolymerSequenceMassCalculation(
  bool deep)
{
  qDebug();

  mp_calcOptions->setIndexRange(
    libXpertMassCore::IndexRange(0, msp_polymer->size()));
  mp_calcOptions->setDeepCalculation(deep);

  // We are in the very first process of configuration. The polymer sequence
  // has been loaded but no masses have yet been computed for it,
  // even less any ionization has been performed (even if
  // msp_polymer->getIonizerCstRef() has been set to the PolChemDef default
  // ionization setting).

  // We now need to seed the polymer and selected oligomer masses to proper
  // values to also engage proper ionization calculations.

  // At present the masses of the Polymer are 0.00. We can thus engage
  // in the calculations starting from non-ionized.

  // First calculate the whole sequence masses for non-ionized.
  msp_polymer->calculateMasses(*mp_calcOptions, true /*reset*/);

  qDebug() << "After first mass calculation, the polymer masses are:"
           << msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO)
           << "and"
           << msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG);

  // Now the masses of the Polymer are calculated for non-ionized.
}

void
SequenceEditorWnd::updateWholeSequenceMasses(bool deep)
{
  mp_calcOptions->setIndexRange(
    libXpertMassCore::IndexRange(0, msp_polymer->size()));
  mp_calcOptions->setDeepCalculation(deep);

  // qDebug() << "The calculation options:" << mp_calcOptions->toString();

  // The mass calculation process never accounts for any ionization whatsoever.
  // So we calculate the masses and set the current ionization state to nothing.
  msp_polymer->calculateMasses(*mp_calcOptions, true /*reset*/);

  // qDebug() << qSetRealNumberPrecision(5)
  //          << "Right entering function, calculate the masses and get the "
  //             "analyte masses mono - avg:"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO)
  //          << "-"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG);

  // Because we computed the masses and that this process does not account
  // for any ionization state, set the current state to not ionized.
  msp_polymer->getIonizerRef().forceCurrentState(
    libXpertMassCore::Formula("+H-H"), 1, 0);

  // qDebug()
  //   << "After resetting the polymer's ionizer current state to not ionized:"
  //   << msp_polymer->getIonizerCstRef().toString();

  libXpertMassCore::ErrorList error_list;
  bool ok = msp_polymer->getIonizerCstRef().validate(&error_list);

  if(!ok || !msp_polymer->getIonizerCstRef().isValid())
    qFatal();

  libXpertMassCore::Enums::IonizationOutcome ionization_outcome =
    msp_polymer->ionize();

  // qDebug() << "Ionization outcome:"
  //          << libXpertMassCore::ionizationOutcomeMap[ionization_outcome];

  if(ionization_outcome == libXpertMassCore::Enums::IonizationOutcome::FAILED)
    qFatal() << "Failed to ionize the polymer sequence.";

  // qDebug() << qSetRealNumberPrecision(5)
  //          << "After ionization, the analyte masses mono - avg:"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO)
  //          << "-"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG);

  mp_ui->monoWholeMassLineEdit->setText(QString::number(
    msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO),
    'f',
    libXpertMassCore::POLYMER_DEC_PLACES));
  mp_ui->avgWholeMassLineEdit->setText(QString::number(
    msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG),
    'f',
    libXpertMassCore::POLYMER_DEC_PLACES));
}

void
SequenceEditorWnd::initialNonIonizedSelectedPolymerSequenceMassCalculation(
  bool deep)
{
  qDebug();

  // If there is a factual selection(that is the selection is marked
  // by the selection mark, then the indexes are according to this schema:

  // [ATGC] -> start: 0 end: 3

  // while if the selection is fake, that is no actual selection is
  // performed, then , if cursor is located at ATGC|, then the indexes
  // are according to this schemaa:

  // ATGC| -> start: 0 end: 4

  // Because the calculations in the polymer are based on a for loop,
  // we need to adjust the values prior to setting them in the
  // calculation options vehicle. Note that the values set in start
  // and end are already "sorted", so that start <= end.

  libXpertMassCore::IndexRangeCollection index_range_collection;

  // Should always return at least one item, that is the
  // pseudo-selection (start of sequence up to cursor index).
  bool was_real_selection =
    mpa_editorGraphicsView->selectionIndices(index_range_collection);

  if(!was_real_selection)
    {
      qDebug() << "There was no real selection.";
      qDebug() << "Coordinates list size:" << index_range_collection.size();
      libXpertMassCore::IndexRange *index_range_p =
        index_range_collection.getRangesCstRef().front();
      qDebug() << "First coordinates' indices:"
               << index_range_p->indicesAsText();
    }
  else
    {
      qDebug() << "There was a real selection.";
      qDebug() << "Coordinates list size:" << index_range_collection.size();
      libXpertMassCore::IndexRange *index_range_p =
        index_range_collection.getRangesCstRef().front();
      qDebug() << "First coordinates' indices:"
               << index_range_p->indicesAsText();
    }

  mp_calcOptions->setIndexRanges(index_range_collection);

  // qDebug() << mp_calcOptions->getIndexRangeCollectionCstRef()
  //               .getRangesCstRef()
  //               .front()
  //               .positionsAsText();

  mp_calcOptions->setDeepCalculation(deep);

  // We are in the very first process of configuration. The polymer sequence
  // has been loaded but no masses have yet been computed for it,
  // even less any ionization has been performed (even if
  // msp_polymer->getIonizerCstRef() has been set to the PolChemDef default
  // ionization setting).

  // We now need to seed the polymer and selected oligomer masses to proper
  // values to also engage proper ionization calculations.

  // At present the masses of the Polymer are 0.00. We can thus engage
  // in the calculations starting from non-ionized.

  // First calculate the whole sequence masses for non-ionized.
  msp_polymer->calculateMasses(*mp_calcOptions, true /*reset*/);

  // Now the masses of the Polymer are calculated for non-ionized.

  qDebug()
    << "After first mass calculation, the polymer selected sequence masses are:"
    << msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO) << "and"
    << msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG);

  // At this point, we can display the selection coordinates:
  QString selectionPositions = index_range_collection.positionsAsText();

  //     qDebug() << __FILE__ << __LINE__
  // 	     << "positions:" << selectionPositions;

  mp_ui->selectionLineEdit->setText(selectionPositions);
}

void
SequenceEditorWnd::updateSelectedSequenceMasses(bool deep)
{
  // qDebug();

  // If there is a factual selection(that is the selection is marked
  // by the selection mark, then the indexes are according to this schema:

  // [ATGC] -> start: 0 end: 3

  // while if the selection is fake, that is no actual selection is
  // performed, then , if cursor is located at ATGC|, then the indexes
  // are according to this schemaa:

  // ATGC| -> start: 0 end: 4

  // Because the calculations in the polymer are based on a for loop,
  // we need to adjust the values prior to setting them in the
  // calculation options vehicle. Note that the values set in start
  // and end are already "sorted", so that start <= end.

  libXpertMassCore::IndexRangeCollection index_range_collection;

  // Should always return at least one item, that is the
  // pseudo-selection (start of sequence up to cursor index).
  bool was_real_selection =
    mpa_editorGraphicsView->selectionIndices(index_range_collection);

  if(!was_real_selection)
    {
      qDebug() << "There was no real selection.";
      qDebug() << "Coordinates list size:" << index_range_collection.size();
      libXpertMassCore::IndexRange *index_range_p =
        index_range_collection.getRangesCstRef().front();
      qDebug() << "First coordinates' indices:"
               << index_range_p->indicesAsText();
    }
  else
    {
      qDebug() << "There was a real selection.";
      qDebug() << "Coordinates list size:" << index_range_collection.size();
      libXpertMassCore::IndexRange *index_range_p =
        index_range_collection.getRangesCstRef().front();
      qDebug() << "First coordinates' indices:"
               << index_range_p->indicesAsText();
    }

  mp_calcOptions->setIndexRanges(index_range_collection);
  mp_calcOptions->setDeepCalculation(deep);

  // qDebug() << mp_calcOptions->getIndexRangeCollectionCstRef()
  //               .getRangesCstRef()
  //               .front()
  //               ->positionsAsText();

  // qDebug() << "The calculation options:" << mp_calcOptions->toString();

  // The mass calculation process never accounts for any ionization whatsoever.
  // So we calculate the masses and set the current ionization state to nothing.
  msp_polymer->calculateMasses(*mp_calcOptions, true /*reset*/);

  // qDebug() << qSetRealNumberPrecision(5)
  //          << "Right entering function, calculate the masses and get the "
  //             "analyte masses mono - avg:"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO)
  //          << "-"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG);

  // Because we computed the masses and that this process does not account
  // for any ionization state, set the current state to not ionized.

  msp_polymer->getIonizerRef().forceCurrentState(
    libXpertMassCore::Formula("+H-H"), 1, 0);

  // qDebug()
  //   << "After resetting the polymer's ionizer current state to not ionized:"
  //   << msp_polymer->getIonizerCstRef().toString();

  libXpertMassCore::ErrorList error_list;
  bool ok = msp_polymer->getIonizerCstRef().validate(&error_list);

  if(!ok || !msp_polymer->getIonizerCstRef().isValid())
    qFatal();

  libXpertMassCore::Enums::IonizationOutcome ionization_outcome =
    msp_polymer->ionize();

  // qDebug() << "Ionization outcome:"
  //          << libXpertMassCore::ionizationOutcomeMap[ionization_outcome];

  if(ionization_outcome == libXpertMassCore::Enums::IonizationOutcome::FAILED)
    qFatal() << "Failed to ionize the polymer sequence.";

  // qDebug() << qSetRealNumberPrecision(5)
  //          << "After ionization, the analyte masses mono - avg:"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO)
  //          << "-"
  //          << msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG);

  mp_ui->monoSelectionMassLineEdit->setText(QString::number(
    msp_polymer->getMass(libXpertMassCore::Enums::MassType::MONO),
    'f',
    libXpertMassCore::POLYMER_DEC_PLACES));
  mp_ui->avgSelectionMassLineEdit->setText(QString::number(
    msp_polymer->getMass(libXpertMassCore::Enums::MassType::AVG),
    'f',
    libXpertMassCore::POLYMER_DEC_PLACES));

  // At this point, we can display the selection coordinates:
  QString selectionPositions = index_range_collection.positionsAsText();

  //     qDebug() << __FILE__ << __LINE__
  // 	     << "positions:" << selectionPositions;

  mp_ui->selectionLineEdit->setText(selectionPositions);
}

void
SequenceEditorWnd::updateMassesNewDecimalsOptions()
{
  updateSelectedSequenceMasses(false);
  updateWholeSequenceMasses(false);
}

void
SequenceEditorWnd::wholeSequenceMasses(double *mono, double *avg)
{
  QString mass;
  bool ok = false;

  // First get the text string and make a double out of it.

  if(mono)
    {
      mass  = mp_ui->monoWholeMassLineEdit->text();
      *mono = mass.toDouble(&ok);
    }

  if(avg)
    {
      mass = mp_ui->avgWholeMassLineEdit->text();
      *avg = mass.toDouble(&ok);
    }
}

void
SequenceEditorWnd::selectedSequenceMasses(double *mono, double *avg)
{
  QString mass;
  bool ok = false;

  if(mono)
    {
      mass  = mp_ui->monoSelectionMassLineEdit->text();
      *mono = mass.toDouble(&ok);
    }

  if(avg)
    {
      mass = mp_ui->avgSelectionMassLineEdit->text();
      *avg = mass.toDouble(&ok);
    }
}

////////////////////////////// SLOTS ///////////////////////////////
bool
SequenceEditorWnd::save()
{
  // We must save to an xml file. It might be that the polymer
  // sequence is totally new, in which case the filePath() call will
  // return something invalid as a QFile object. In that case we ask
  // the saveAs() to do the job.

  if(!QFile::exists(msp_polymer->getFilePath()))
    return saveAs();

  if(!msp_polymer->writeXmlFile())
    {
      statusBar()->showMessage(tr("File save failed."));

      QMessageBox msgBox;

      msgBox.setText("Failed to save the document.");
      msgBox.setInformativeText(
        "Please, check that you have "
        "write permissions on the file.");
      msgBox.setStandardButtons(QMessageBox::Ok);

      msgBox.exec();

      return false;
    }

  statusBar()->showMessage(tr("File save succeeded."));
  setWindowModified(false);
  //  updateWindowTitle();

  return true;
}

bool
SequenceEditorWnd::saveAs()
{

  // We must save the new contents of the polymer sequence to an xml
  // file that is not the file from which this sequence might have
  // been saved.

  QString filePath = QFileDialog::getSaveFileName(
    this,
    tr("Save libXpertMassCore::Polymer libXpertMassCore::Sequence File"),
    QDir::homePath(),
    tr("mXp files(*.mxp *.mXp *.MXP)"));

  if(filePath.isEmpty())
    {
      statusBar()->showMessage(tr("Filepath is empty."));
      return false;
    }

  msp_polymer->setFilePath(filePath);

  if(!msp_polymer->writeXmlFile())
    {
      statusBar()->showMessage(tr("File save failed."));

      QMessageBox msgBox;

      msgBox.setText("Failed to save the document.");
      msgBox.setInformativeText(
        "Please, check that you have "
        "write permissions on the file.");
      msgBox.setStandardButtons(QMessageBox::Ok);

      msgBox.exec();

      return false;
    }

  statusBar()->showMessage(tr("File save succeeded."));
  setWindowModified(false);
  updateWindowTitle();

  return true;
}

void
SequenceEditorWnd::importRaw()
{
  // Open a text file and make a Sequence out of it. If errors,
  // then show the purification dialog.

  libXpertMassCore::Sequence sequence;

  QString filePath;
  QString name;


  filePath = QFileDialog::getOpenFileName(
    this, tr("Import Raw Text File"), QDir::homePath(), tr("Any file type(*)"));

  if(!QFile::exists(filePath))
    return;

  // Read the sequence.

  QFile file(filePath);

  if(!file.open(QFile::ReadOnly))
    return;

  QTextStream stream(&file);

  std::vector<std::size_t> failing_indices;

  while(!stream.atEnd())
    {
      QString line = stream.readLine(1000);

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

      // The function below will create the Monomer instances right away.
      int result = sequence.appendSequence(line, failing_indices);
      if(result == -1)
        qCritical()
          << "The processed Sequence contained failing Monomer codes.";
    }

  if(failing_indices.size())
    {
      QMessageBox::critical(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to import the sequence. Please check the validity of the "
           "Monomer codes."),
        QMessageBox::Ok);

      return;
    }

  int sequence_size = sequence.size();

  if(!sequence_size)
    {
      QMessageBox::information(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("The file does not contain any sequence."),
        QMessageBox::Ok);
    }

  // At this point we can paste...
  int ret = mpa_editorGraphicsView->insertSequenceAtPoint(sequence);

  if(ret != sequence_size)
    {
      QMessageBox::critical(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to import the sequence."),
        QMessageBox::Ok);
    }
}

void
SequenceEditorWnd::clipboardCopy(QClipboard::Mode mode)
{
  libXpertMassCore::IndexRangeCollection index_range_collection;

  if(mpa_editorGraphicsView->selectionIndices(index_range_collection))
    {
      int index_range_count = index_range_collection.size();

      // There is a real selection, which is what
      // we want, but if there are more than one region selections,
      // then inform the user.

      if(index_range_count > 1)
        {
          QMessageBox::warning(this,
                               QString("%1 - %2 - %3")
                                 .arg(m_applicationName)
                                 .arg(m_windowDescription)
                                 .arg("Export sequence to clipboard"),

                               tr(" The sequence exported to the clipboard "
                                  "will comprise %1 region selections.")
                                 .arg(index_range_count),
                               QMessageBox::Ok);
        }

      QString text;

      foreach(const libXpertMassCore::IndexRange *item,
              index_range_collection.getRangesCstRef())
        text += msp_polymer->getSequenceCstRef().getSequence(
          item->m_start, item->m_stop, false);

      QClipboard *clipboard = QApplication::clipboard();
      clipboard->setText(text, mode);
    }
}

void
SequenceEditorWnd::clipboardCut(QClipboard::Mode mode)
{
  libXpertMassCore::IndexRangeCollection index_range_collection;

  if(mpa_editorGraphicsView->selectionIndices(index_range_collection))
    {
      // There is a real selection, which is what we want, but if
      // there are more than one region selections than we cannot
      // perform the action.
      int index_range_count = index_range_collection.size();

      if(index_range_count > 1)
        {
          QMessageBox::information(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            tr("Cut operations are not supported "
               "in multi-region selection mode."),
            QMessageBox::Ok);

          return;
        }

      QString text = msp_polymer->getSequenceCstRef().getSequence(
        index_range_collection.getRangesCstRef().back()->m_start,
        index_range_collection.getRangesCstRef().back()->m_stop,
        false);

      QClipboard *clipboard = QApplication::clipboard();
      clipboard->setText(text, mode);

      mpa_editorGraphicsView->removeSelectedOligomer();
    }
}

void
SequenceEditorWnd::clipboardPaste(QClipboard::Mode mode)
{
  libXpertMassCore::IndexRangeCollection index_range_collection;

  if(mpa_editorGraphicsView->selectionIndices(index_range_collection))
    {
      // There is a real selection, which is what we want, but if
      // there are more than one region selections than we cannot
      // perform the action.
      int index_range_count = index_range_collection.size();

      if(index_range_count > 1)
        {
          QMessageBox::information(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            tr("Paste operations are not supported "
               "in multi-region selection mode."),
            QMessageBox::Ok);

          return;
        }

      // There is one region selected, we have to first remove it
      // and then we insert the pasted sequence, which equals to a
      // sequence replacement.

      mpa_editorGraphicsView->removeSelectedOligomer();
    }

  QClipboard *clipboard = QApplication::clipboard();
  QString text;

  if(mode == QClipboard::Selection)
    text = clipboard->text(QClipboard::Selection);
  else
    text = clipboard->text(QClipboard::Clipboard);

  if(text.isEmpty())
    return;

  libXpertMassCore::Sequence sequence(
    mup_polChemDefRendering->getPolChemDefCstSPtr(), text);

  if(!sequence.isValid())
    {
      QMessageBox::critical(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to paste the sequence (the sequence is invalid)."),
        QMessageBox::Ok);

      return;
    }

  // At this point we can paste...
  int size = sequence.size();
  int ret  = mpa_editorGraphicsView->insertSequenceAtPoint(sequence);

  if(ret != size)
    {
      QMessageBox::critical(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to paste the sequence."),
        QMessageBox::Ok);
    }
}

void
SequenceEditorWnd::clipboardClear(QClipboard::Mode mode)
{
  QClipboard *clipboard = QApplication::clipboard();

  clipboard->clear(mode);
}

void
SequenceEditorWnd::findSequence()
{
  SequenceEditorFindDlg *dlg = new SequenceEditorFindDlg(
    this,
    msp_polymer,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Find sequence element");

  dlg->show();

  return;
}

void
SequenceEditorWnd::vignetteSizeChanged()
{
  int size = mp_ui->vignetteSizeSpinBox->value();

  if(!m_postInitialized)
    return;

  mpa_editorGraphicsView->requestVignetteSize(size);
}

void
SequenceEditorWnd::nameLineEditChanged(const QString &text)
{
  msp_polymer->setName(text);
  setWindowModified(true);
  updateWindowTitle();
}

MonomerModificationDlg *
SequenceEditorWnd::modifMonomer()
{
  MonomerModificationDlg *dlg = new MonomerModificationDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Mononomer modification");

  dlg->show();
  return dlg;
}

PolymerModificationDlg *
SequenceEditorWnd::modifPolymer()
{

  PolymerModificationDlg *dlg = new PolymerModificationDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Polymer modification");

  dlg->show();
  return dlg;
}

PolymerModificationDlg *
SequenceEditorWnd::modifLeftEnd()
{
  PolymerModificationDlg *dlg = new PolymerModificationDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Polymer modification",
    libXpertMassCore::Enums::PolymerEnd::LEFT);

  dlg->show();
  return dlg;
}

PolymerModificationDlg *
SequenceEditorWnd::modifRightEnd()
{
  PolymerModificationDlg *dlg = new PolymerModificationDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Polymer modification",
    libXpertMassCore::Enums::PolymerEnd::RIGHT);

  dlg->show();
  return dlg;
}

void
SequenceEditorWnd::modifPolymerEnd(libXpertMassCore::Enums::PolymerEnd end,
                                   const QString &modif_name)
{
  qDebug() << "Now modifying polymer" << end << "end with modif:" << modif_name;

  bool ok = false;

  if(static_cast<bool>(end & libXpertMassCore::Enums::PolymerEnd::LEFT))
    {
      ok = msp_polymer->setLeftEndModifByName(modif_name);

      if(!ok)
        qCritical() << "Failed to modify the polymer's left end with"
                    << modif_name;
    }

  if(static_cast<bool>(end == libXpertMassCore::Enums::PolymerEnd::RIGHT))
    {
      ok = msp_polymer->setRightEndModifByName(modif_name);

      if(!ok)
        qCritical() << "Failed to modify the polymer's right end with"
                    << modif_name;
    }

  updatePolymerEndsModifs();

  updateWholeSequenceMasses(true);
  updateSelectedSequenceMasses(true);
}

MonomerCrossLinkDlg *
SequenceEditorWnd::crossLinkMonomers()
{
  MonomerCrossLinkDlg *dlg = new MonomerCrossLinkDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Monomer cross-linking");

  // Make the connection of the signal/slot pair, so that when a
  // crossLink changes in the polymer sequence, the
  // MonomerCrossLinkDlg will be triggered to redisplay the data.

  QObject::connect(msp_polymer.get(),
                   &libXpertMassCore::Polymer::crossLinkChangedSignal,
                   dlg,
                   &MonomerCrossLinkDlg::crossLinkChangedSlot);

  QObject::connect(this,
                   &SequenceEditorWnd::polymerSequenceModifiedSignal,
                   dlg,
                   &MonomerCrossLinkDlg::polymerSequenceModifiedSlot);

  dlg->show();
  return dlg;
}

CleavageDlg *
SequenceEditorWnd::cleave()
{
  // Before calling the cleavage window, compute a crypto hash of the
  // sequence as it is currently displayed, so that we can transmit it to
  // the cleavage dialog which will need it to make sure that we do not
  // access from there (specifically from the treeview widgets) the polymer
  // sequence that might have changed (typically shortened) after having
  // computed a cleavage.

  QByteArray hash = msp_polymer->md5Sum(
    static_cast<
      std::underlying_type_t<libXpertMassCore::Enums::HashAccountData>>(
      libXpertMassCore::Enums::HashAccountData::SEQUENCE) |
    static_cast<
      std::underlying_type_t<libXpertMassCore::Enums::HashAccountData>>(
      libXpertMassCore::Enums::HashAccountData::MONOMER_MODIF));

  CleavageDlg *dlg = new CleavageDlg(
    this,
    msp_polymer,
    mup_polChemDefRendering->getPolChemDefCstSPtr(),
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Polymer cleavage",
    hash,
    *mp_calcOptions,
    msp_polymer->getIonizerCstRef());

  // Relay the signal so that the program window gets it.
  connect(dlg,
          &CleavageDlg::displayMassSpectrumSignal,
          [this](const QString &title,
                 const QByteArray &color_byte_array,
                 pappso::TraceCstSPtr trace) {
            // qDebug() << "Relaying the CleavageDlg displayMassSpectrumSignal "
            //"from sequence editor window:"
            //<< this;
            emit displayMassSpectrumSignal(title, color_byte_array, trace);
          });

  dlg->show();

  return dlg;
}

FragmentationDlg *
SequenceEditorWnd::fragment()
{
  // At the moment we can only perform fragmentation on single
  // region selections.

  libXpertMassCore::IndexRangeCollection index_range_collection;

  bool res = mpa_editorGraphicsView->selectionIndices(index_range_collection);

  if(!res)
    {
      QMessageBox::information(this,
                               m_applicationName,
                               tr("No oligomer is selected. "
                                  "Select an oligomer first"),
                               QMessageBox::Ok);
      return nullptr;
    }

  if(index_range_collection.size() > 1)
    {
      QMessageBox::information(this,
                               m_applicationName,
                               tr("Fragmentation simulations are not "
                                  "supported\nin multi-region selection "
                                  "mode."),
                               QMessageBox::Ok);
      return nullptr;
    }

  if((bool)(mp_calcOptions->getMonomerEntities() &
            libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER))
    {
      // Check what is the situation with respect to the cross-links,
      // which, from a fragmentation standpoint are particularly
      // difficult to handle. In particular, alert the user if the
      // select oligomer has a partial cross-link (a cross-link that
      // links on oligomer monomer to a monomer outside of that
      // oligomer).

      int start_index =
        index_range_collection.getRangesCstRef().front()->m_start;
      int stop_index = index_range_collection.getRangesCstRef().front()->m_stop;

      // Get the cross-links for the region.
      std::vector<MsXpS::libXpertMassCore::CrossLinkSPtr> cross_links;
      std::size_t partials_count = 0;

      // bool at_least_one_fully_encompassed_found =
      msp_polymer->crossLinksInRange(
        start_index, stop_index, cross_links, partials_count);

      if(partials_count)
        {
          QMessageBox::warning(this,
                               m_applicationName,
                               tr("Fragmentation calculations do not\n"
                                  "take into account partial cross-links.\n"
                                  "These partial cross-links are ignored."),
                               QMessageBox::Ok);
        }
    }

  // No need to do this here, because the dialog window will have
  // its own calcOptions copy and will make this work anyway upon
  // initialization.

  //    mp_calcOptions->setCoordinateList(index_range_collection);

  FragmentationDlg *dlg = new FragmentationDlg(
    this,
    msp_polymer,
    mup_polChemDefRendering->getPolChemDefCstSPtr(),
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Polymer fragmentation",
    *mp_calcOptions,
    msp_polymer->getIonizerCstRef());

  // Relay the signal so that the program window gets it.
  connect(dlg,
          &FragmentationDlg::displayMassSpectrumSignal,
          [this](const QString &title,
                 const QByteArray &color_byte_array,
                 pappso::TraceCstSPtr trace) {
            // qDebug()
            //<< "Relaying the displayMassSpectrumSignal from FragmentationDlg";
            emit displayMassSpectrumSignal(title, color_byte_array, trace);
          });

  dlg->show();

  return dlg;
}

MassSearchDlg *
SequenceEditorWnd::massSearch()
{
  MassSearchDlg *dlg = new MassSearchDlg(
    this,
    msp_polymer,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Mass searches",
    *mp_calcOptions,
    msp_polymer->getIonizerCstRef());

  dlg->show();

  return dlg;
}

MzCalculationDlg *
SequenceEditorWnd::mzCalculation()
{
  // We should try to feed the mz calculation dialog with the mass
  // data from the sequence editor window. If there is a real
  // selection, than use the masses of the selected
  // region(s). Otherwise use the masses for the whole sequence.

  libXpertMassCore::IndexRangeCollection index_range_collection;
  double mono = 0;
  double avg  = 0;

  if(mpa_editorGraphicsView->selectionIndices(index_range_collection))
    {
      // There is real selection, set the masses for that selection
      // in the newly created dialog window.

      selectedSequenceMasses(&mono, &avg);
    }
  else
    {
      wholeSequenceMasses(&mono, &avg);
    }

  // qDebug() << "mono:" << mono << "avg:" << avg;

  MzCalculationDlg *dlg = new MzCalculationDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    mup_polChemDefRendering->getPolChemDefCstSPtr(),
    m_applicationName,
    "m/z ratio calculator",
    msp_polymer->getIonizerCstRef(),
    mono,
    avg);

  dlg->show();

  return dlg;
}

CompositionsDlg *
SequenceEditorWnd::compositions()
{
  CompositionsDlg *dlg = new CompositionsDlg(
    this,
    msp_polymer,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Composition calculator",
    *mp_calcOptions,
    msp_polymer->getIonizerCstRef());

  dlg->show();

  return dlg;
}

PkaPhPiDlg *
SequenceEditorWnd::pkaPhPi()
{
  // Make sure we can read the data file.

  // Where is the data file?
  QString file_path =
    mup_polChemDefRendering->getPolChemDefCstSPtr()->getXmlDataDirPath() +
    "/pka_ph_pi.xml";

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "pkaPhPi file is" << file_path;

  // Allocate the lists in which to store the different monomers and
  // modifs allocated upon parsing of the xml file.

  std::vector<MsXpS::libXpertMassCore::MonomerSPtr> monomers;
  std::vector<MsXpS::libXpertMassCore::ModifSPtr> modifs;

  // Create the parser using the above file_path.
  libXpertMassCore::PkaPhPiDataParser parser(
    mup_polChemDefRendering->getPolChemDefCstSPtr(), file_path);

  // Ask that the rendering be performed.
  if(!parser.renderXmlFile(monomers, modifs))
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("pKa pH pI - %1@%2\n"
           "Failed to render xml file(%3).")
          .arg(__FILE__)
          .arg(__LINE__)
          .arg(file_path),
        QMessageBox::Ok);
      return nullptr;
    }

  libXpertMassCore::PkaPhPi pka_ph_pi(
    mup_polChemDefRendering->getPolChemDefCstSPtr(),
    msp_polymer,
    *mp_calcOptions);
  pka_ph_pi.setMonomers(std::move(monomers));
  pka_ph_pi.setModifs(std::move(modifs));

  PkaPhPiDlg *dlg = new PkaPhPiDlg(
    this,
    msp_polymer,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    m_applicationName,
    "Isoelectric point (pI) calculator",
    std::move(pka_ph_pi),
    *mp_calcOptions);

  dlg->show();

  return dlg;
}

void
SequenceEditorWnd::decimalPlacesOptions()
{
  libXpertMassGui::DecimalPlacesOptionsDlg *dlg =
    new libXpertMassGui::DecimalPlacesOptionsDlg(
      dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(), this);

  connect(dlg,
          &libXpertMassGui::DecimalPlacesOptionsDlg::
            decimalPlacesOptionsChangedSignal,
          this,
          &SequenceEditorWnd::updateMassesNewDecimalsOptions);

  dlg->show();
}

void
SequenceEditorWnd::newCalculatorWholeSequenceMasses()
{
  // We want to start a new calculator preseeded with the whole
  // sequence mono/avg masses. Let's get them right away.

  QString mono = mp_ui->monoWholeMassLineEdit->text();
  QString avg  = mp_ui->avgWholeMassLineEdit->text();

  newCalculator(mono, avg);
}

void
SequenceEditorWnd::newCalculatorSelectedSequenceMasses()
{
  // We want to start a new calculator preseeded with the selected
  // sequence mono/avg masses. Let's get them right away.

  QString mono = mp_ui->monoSelectionMassLineEdit->text();
  QString avg  = mp_ui->avgSelectionMassLineEdit->text();

  newCalculator(mono, avg);
}

void
SequenceEditorWnd::newCalculator(QString mono, QString avg)
{
  // To start a new calculator, we have to get the name of the file
  // that contains the polymer chemistry definition. Let's get that
  // filePath from the polymer chemistry definition of *this*
  // sequence editor window.

  QString filePath =
    mup_polChemDefRendering->getPolChemDefCstSPtr()->getXmlDataFilePath();

  // Open a calculator window with the proper polymer chemistry
  // definition and also with the proper masses to preseed.

  new CalculatorWnd(
    mp_parentWnd, filePath, m_applicationName, "Calculator", mono, avg);
}

void
SequenceEditorWnd::crossLinksPartiallyEncompassedSlot(int count)
{
  mp_ui->incompleteCrossLinkWarningLabel->setText(
    tr("Incomplete cross-links: %1").arg(count));
}

void
SequenceEditorWnd::keyPressEvent(QKeyEvent *event)
{
  event->accept();
}

void
SequenceEditorWnd::updatePolymerEndsModifs()
{

  QString text;

  text = msp_polymer->getLeftEndModifCstRef().getName();

  if(!text.isEmpty())
    {
      mp_ui->leftEndModifPushButton->setText(text);
    }
  else
    {
      mp_ui->leftEndModifPushButton->setText(tr("NOT_SET"));
    }

  text = msp_polymer->getRightEndModifCstRef().getName();

  if(!text.isEmpty())
    {
      mp_ui->rightEndModifPushButton->setText(text);
    }
  else
    {
      mp_ui->rightEndModifPushButton->setText(tr("NOT_SET"));
    }
}

void
SequenceEditorWnd::vignetteListWidgetItemDoubleClicked(QListWidgetItem *item)
{
  QString text = item->text();

  // The line of text has the following format:

  // Xcode=Name

  // Thus, we want to get the string prior to the '='

  QStringList elements = text.split("=");

  QString code = elements.first();

  // Now that we know the code, we can insert it at the right
  // place. First create a sequence with that code.

  libXpertMassCore::Sequence sequence(
    mup_polChemDefRendering->getPolChemDefCstSPtr(), code);

  if(!sequence.isValid())
    qCritical() << "The sequence created with code" << code
                << "did not validate successfully.";

  mpa_editorGraphicsView->insertSequenceAtPoint(sequence);
}

QString
SequenceEditorWnd::getSequenceName() const
{
  return mp_ui->sequenceNameLineEdit->text();
}

void
SequenceEditorWnd::setCumulatedProbabilities(double cumul_probs)
{
  mp_ui->cumulatedProbsDoubleSpinBox->setValue(cumul_probs);
}

void
SequenceEditorWnd::setNormalizationIntensity(double intensity)
{
  mp_ui->normalizeIntensityDoubleSpinBox->setValue(intensity);
}

void
SequenceEditorWnd::traceColorPushButtonClicked()
{

  QPushButton *colored_push_button = mp_ui->colorSelectorPushButton;

  // Allow (true) the user to select a color that has been chosen already.
  QColor color = libXpertMassGui::ColorSelector::chooseColor(true);

  if(color.isValid())
    {
      QPalette palette = colored_push_button->palette();
      palette.setColor(QPalette::Button, color);
      colored_push_button->setAutoFillBackground(true);
      colored_push_button->setPalette(palette);
      colored_push_button->update();

      // Now prepare the color in the form of a QByteArray

      QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly);

      stream << color;
    }
}

void
SequenceEditorWnd::setTraceColor(QColor color)
{
  // We want to color the pushbutton.
  QPushButton *colored_push_button = mp_ui->colorSelectorPushButton;

  if(color.isValid())
    {
      QPalette palette = colored_push_button->palette();
      palette.setColor(QPalette::Button, color);
      colored_push_button->setAutoFillBackground(true);
      colored_push_button->setPalette(palette);
      colored_push_button->update();

      // Now prepare the color in the form of a QByteArray

      QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly);

      stream << color;
    }
}

void
SequenceEditorWnd::setTraceTitle(const QString &title)
{
  mp_ui->massSpectrumTitleLineEdit->setText(title);
}

libXpertMassGui::MassPeakShaperConfigDlg *
SequenceEditorWnd::configureMassPeakShaper()
{
  qDebug();

  if(mp_massPeakShaperConfigDlg == nullptr)
    {
      mp_massPeakShaperConfigDlg = new libXpertMassGui::MassPeakShaperConfigDlg(
        this, m_applicationName, "Mass peak shaper configuration");

      if(mp_massPeakShaperConfigDlg == nullptr)
        qFatal("Programming error. Failed to allocate the dialog window.");
    }

  // The signal below is only emitted when checking parameters worked ok.
  connect(mp_massPeakShaperConfigDlg,
          &libXpertMassGui::MassPeakShaperConfigDlg::
            updatedMassPeakShaperConfigSignal,
          [this](const libXpertMassCore::MassPeakShaperConfig &config) {
            m_massPeakShaperConfig.initialize(config);

            // qDebug().noquote() << "Our local copy of the config:"
            //<< m_massPeakShaperConfig.toString();
          });

  mp_massPeakShaperConfigDlg->activateWindow();
  mp_massPeakShaperConfigDlg->raise();
  mp_massPeakShaperConfigDlg->show();

  qDebug() << "Now returning pointer to the mass peak shaper dialog window.";

  return mp_massPeakShaperConfigDlg;
}

bool
SequenceEditorWnd::loadIsotopicDataFromFile(const QString &file_path)
{
  QString selected_file_path = file_path;
  QFileInfo file_info(selected_file_path);

  if(selected_file_path.isEmpty() || !file_info.isFile())
    {
      selected_file_path = QFileDialog::getOpenFileName(
        this, tr("Load User IsoSpec table"), QDir::home().absolutePath());

      if(selected_file_path.isEmpty())
        {
          QMessageBox::warning(
            this,
            QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
            tr("Failed to set the file name."),
            QMessageBox::Ok);

          return false;
        }
    }

  qDebug() << "The selected file path:" << selected_file_path;

  file_info.setFile(selected_file_path);
  if(!file_info.isReadable())
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        QString("The selected file\n%1\n is not readable.")
          .arg(selected_file_path),
        QMessageBox::Ok);

      return false;
    }

  if(msp_isotopicData == nullptr)
    msp_isotopicData = std::make_shared<libXpertMassCore::IsotopicData>();

  // We need to instantiate the proper isotopic data handler. In our present
  // case that has to be user config handler, we do not want the library
  // handler.

  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler(
    msp_isotopicData);

  if(!isotopic_data_handler.loadData(selected_file_path) ||
     !msp_isotopicData->size())
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        "Failed to load the isotopic data.",
        QMessageBox::Ok);

      return false;
    }

  // qDebug() << "Could load isotopic data from file" << selected_file_path;

  return true;
}

void
SequenceEditorWnd::synthesizeMassSpectra()
{
  if(!msp_polymer->size())
    return;

  // Synthesize a mass spectrum with the elemental composition determined using
  // the current engine configuration.

  // Clear the synthetic mass spectrum. Make sure the mass spectrum to clipboard
  // menu item is not "available". Same for to file.
  m_syntheticMassSpectrum.clear();
  mp_ui->massSpectrumSynthesisComboBox->setItemText(
    (int)MassSpectrumSynthesisActions::COPY_MASS_SPECTRUM_TO_CLIPBOARD,
    "Mass spectrum not yet available");

  mp_ui->massSpectrumSynthesisComboBox->setItemText(
    (int)MassSpectrumSynthesisActions::SAVE_MASS_SPECTRUM_TO_FILE,
    "Mass spectrum not yet available");

  setCursor(Qt::WaitCursor);

  libXpertMassCore::ErrorList error_list;

  if(!m_massPeakShaperConfig.resolve(error_list))
    {
      QMessageBox msgBox;
      QString msg =
        QString("Mass peak shaper configuration errors:\n%1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list, "\n"));
      msgBox.setText(msg);
      msgBox.exec();

      configureMassPeakShaper();
    }

  // At this point, we need to get a handle on the isotopic data. Note how we
  // want a non-const shared pointer!

  if(msp_isotopicData == nullptr)
    {
      qDebug()
        << "Using the isotopic data from the polymer chemistry definition.";

      msp_isotopicData = std::make_shared<libXpertMassCore::IsotopicData>(
        *mup_polChemDefRendering->getPolChemDefCstSPtr()
           ->getIsotopicDataCstSPtr());
    }

  if(msp_isotopicData == nullptr)
    qFatal("Programming error. The isotopic data cannot be nullptr.");

  // Great, we now we have isotopes to do the calculations!

  // And now we can instantiate a cluster generator

  libXpertMassCore::IsotopicClusterGenerator isotopic_cluster_generator(
    msp_isotopicData);

  // mp_calcOptions->debugPutStdErr();

  // We need to provide the polymer coordinates. When a sequence is opened anew,
  // the cursor is left of the first monomer (if any monomer is present in the
  // sequence). This is displayed in the coordinates as [ 0 -- 1 ]. We need to
  // check this because if this is so, we instead craft coordinates that match
  // the entire polymer sequence.

  libXpertMassCore::IndexRangeCollection index_range_collection;

  // Returns true if there is actually a really manually set selection (greyed
  // area over the selected sequence). Otherwise returns false. In this case,
  // either the cursor has been left untouched since opening the sequence and
  // then it is located left of the first monomer vignette or it is located as a
  // text cursor in the middle of some text.
  if(!mpa_editorGraphicsView->selectionIndices(index_range_collection))
    {
      if(!index_range_collection.getRangesCstRef().front()->isValid())
        {
          // We are in the situation where the cursor is left of the first
          // monomer. That is the situation where we want to simulate the
          // selection of the whole sequence.

          // qDebug() << "Now faking whole polymer selection.";

          index_range_collection.setIndexRange(0, msp_polymer->size() - 1);
        }
      else
        qDebug()
          << "There is no real selection but cursor-determined selection:"
          << index_range_collection.getRangesCstRef().front()->indicesAsText();
    }

  qDebug() << "Going to determine the polymer elemental composition.";

  QString elemental_composition = msp_polymer->elementalComposition(
    index_range_collection, *mp_calcOptions, msp_polymer->getIonizerCstRef());

  std::pair<QString, int> formula_charge_pair(
    elemental_composition, msp_polymer->getIonizerCstRef().getLevel());

  isotopic_cluster_generator.setFormulaChargePair(formula_charge_pair);

  isotopic_cluster_generator.setIsotopicDataType(
    libXpertMassCore::IsotopicDataType::LIBRARY_CONFIG);

  double cumulated_probabilities = mp_ui->cumulatedProbsDoubleSpinBox->value();
  isotopic_cluster_generator.setMaxSummedProbability(cumulated_probabilities);
  double normalization_intensity =
    mp_ui->normalizeIntensityDoubleSpinBox->value();

  // Not needed here because will be performed later at peak shaping time.
  // qDebug() << "Setting the normalization intensity to" <<
  // normalization_intensity;
  //
  isotopic_cluster_generator.setNormalizationIntensity(normalization_intensity);

  // And now perform the work. For each formula/charge pair, the generator will
  // create an isotopic cluster shaped trace associated to the corresponding
  // charge in a libXpertMassCore::IsotopicClusterChargePair. All these pairs
  // are stored in a vector.

  int count = isotopic_cluster_generator.run();

  if(!count)
    {
      qDebug() << "Failed to create any isotopic cluster.";

      QMessageBox::information(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        "Failed to compute a single isotopic cluster.",
        QMessageBox::Ok);

      setCursor(Qt::ArrowCursor);
      return;
    }

  // qDebug() << "The number of clusters generated:" << count;
  std::vector<libXpertMassCore::IsotopicClusterChargePair>
    isotopic_cluster_charge_pairs =
      isotopic_cluster_generator.getIsotopicClusterChargePairs();

  // At this point we should use these pairs to create a shape for each. But
  // first reset to 1 the charge because that charge was already accounted for
  // at the generation of the cluster. We do not want to divide m/z again by
  // charge. If charge had been 1, that would be no problem, but let's say the
  // charge was 2, if we did maintain that charge to a value of 2, then we would
  // get a tetra-protonated species cluster.

  // Note how we ask a reference to the pair that is iterated into, otherwise we
  // would get a copy and we would lose the local charge modification.
  for(libXpertMassCore::IsotopicClusterChargePair &pair :
      isotopic_cluster_charge_pairs)
    pair.second = 1;

  qDebug();

  // Now instantiate the isotopic cluster shaper and set the clusters' data in
  // it.

  libXpertMassCore::IsotopicClusterShaper isotopic_cluster_shaper(
    isotopic_cluster_charge_pairs, m_massPeakShaperConfig);

  isotopic_cluster_shaper.setNormalizeIntensity(normalization_intensity);

  // And now run the shaper.
  m_syntheticMassSpectrum = isotopic_cluster_shaper.run();

  if(!m_syntheticMassSpectrum.size())
    {
      qDebug() << "The synthetic mass spectrum has not a single data point.";
      setCursor(Qt::ArrowCursor);
      return;
    }

  mp_ui->massSpectrumSynthesisComboBox->setItemText(
    (int)MassSpectrumSynthesisActions::COPY_MASS_SPECTRUM_TO_CLIPBOARD,
    "Mass spectrum to clipboard");

  mp_ui->massSpectrumSynthesisComboBox->setItemText(
    (int)MassSpectrumSynthesisActions::SAVE_MASS_SPECTRUM_TO_FILE,
    "Mass spectrum to file");

  setCursor(Qt::ArrowCursor);

  QString trace_title = mp_ui->massSpectrumTitleLineEdit->text();

  if(trace_title.isEmpty())
    trace_title = getSequenceName();

  if(trace_title.isEmpty())
    trace_title = msp_polymer->getFilePath();

  emit displayMassSpectrumSignal(
    trace_title,
    m_colorByteArray,
    std::make_shared<const pappso::Trace>(m_syntheticMassSpectrum));
}

void
SequenceEditorWnd::massSpectrumSynthesisMenuActivated(int index)
{
  if(index == (int)MassSpectrumSynthesisActions::LOAD_ISOTOPIC_DATA)
    {
      loadIsotopicDataFromFile();
      return;
    }
  if(index == (int)MassSpectrumSynthesisActions::CONFIGURE_MASS_PEAK_SHAPER)
    configureMassPeakShaper();
  else if(index == (int)MassSpectrumSynthesisActions::SYNTHESIZE_MASS_SPECTRA)
    synthesizeMassSpectra();
  else if(index ==
          (int)MassSpectrumSynthesisActions::COPY_MASS_SPECTRUM_TO_CLIPBOARD)
    {
      if(!m_syntheticMassSpectrum.size())
        {
          mp_ui->massSpectrumSynthesisComboBox->setItemText(
            (int)MassSpectrumSynthesisActions::COPY_MASS_SPECTRUM_TO_CLIPBOARD,
            "Mass spectrum not yet available");

          return;
        }

      QClipboard *clipboard = QApplication::clipboard();
      clipboard->setText(m_syntheticMassSpectrum.toString());
    }
  else if(index ==
          (int)MassSpectrumSynthesisActions::SAVE_MASS_SPECTRUM_TO_FILE)
    {
      if(!m_syntheticMassSpectrum.size())
        {
          mp_ui->massSpectrumSynthesisComboBox->setItemText(
            (int)MassSpectrumSynthesisActions::SAVE_MASS_SPECTRUM_TO_FILE,
            "Mass spectrum not yet available");

          return;
        }

      QString file_path = QFileDialog::getSaveFileName(
        this,
        tr("Select file to export the mass spectrum  to"),
        QDir::homePath(),
        tr("Data files(*.xy *.XY)"));

      QFile file(file_path);

      if(!file.open(QIODevice::WriteOnly))
        {
          QMessageBox::information(0,
                                   tr("MassXpert3 - Export mass spectrum"),
                                   tr("Failed to open file in write mode."),
                                   QMessageBox::Ok);
          return;
        }

      QTextStream stream(&file);
      stream.setEncoding(QStringConverter::Utf8);

      prepareResultsTxtString();

      stream << m_syntheticMassSpectrum.toString();

      file.close();
    }
  else
    qFatal("Programming error. Should never reach this point.");
}

// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////

void
SequenceEditorWnd::prepareResultsTxtString()
{
  mpa_resultsString->clear();

  // First whole sequence
  bool entities =
    static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::MODIF);

  QString sequence =
    msp_polymer->getSequenceCstRef().getSequence(-1, -1, entities);

  *mpa_resultsString += QObject::tr(
                          "\n---------------------------\n"
                          "Sequence Data: %1\n"
                          "---------------------------\n"
                          "Name: %1\n"
                          "Code : %2\n"
                          "File path: %3\n"
                          "Sequence: %4\n")
                          .arg(msp_polymer->getName())
                          .arg(msp_polymer->getCode())
                          .arg(msp_polymer->getFilePath())
                          .arg(sequence);


  *mpa_resultsString += QObject::tr("\nIonization rule:\n");

  *mpa_resultsString +=
    QObject::tr("Formula: %1 - ")
      .arg(
        msp_polymer->getIonizerCstRef().getFormulaCstRef().getActionFormula());

  *mpa_resultsString +=
    QObject::tr("Nominal charge: %1 - ")
      .arg(msp_polymer->getIonizerCstRef().getNominalCharge());

  *mpa_resultsString +=
    QObject::tr("Level: %1\n").arg(msp_polymer->getIonizerCstRef().getLevel());

  *mpa_resultsString += QObject::tr("\nCalculation options:\n");

  if(entities)
    *mpa_resultsString += QObject::tr("Account monomer modifs: yes\n");
  else
    *mpa_resultsString += QObject::tr("Account monomer modifs: no\n");

  entities =
    static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER);

  if(entities)
    *mpa_resultsString += QObject::tr("Account cross-links: yes\n");
  else
    *mpa_resultsString += QObject::tr("Account cross-links: no\n");


  // Left end and right end modifs
  entities =
    static_cast<bool>(
      mp_calcOptions->getPolymerEntities() &
      libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF) ||
    static_cast<bool>(mp_calcOptions->getPolymerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF);

  if(!entities)
    {
      *mpa_resultsString += QObject::tr("Account ends' modifs: no\n");
    }
  else
    {
      *mpa_resultsString += QObject::tr("Account ends' modifs: yes - ");

      // Left end modif
      entities = static_cast<bool>(
        mp_calcOptions->getPolymerEntities() &
        libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF);
      if(entities)
        {
          *mpa_resultsString +=
            QObject::tr("Left end modif: %1 - ")
              .arg(msp_polymer->getLeftEndModifCstRef().getName());
        }

      // Right end modif
      entities = static_cast<bool>(
        mp_calcOptions->getPolymerEntities() &
        libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF);
      if(entities)
        {
          *mpa_resultsString +=
            QObject::tr("Right end modif: %1")
              .arg(msp_polymer->getRightEndModifCstRef().getName());
        }
    }

  // The options about multi-region selections and multi-selection
  // regions.
  if(mp_ui->multiRegionSelectionCheckBox->checkState())
    {
      *mpa_resultsString +=
        QObject::tr("\nMulti-region selection enabled: yes\n");

      if(mp_ui->regionSelectionOligomerRadioButton->isChecked())
        {
          *mpa_resultsString += QObject::tr(
            "Multi-region selections are "
            "treated as oligomers\n");
        }
      else if(mp_ui->regionSelectionResChainRadioButton->isChecked())
        {
          *mpa_resultsString += QObject::tr(
            "Multi-region selections are treated as "
            "residual chains\n");
        }

      if(mp_ui->multiSelectionRegionCheckBox->checkState())
        {
          *mpa_resultsString +=
            QObject::tr("Multi-selection region enabled: yes\n");
        }
      else
        {
          *mpa_resultsString +=
            QObject::tr("Multi-selection region enabled: no\n");
        }
    }
  else
    {
      *mpa_resultsString +=
        QObject::tr("\nMulti-region selection enabled: no\n");
    }

  // If there are cross-links, list all of these.

  if(msp_polymer->getCrossLinksCstRef().size())
    *mpa_resultsString += QObject::tr("\n\nCross-links:\n");

  for(const libXpertMassCore::CrossLinkSPtr &cross_link_sp :
      msp_polymer->getCrossLinksCstRef())
    *mpa_resultsString += cross_link_sp->prepareResultsTxtString();

  // Finally give the masses for the whole sequence:

  QString value;

  *mpa_resultsString += QObject::tr("\n\nWhole sequence mono mass: %1\n")
                          .arg(mp_ui->monoWholeMassLineEdit->text());

  *mpa_resultsString += QObject::tr("Whole sequence avg mass: %1\n\n")
                          .arg(mp_ui->avgWholeMassLineEdit->text());


  // And now the selected sequence region(s).

  entities = static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                               libXpertMassCore::Enums::ChemicalEntity::MODIF);

  sequence = msp_polymer->getSequenceCstRef().getSequence(
    mp_calcOptions->getIndexRangeCollectionCstRef(), entities, true);

  *mpa_resultsString += sequence;

  entities =
    static_cast<bool>(mp_calcOptions->getMonomerEntities() &
                      libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER);

  if(entities)
    {
      // We should inform that no, one, more cross-links might be left out:
      *mpa_resultsString += QObject::tr("%1\n\n").arg(
        mp_ui->incompleteCrossLinkWarningLabel->text());
    }

  *mpa_resultsString += QObject::tr("Selected sequence mono mass: %1\n")
                          .arg(mp_ui->monoSelectionMassLineEdit->text());

  *mpa_resultsString += QObject::tr("Selected sequence avg mass: %1\n")
                          .arg(mp_ui->avgSelectionMassLineEdit->text());
}

void
SequenceEditorWnd::exportClipboard()
{
  prepareResultsTxtString();

  QClipboard *clipboard = QApplication::clipboard();

  clipboard->setText(*mpa_resultsString, QClipboard::Clipboard);
}

void
SequenceEditorWnd::exportFile()
{
  if(m_resultsFilePath.isEmpty())
    {
      if(!exportSelectFile())
        return;
    }

  QFile file(m_resultsFilePath);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
    {
      QMessageBox::information(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Export data - failed to open file in append mode."),
        QMessageBox::Ok);
      return;
    }

  QTextStream stream(&file);
  stream.setEncoding(QStringConverter::Utf8);

  prepareResultsTxtString();

  stream << *mpa_resultsString;

  file.close();
}

bool
SequenceEditorWnd::exportSelectFile()
{
  m_resultsFilePath =
    QFileDialog::getSaveFileName(this,
                                 tr("Select File To Export Data To"),
                                 QDir::homePath(),
                                 tr("Data files(*.dat *.DAT)"));

  if(m_resultsFilePath.isEmpty())
    return false;

  return true;
}

// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////


bool
SequenceEditorWnd::maybeSave()
{
  // Returns true if we can continue(either saved ok or discard). If
  // save failed or cancel we return false to indicate to the caller
  // that something is wrong.

  if(isWindowModified())
    {
      QMessageBox::StandardButton ret;
      ret = QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("The document %1 has been modified.\n"
           "Do you want to save your changes?")
          .arg(msp_polymer->getFilePath()),
        QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);

      if(ret == QMessageBox::Save)
        {
          // We want to save the file. If the file has no existing
          // file associate the save as function will be called
          // automatically.
          return save();
        }
      else if(ret == QMessageBox::Discard)
        return true;
      else if(ret == QMessageBox::Cancel)
        return false;
    }

  return true;
}

void
SequenceEditorWnd::registerJsConstructor(QJSEngine *engine)

{
  if(!engine)
    {
      qWarning() << "Cannot register SequenceEditorWnd class: engine is null";
      return;
    }

  // Register the meta object as a constructor

  QJSValue jsMetaObject =
    engine->newQMetaObject(&SequenceEditorWnd::staticMetaObject);
  engine->globalObject().setProperty("SequenceEditorWnd", jsMetaObject);
}


} // namespace MassXpert
} // namespace MsXpS
