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


/////////////////////// Qt includes
#include <QtGui>
#include <QtXml>
#include <QtGlobal>
#include <QMessageBox>
#include <QFileDialog>

/////////////////////// Local includes
#include <massxpert/nongui/globals.hpp>
#include <massxpert/gui/SequenceEditorWnd.hpp>
#include <massxpert/gui/MainWindow.hpp>
#include <massxpert/gui/SequenceEditorGraphicsView.hpp>
#include <massxpert/gui/SequenceEditorFindDlg.hpp>
#include <massxpert/gui/MonomerModificationDlg.hpp>
#include <massxpert/gui/PolymerModificationDlg.hpp>
#include <massxpert/gui/MonomerCrossLinkDlg.hpp>
#include <massxpert/gui/CleavageDlg.hpp>
#include <massxpert/gui/FragmentationDlg.hpp>
#include <massxpert/gui/MassSearchDlg.hpp>
#include <massxpert/gui/SequencePurificationDlg.hpp>
#include <massxpert/gui/SequenceImportDlg.hpp>
#include <massxpert/gui/CalculatorWnd.hpp>
#include <massxpert/gui/MzCalculationDlg.hpp>
#include <massxpert/gui/CompositionsDlg.hpp>
#include <massxpert/nongui/PkaPhPi.hpp>
#include <massxpert/nongui/PkaPhPiDataParser.hpp>
#include <massxpert/gui/PkaPhPiDlg.hpp>
#include <massxpert/nongui/CrossLink.hpp>
#include <massxpert/nongui/CrossLinkerSpec.hpp>
#include <massxpert/gui/DecimalPlacesOptionsDlg.hpp>
#include <massxpert/nongui/MonomerDictionary.hpp>
#include <massxpert/nongui/ConfigSetting.hpp>


namespace msXpSmassXpert
{

SequenceEditorWnd::SequenceEditorWnd(MainWindow *parent)
  : AbstractMainTaskWindow{
      parent, "SequenceEditorWnd", "massXpert: sequence editor"}
{
  if(!initialize())
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Sequence Editor"),
                           tr("Failed to pre-initialize the polymer "
                              "sequence editor window."),
                           QMessageBox::Ok);
    }

  readSettings();

  show();
}


SequenceEditorWnd::~SequenceEditorWnd()
{
  delete mpa_resultsString;

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

  delete mpa_editorGraphicsScene;
  mpa_editorGraphicsScene = 0;

  delete mpa_editorGraphicsView;
  mpa_editorGraphicsView = 0;

  delete mpa_polymer;
}


void
SequenceEditorWnd::writeSettings()
{
  QSettings settings(mp_parentWnd->configSettingsFilePath(),
                     QSettings::IniFormat);

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

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

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

  settings.endGroup();
}


void
SequenceEditorWnd::readSettings()
{
  QSettings settings(mp_parentWnd->configSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);

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

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

  m_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(QFocusEvent *event)
{
  mp_parentWnd->setLastFocusedSeqEdWnd(this);
}


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


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

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

  // First whole sequence
  bool entities = (m_calcOptions.monomerEntities() & MONOMER_CHEMENT_MODIF);

  QString *sequence = mpa_polymer->monomerText(-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(mpa_polymer->name())
                          .arg(mpa_polymer->code())
                          .arg(mpa_polymer->filePath())
                          .arg(*sequence);

  delete sequence;

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

  *mpa_resultsString +=
    QObject::tr("Formula: %1 - ").arg(m_ionizeRule.formula());

  *mpa_resultsString += QObject::tr("Charge: %1 - ").arg(m_ionizeRule.charge());

  *mpa_resultsString += QObject::tr("Level: %1\n").arg(m_ionizeRule.level());

  *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 = (m_calcOptions.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK);

  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 =
    (m_calcOptions.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF ||
     m_calcOptions.polymerEntities() & POLYMER_CHEMENT_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 =
        (m_calcOptions.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF);
      if(entities)
        {
          *mpa_resultsString += QObject::tr("Left end modif: %1 - ")
                                  .arg(mpa_polymer->leftEndModif().name());
        }

      // Right end modif
      entities =
        (m_calcOptions.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF);
      if(entities)
        {
          *mpa_resultsString += QObject::tr("Right end modif: %1")
                                  .arg(mpa_polymer->leftEndModif().name());
        }
    }

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

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

      if(m_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(mpa_polymer->crossLinkList().size())
    {
      *mpa_resultsString += QObject::tr("\n\nCross-links:\n");

      for(int iter = 0; iter < mpa_polymer->crossLinkList().size(); ++iter)
        {
          CrossLink *crossLink = mpa_polymer->crossLinkList().at(iter);

          QString *text = crossLink->prepareResultsTxtString();

          Q_ASSERT(text);

          *mpa_resultsString += *text;

          delete text;
        }
    }

  // Finally give the masses for the whole sequence:

  QString value;

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

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


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

  entities = (m_calcOptions.monomerEntities() & MONOMER_CHEMENT_MODIF);

  sequence =
    mpa_polymer->monomerText(m_calcOptions.coordinateList(), entities, true);

  *mpa_resultsString += *sequence;

  delete sequence;

  entities = (m_calcOptions.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK);

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

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

  *mpa_resultsString += QObject::tr("Selected sequence avg mass: %1\n")
                          .arg(m_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,
                               tr("massXpert - Export Data"),
                               tr("Failed to open file in append mode."),
                               QMessageBox::Ok);
      return;
    }

  QTextStream stream(&file);
  stream.setCodec("UTF-8");

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


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/ImportPdb
  importPdbProtAct = new QAction(tr("&Import PDB"), this);
  importPdbProtAct->setShortcut(tr("Ctrl+I"));
  importPdbProtAct->setStatusTip(tr("Imports a PDB file"));
  connect(importPdbProtAct, SIGNAL(triggered()), this, SLOT(importPdbProt()));


  // File/ExportClipboard
  exportClipboardAct = new QAction(tr("Export to &clipboard"), this);
  exportClipboardAct->setShortcut(
    QKeySequence(Qt::CTRL + Qt::Key_E, 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::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::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::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::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(
    m_ui.leftEndModifPushButton, SIGNAL(clicked()), this, SLOT(modifLeftEnd()));

  connect(m_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::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::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::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::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();

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

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


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

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

  createActions();
  createMenus();

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

  setFocusPolicy(Qt::StrongFocus);

  m_ui.ionizationChargeSpinBox->setRange(1, 1000000000);
  m_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.
  m_ui.regionSelectionOligomerRadioButton->click();

  m_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(m_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...
  m_ui.monoWholeMassLineEdit->setDragEnabled(true);
  m_ui.avgWholeMassLineEdit->setDragEnabled(true);

  m_ui.regionSelectionOligomerRadioButton->setChecked(true);
  m_calcOptions.setSelectionType(SELECTION_TYPE_OLIGOMERS);

  m_ui.multiRegionSelectionCheckBox->setChecked(true);
  m_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);
  m_ui.thisWndLineEdit->setText(text);

  setWindowModified(false);

  return true;
}


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

  connect(mpa_polymer,
          SIGNAL(crossLinksPartiallyEncompassedSignal(int)),
          this,
          SLOT(crossLinksPartiallyEncompassedSlot(int)));

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

  MonomerCodeEvaluator *evaluator = new MonomerCodeEvaluator(
    mpa_polymer, this, m_ui.codeLineEdit, m_ui.codeErrorLineEdit);
  mpa_editorGraphicsView->setMonomerCodeEvaluator(evaluator);

  m_ui.sequenceNameLineEdit->setText(mpa_polymer->name());
  updateWindowTitle();

  updatePolymerEndsModifs();

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

  if(!populateMonomerCodeList())
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Sequence Editor"),
                           tr("%1@%2\n"
                              "Failed to populate the monomer code list.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      return false;
    }

  populateCalculationOptions();

  m_ui.vignetteListWidget->setSelectionMode(QAbstractItemView::MultiSelection);


  ////// Connection of the SIGNALS and SLOTS //////
  connect(m_ui.vignetteSizeSpinBox,
          SIGNAL(editingFinished()),
          this,
          SLOT(vignetteSizeChanged()));

  connect(m_ui.sequenceNameLineEdit,
          SIGNAL(textChanged(const QString &)),
          this,
          SLOT(nameLineEditChanged(const QString &)));

  connect(m_ui.leftCapCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(m_ui.rightCapCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(m_ui.leftModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(leftModifOptionsChanged()));

  connect(m_ui.forceLeftModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(forceLeftModifOptionsChanged()));

  connect(m_ui.rightModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(rightModifOptionsChanged()));

  connect(m_ui.forceRightModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(forceRightModifOptionsChanged()));

  connect(m_ui.multiRegionSelectionCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(multiRegionSelectionOptionChanged(int)));

  connect(m_ui.multiSelectionRegionCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(multiSelectionRegionOptionChanged(int)));

  connect(m_ui.regionSelectionOligomerRadioButton,
          SIGNAL(toggled(bool)),
          this,
          SLOT(regionSelectionOligomerOptionChanged(bool)));

  connect(m_ui.regionSelectionResChainRadioButton,
          SIGNAL(toggled(bool)),
          this,
          SLOT(regionSelectionResChainOptionChanged(bool)));

  connect(m_ui.monomerModifCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(monomerModifOptionChanged(int)));

  connect(m_ui.monomerCrossLinkCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(monomerCrossLinkOptionChanged(int)));

  connect(m_ui.ionizationFormulaLineEdit,
          SIGNAL(editingFinished()),
          this,
          SLOT(calculationOptionsChanged()));

  connect(m_ui.ionizationChargeSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(m_ui.ionizationLevelSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(calculationOptionsChanged()));

  connect(m_ui.selectionLineEdit,
          SIGNAL(textEdited(const QString &)),
          this,
          SLOT(coordinatesManuallyEdited(const QString &)));


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

  m_postInitialized = true;

  return true;
}


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

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

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

      return false;
    }

  QString name = Polymer::xmlPolymerFileGetPolChemDefName(filePath);

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

      return false;
    }

  mp_polChemDef = preparePolChemDef(name);

  if(!mp_polChemDef)
    {
      QMessageBox::warning(
        this,
        tr("massXpert - Polymer Sequence Editor"),
        tr("%1@%2\n"
           "Failed to prepare the polymer chemistry definition.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  const msXpSlibmass::UserSpec &userSpec = mp_parentWnd->userSpec();

  mpa_polymer =
    new Polymer(mp_polChemDef, "NOT_SET", "NOT_SET", userSpec.userName());

  if(!readFile(filePath))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Sequence Editor"),
                           tr("%1@%2\n"
                              "Failed to load the polymer file.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      return false;
    }

  if(!postInitialize())
    return false;

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

      return false;
    }

  focusInEvent(0);

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses();

  return true;
}


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

  PolChemDefSpec *polChemDefSpec =
    mp_parentWnd->polChemDefSpecFilePath(filePath);

  if(!polChemDefSpec)
    {
      QMessageBox::warning(
        this,
        tr("massXpert - Polymer Sequence Editor"),
        tr("%1@%2\n"
           "Failed to find the corresponding polymer chemistry "
           "filename.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  mp_polChemDef = preparePolChemDef(polChemDefSpec->name());

  if(!mp_polChemDef)
    {
      QMessageBox::warning(
        this,
        tr("massXpert - Polymer Sequence Editor"),
        tr("%1@%2\n"
           "Failed to prepare the polymer chemistry definition.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  mpa_polymer = new Polymer(
    mp_polChemDef, "NOT_SET", "NOT_SET", mp_parentWnd->userSpec().userName());

  if(!postInitialize())
    return false;

  if(mpa_editorGraphicsView->drawSequence() == -1)
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Sequence Editor"),
                           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 &filePath)
{
  return mpa_polymer->renderXmlPolymerFile(filePath);
}


PolChemDef *
SequenceEditorWnd::preparePolChemDef(const QString &name)
{
  // Is a polymer definition already available, or shall we have to
  // load one first ?
  PolChemDef *polChemDef = mp_parentWnd->polChemDefName(name);
  //    qDebug() << __FILE__ << __LINE__ << "polChemDef:" << polChemDef;

  if(!polChemDef)
    {
      // No polymer chemistry definition by that name is currently
      // loaded in memory. We'll have to load one.

      PolChemDefSpec *polChemDefSpec = mp_parentWnd->polChemDefSpecName(name);

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

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

          return 0;
        }

      // polChemDefSpec should provide data to create a new polymer
      // chemistry definition object.

      polChemDef = new PolChemDef(*polChemDefSpec);

      if(!polChemDef->renderXmlPolChemDefFile())
        {
          delete polChemDef;

          QMessageBox::warning(this,
                               tr("massXpert - Polymer Sequence Editor"),
                               tr("%1@%2\n"
                                  "Failed to render the polymer chemistry"
                                  " definition xml file.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                               QMessageBox::Ok);

          return 0;
        }

      // We still have to initialize the m_monomerSpecList and
      // m_modifSpecList lists...
      QString dictionary = polChemDef->dirPath() + "/monomer_dictionary";

      if(!MonomerSpec::parseFile(dictionary, polChemDef->monomerSpecList()))
        {
          delete polChemDef;

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

          return 0;
        }

      dictionary = polChemDef->dirPath() + "/modification_dictionary";

      if(!ModifSpec::parseFile(dictionary, polChemDef->modifSpecList()))
        {
          delete polChemDef;

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

          return 0;
        }

      dictionary = polChemDef->dirPath() + "/cross_linker_dictionary";

      if(!CrossLinkerSpec::parseFile(dictionary,
                                     polChemDef->crossLinkerSpecList()))
        {
          delete polChemDef;

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

          return 0;
        }

      // Finally we can add this PolChemDef to the application list
      // of such objects.
      mp_parentWnd->polChemDefList()->append(polChemDef);

      // Note also that we have to tell the definition where to
      // auto-remove itself as soon as the reference count comes down
      // to 0.
      polChemDef->setRepositoryList(mp_parentWnd->polChemDefList());

      // Finally increment the reference count of the polymer
      // chemistry definition.
      polChemDef->incrementRefCount();
    }
  else
    polChemDef->incrementRefCount();

  return polChemDef;
}


void
SequenceEditorWnd::populateCalculationOptions()
{
  const PolChemDef *polChemDef = mpa_polymer->polChemDef();

  if(!polChemDef)
    return;

  m_ionizeRule = polChemDef->ionizeRule();

  m_ui.ionizationFormulaLineEdit->setText(m_ionizeRule.formula());
  m_ui.ionizationChargeSpinBox->setValue(m_ionizeRule.charge());
  m_ui.ionizationLevelSpinBox->setValue(m_ionizeRule.level());

  // Update the masses|m/z ratios label.
  if(!m_ionizeRule.level())
    {
      m_ui.massesOrMzGroupBox->setTitle(tr("Masses"));
    }
  else
    {
      m_ui.massesOrMzGroupBox->setTitle(tr("m/z ratios"));
    }


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

  m_ui.leftCapCheckBox->setChecked(m_calcOptions.capping() & CAP_LEFT);
  m_ui.rightCapCheckBox->setChecked(m_calcOptions.capping() & CAP_RIGHT);

  m_ui.monomerModifCheckBox->setChecked(m_calcOptions.monomerEntities() &
                                        MONOMER_CHEMENT_MODIF);

  m_ui.leftModifCheckBox->setChecked(m_calcOptions.polymerEntities() &
                                     POLYMER_CHEMENT_LEFT_END_MODIF);
  m_ui.rightModifCheckBox->setChecked(m_calcOptions.polymerEntities() &
                                      POLYMER_CHEMENT_RIGHT_END_MODIF);
}


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

  // CAPPING
  int value = 0;

  if(m_ui.leftCapCheckBox->checkState() == Qt::Checked)
    value |= CAP_LEFT;

  if(m_ui.rightCapCheckBox->checkState() == Qt::Checked)
    value |= CAP_RIGHT;

  m_calcOptions.setCapping(value);

  //   qDebug() << "capping: " << m_calcOptions.capping();


  //   qDebug() << "polymer entities: " << m_calcOptions.polymerEntities();

  // IONIZATION RULE

  QString text = m_ui.ionizationFormulaLineEdit->text();

  if(!text.isEmpty())
    {
      const PolChemDef *polChemDef               = mpa_polymer->polChemDef();
      const QList<msXpSlibmass::Atom *> &refList = polChemDef->atomList();

      msXpSlibmass::Formula formula(text);

      if(!formula.validate(refList))
        {
          QMessageBox::warning(this,
                               tr("massXpert - Polymer Sequence Editor"),
                               tr("Ionization rule formula is not valid."),
                               QMessageBox::Ok);

          m_ui.ionizationFormulaLineEdit->setFocus();

          return;
        }

      m_ionizeRule.setFormula(text);
      //       qDebug() << "ionization formula: " << m_ionizeRule.formula();
    }

  m_ionizeRule.setCharge(m_ui.ionizationChargeSpinBox->value());
  //   qDebug() << "ionization charge: " << m_ionizeRule.charge();

  m_ionizeRule.setLevel(m_ui.ionizationLevelSpinBox->value());
  //   qDebug() << "ionization level: " << m_ionizeRule.level();

  // Update the masses|m/z ratios label.
  if(!m_ionizeRule.level())
    {
      m_ui.massesOrMzGroupBox->setTitle(tr("Masses"));
    }
  else
    {
      m_ui.massesOrMzGroupBox->setTitle(tr("m/z ratios"));
    }

  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 oldCoordText = m_ui.selectionLineEdit->text();
  QString newCoordText = text;

  CoordinateList oldCoordList;
  mpa_editorGraphicsView->selectionIndices(&oldCoordList);

  CoordinateList newCoordList;
  int res = newCoordList.setCoordinates(newCoordText);
  if(res == -1)
    {
      m_ui.selectionLineEdit->setText(oldCoordText);
      return -1;
    }

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

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

  if(newCoordList.size() > 1 && !isMultiRegion)
    {
      m_ui.selectionLineEdit->setText(oldCoordText);
      return -1;
    }

  if(newCoordList.overlap() && !isMultiSelection)
    {
      m_ui.selectionLineEdit->setText(oldCoordText);
      return -1;
    }

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

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

  return newCoordList.size();
}


void
SequenceEditorWnd::leftModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int flags = m_calcOptions.polymerEntities();

  if(m_ui.leftModifCheckBox->checkState() == Qt::Checked)
    {
      flags |= POLYMER_CHEMENT_LEFT_END_MODIF;
    }
  else
    {
      flags &= ~POLYMER_CHEMENT_LEFT_END_MODIF;

      if(m_ui.forceLeftModifCheckBox->checkState() == Qt::Checked)
        {
          m_ui.forceLeftModifCheckBox->toggle();
          flags &= ~POLYMER_CHEMENT_FORCE_LEFT_END_MODIF;
        }
    }

  m_calcOptions.setPolymerEntities(flags);

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}


void
SequenceEditorWnd::forceLeftModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int flags = m_calcOptions.polymerEntities();

  if(m_ui.forceLeftModifCheckBox->checkState() == Qt::Checked)
    {
      if(m_ui.leftModifCheckBox->checkState() != Qt::Checked)
        {
          m_ui.leftModifCheckBox->toggle();
          flags |= POLYMER_CHEMENT_LEFT_END_MODIF;
        }

      flags |= POLYMER_CHEMENT_FORCE_LEFT_END_MODIF;
    }
  else
    {
      flags &= ~POLYMER_CHEMENT_FORCE_LEFT_END_MODIF;
    }

  m_calcOptions.setPolymerEntities(flags);

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}


void
SequenceEditorWnd::rightModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int flags = m_calcOptions.polymerEntities();

  if(m_ui.rightModifCheckBox->checkState() == Qt::Checked)
    {
      flags |= POLYMER_CHEMENT_RIGHT_END_MODIF;
    }
  else
    {
      flags &= ~POLYMER_CHEMENT_RIGHT_END_MODIF;

      if(m_ui.forceRightModifCheckBox->checkState() == Qt::Checked)
        {
          m_ui.forceRightModifCheckBox->toggle();
          flags &= ~POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF;
        }
    }

  m_calcOptions.setPolymerEntities(flags);

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(false);
}


void
SequenceEditorWnd::forceRightModifOptionsChanged()
{
  // POLYMER MODIFICATION
  int flags = m_calcOptions.polymerEntities();

  if(m_ui.forceRightModifCheckBox->checkState() == Qt::Checked)
    {
      if(m_ui.rightModifCheckBox->checkState() != Qt::Checked)
        {
          m_ui.rightModifCheckBox->toggle();
          flags |= POLYMER_CHEMENT_RIGHT_END_MODIF;
        }

      flags |= POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF;
    }
  else
    {
      flags &= ~POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF;
    }

  m_calcOptions.setPolymerEntities(flags);

  updateWholeSequenceMasses();
  updateSelectedSequenceMasses(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.

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

      m_ui.multiSelectionRegionCheckBox->setEnabled(true);
    }

  updateSelectedSequenceMasses(false);
}


bool
SequenceEditorWnd::isMultiRegionSelection()
{
  return m_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 m_ui.multiSelectionRegionCheckBox->isChecked();
}


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

      m_calcOptions.setSelectionType(SELECTION_TYPE_OLIGOMERS);
    }
  else
    m_calcOptions.setSelectionType(SELECTION_TYPE_RESIDUAL_CHAINS);

  updateSelectedSequenceMasses(false);
}


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

      m_calcOptions.setSelectionType(SELECTION_TYPE_RESIDUAL_CHAINS);
    }
  else
    m_calcOptions.setSelectionType(SELECTION_TYPE_OLIGOMERS);

  updateSelectedSequenceMasses(false);
}


void
SequenceEditorWnd::monomerModifOptionChanged(int checkState)
{
  int flags = m_calcOptions.monomerEntities();

  if(checkState == Qt::Checked)
    flags |= MONOMER_CHEMENT_MODIF;
  else
    flags &= ~MONOMER_CHEMENT_MODIF;

  //   if (flags & MONOMER_CHEMENT_MODIF)
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "monomerEntities set for MONOMER_CHEMENT_MODIF";
  //   else
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "monomerEntities NOT set for MONOMER_CHEMENT_MODIF";

  m_calcOptions.setMonomerEntities(flags);

  updateWholeSequenceMasses(true);
  updateSelectedSequenceMasses(true);
}


void
SequenceEditorWnd::monomerCrossLinkOptionChanged(int checkState)
{
  int flags = m_calcOptions.monomerEntities();

  if(checkState == Qt::Checked)
    flags |= MONOMER_CHEMENT_CROSS_LINK;
  else
    {
      flags &= ~MONOMER_CHEMENT_CROSS_LINK;

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

  //   if (flags & MONOMER_CHEMENT_CROSS_LINK)
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "monomerEntities set for MONOMER_CHEMENT_CROSS_LINK";
  //   else
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "monomerEntities NOT set for MONOMER_CHEMENT_CROSS_LINK";

  m_calcOptions.setMonomerEntities(flags);


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

  m_ui.multiSelectionRegionCheckBox->setChecked(false);

  updateWholeSequenceMasses(true);
  updateSelectedSequenceMasses(true);
}


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

  const PolChemDef *polChemDef = mpa_polymer->polChemDef();

  if(!polChemDef)
    return true;

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

          delete item;
        }
    }

  for(int iter = 0; iter < polChemDef->monomerList().size(); ++iter)
    {
      Monomer *monomer = polChemDef->monomerList().at(iter);

      QString text = monomer->code() + "=" + monomer->name();
      m_ui.vignetteListWidget->addItem(text);
    }

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

  connect(m_ui.vignetteListWidget,
          SIGNAL(itemDoubleClicked(QListWidgetItem *)),
          this,
          SLOT(vignetteListWidgetItemDoubleClicked(QListWidgetItem *)));

  return true;
}


const PolChemDef *
SequenceEditorWnd::polChemDef() const
{
  return mpa_polymer->polChemDef();
}


PolChemDef *
SequenceEditorWnd::polChemDef()
{
  return mp_polChemDef;
}


Polymer *
SequenceEditorWnd::polymer()
{
  return mpa_polymer;
}


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


const CalcOptions &
SequenceEditorWnd::calcOptions() const
{
  return m_calcOptions;
}


const msXpSlibmass::IonizeRule &
SequenceEditorWnd::ionizeRule() const
{
  return m_ionizeRule;
}


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


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

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


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

  QWidget::setWindowModified(isModified);
}


void
SequenceEditorWnd::updateWindowTitle()
{
  if(mpa_polymer->filePath().isEmpty())
    setWindowTitle(tr("%1 %2[*]")
                     .arg(tr("massXpert - Polymer Sequence Editor:"))
                     .arg(tr("Untitled")));
  else
    {
      QFileInfo fileInfo(mpa_polymer->filePath());

      setWindowTitle(tr("%1 %2[*]")
                       .arg(tr("massXpert - Polymer Sequence Editor:"))
                       .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");

  m_ui.monomerPositionLineEdit->setText(str);
}


void
SequenceEditorWnd::updateWholeSequenceMasses(bool deep)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "updateWholeSequenceMasses";

  m_calcOptions.setCoordinateList(Coordinates(0, mpa_polymer->size()));
  m_calcOptions.setDeepCalculation(deep);

  mpa_polymer->deionize();
  mpa_polymer->calculateMasses(m_calcOptions);
  mpa_polymer->ionize(m_ionizeRule);

  m_ui.monoWholeMassLineEdit->setText(
    mpa_polymer->monoString(msXpS::POLYMER_DEC_PLACES));
  m_ui.avgWholeMassLineEdit->setText(
    mpa_polymer->avgString(msXpS::POLYMER_DEC_PLACES));
}


void
SequenceEditorWnd::updateSelectedSequenceMasses(bool deep)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "updateSelectedSequenceMasses";

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

  CoordinateList coordinateList;

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

  if(realSelections)
    {
      // We have increment all end indices for the items in the
      // coordinateList by one unit. See above for the explanation.
      // for(int iter = 0; iter < coordinateList.size(); ++iter)
      //  {
      // No, we do not need to do this anymore.
      // coordinateList.at(iter)->incrementEnd();
      //  }
    }

  m_calcOptions.setCoordinateList(coordinateList);

  m_calcOptions.setDeepCalculation(deep);

  mpa_polymer->deionize();
  mpa_polymer->calculateMasses(m_calcOptions);
  mpa_polymer->ionize(m_ionizeRule);

  m_ui.monoSelectionMassLineEdit->setText(
    mpa_polymer->monoString(msXpS::OLIGOMER_DEC_PLACES));
  m_ui.avgSelectionMassLineEdit->setText(
    mpa_polymer->avgString(msXpS::OLIGOMER_DEC_PLACES));

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

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

  m_ui.selectionLineEdit->setText(selectionPositions);
}


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  = m_ui.monoWholeMassLineEdit->text();
      *mono = mass.toDouble(&ok);
    }

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


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

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

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


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,
                                 tr("massXpert - Polymer Sequence Editor"),
                                 tr("The document %1 has been modified.\n"
                                    "Do you want to save your changes?")
                                   .arg(mpa_polymer->filePath()),
                                 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;
}


////////////////////////////// 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(mpa_polymer->filePath()))
    return saveAs();

  if(!mpa_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 Polymer Sequence File"),
                                 QDir::homePath(),
                                 tr("mXp files(*.mxp *.mXp *.MXP)"));

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

  mpa_polymer->setFilePath(filePath);

  if(!mpa_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 QMxtSequence out of it. If errors,
  // then show the purification dialog.

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

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

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

      sequence.appendMonomerText(line);
    }

  // And now make sure the sequence is correct.

  //     qDebug() << __FILE__ << __LINE__
  // 	     << "Done streaming file."

  QList<int> errorList;

  if(sequence.makeMonomerList(mpa_polymer->polChemDef(), true, &errorList) ==
     -1)
    {
      SequencePurificationDlg *dlg = new SequencePurificationDlg(
        this, mp_parentWnd->configSettingsFilePath(), &sequence, &errorList);

      dlg->show();

      return;
    }

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

  if(ret != size)
    {
      QMessageBox::critical(this,
                            tr("massXpert - Polymer Sequence Editor"),
                            tr("Failed to import the sequence."),
                            QMessageBox::Ok);
    }
}


void
SequenceEditorWnd::importPdbProt()
{
  // We want to import the protein sequence data out of a PDB file.

  // Format of a line:
  // SEQRES 1 A 21 GLY ILE VAL GLU GLN CYS CYS THR SER ILE CYS SER LEU

  // PDB File Format - Contents Guide Version 3.20 (Sept 15, 2008)
  // http://www.wwpdb.org/docs.html
  // File:
  // ftp://ftp.wwpdb.org/pub/pdb/doc/format_descriptions/Format_v32_A4.pdf
  //
  // The monomer codes are 3 uppercase letters-long.

  // First, get to know what the PDB file is:

  QString filePath =
    QFileDialog::getOpenFileName(this,
                                 tr("massXpert - Import PDB Protein Sequence"),
                                 QDir::homePath(),
                                 tr("PDB files (*.PDB *.Pdb *.pdb)"));

  QFile file(filePath);

  if(!file.exists(filePath))
    {
      QMessageBox::critical(this,
                            tr("massXpert - Import PDB Protein Sequence"),
                            tr("File not found."),
                            QMessageBox::Ok);
      return;
    }

  // Make sure we can read the sequence.

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

  QTextStream stream(&file);

  // Now, ensure that we can translate the ILE-formatted codes to
  // the codes we are currently using in our sequence.

  int codeLength = polChemDef()->codeLength();

  // Load the dictionary file "pdb-protein-to-mxp.dic" (from
  // dataDir/dictionaries) by creating a dictionary object.

  const ConfigSettings *configurationSettings = mp_parentWnd->configSettings();

  const ConfigSetting *configSetting =
    configurationSettings->value("dataDir", msXpS::UserType::USER_TYPE_SYSTEM);

  QString dictionaryFilePath(configSetting->m_value.toString() +
                             "/dictionaries" + "/pdb-protein-to-mxp.dic");

  MonomerDictionary dictionary;

  dictionary.setFilePath(dictionaryFilePath);
  dictionary.setInputCodeLength(3);
  dictionary.setOutputCodeLength(codeLength);

  if(!dictionary.loadDictionary())
    {
      QMessageBox::critical(this,
                            tr("massXpert - Import PDB Protein Sequence"),
                            tr("Failed to load the dictionary."),
                            QMessageBox::Ok);
      return;
    }

  // At this point we have loaded the dictionary, let's process the
  // file contents to extract the protein sequence data out of it.

  // Create a string list where to store all the chain sequences
  // parsed in the file and a parallel one where to store the
  // corresponding chain ID.
  QStringList *chainStringList   = new QStringList();
  QStringList *chainStringListId = new QStringList();

  // Create a string to hold the sequence that is going to be
  // extracted from the PDB file's lines.
  QString codes;

  // Create a sequence object we'll use to store the monomers we
  // have successfully parsed in.
  Sequence sequence;

  // Iterate in the file and for each line check that it matches
  // lines containing SEQRES records. If so, process the line to
  // extract the monomer codes in the style MET LEU THR GLN LYS THR

  // Define a regular expression that will sort out from the file
  // all the lines that hold a SEQRES record, and specifically one
  // such record that holds a protein sequence (and not a nucleic
  // acid sequence), which we know because codes for aminoacids are
  // 3-letters long in the form of GLY.  "MET LEU THR GLN LYS THR
  // LYS ASP ILE VAL LYS ALA THR $");

  QRegExp regExp(
    "^SEQRES "
    "[0-9 ][0-9 ][0-9 ] "
    "[A-Z ] "
    "[0-9 ][0-9 ][0-9 ][0-9 ]  "
    "([A-Z][A-Z][A-Z] ){1,13}[ ]*$");

  QRegExpValidator validator(regExp, 0);
  int pos = 0;

  QChar currentId;

  while(!stream.atEnd())
    {
      // PDF file format stipulates that:

      // Each line in the PDB entry file consists of 80 columns. The
      // last character in each PDB entry should be an end-of- line
      // indicator.

      QString line = stream.readLine(80);

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

      // Test now if the currently parsed line matches the regular
      // expression.

      if(validator.validate(line, pos) != QValidator::Acceptable)
        {
          // 	    qDebug() << __FILE__ << __LINE__
          // 		     << "line is faulty:" << line;

          // The line does not match a protein SEQRES record. Go to
          // next line in the file.
          continue;
        }

      // At this point the line is worth working on: it is a line
      // describing a portion of a protein sequence.

      // Note that the file format specification stipulates that
      // there might be any number of chain sequences. Their ID is
      // the 'B' at index 11 in the line "SEQRES 29 B 403 TYR TYR
      // ILE CYS GLY PRO ".

      QChar id = line.at(11);

      if(id != currentId)
        {
          // We are initiating a new chain sequence. Store the
          // previously chain sequence into the string list. The
          // syntax is that a first string with the chain ID is
          // added to the string list and then its sequence.

          if(!codes.isEmpty())
            {
              chainStringList->append(codes);
              chainStringListId->append(QString(currentId));

              // 		qDebug() << __FILE__ << __LINE__
              // 			 << "appending codes:"
              // 			 << codes;

              // Now clear the string so that we can start filling
              // it with a new chain sequence.
              codes.clear();
            }

          // Now update the current id.
          currentId = id;
        }

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


      // Create a temporary string with the line.
      QString tempSeq(line);

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

      // From this tempSeq string, only retain the monomer codes:
      // This is the string:
      //"SEQRES  29 B  403  TYR TYR ILE CYS GLY PRO "
      // follows: "ILE PRO PHE MET ARG MET GLN          "

      // This is what we want to retain:
      // "TYR TYR ILE CYS GLY PRO ILE PRO PHE MET ARG MET GLN"

      int firstResidueIndex =
        tempSeq.indexOf(QRegExp("([A-Z][A-Z][A-Z] ){1,13}[ ]*$"), 0);

      int lastResidueIndex = tempSeq.lastIndexOf(QRegExp("[A-Z][A-Z][A-Z]"));

      int stringLength = lastResidueIndex - firstResidueIndex;

      //  	qDebug() << __FILE__ << __LINE__
      //  		 << "firstResidueIndex:" << firstResidueIndex
      // 		 << "lastResidueIndex:" << lastResidueIndex;

      QString tempCodes =
        QString(" %1").arg(tempSeq.mid(firstResidueIndex, stringLength + 3));

      // This is what tempCodes contains now (not the leading
      // space): " TYR TYR ILE CYS GLY PRO ILE PRO PHE MET ARG MET
      // GLN"

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

      // Appends the codes from the currently parsed line into the
      // string to hold all the sequence in the file.

      codes.append(tempCodes);
    }

  // At this point all the PDB file has been read through. We still
  // have one ongoing codes string, if not empty which we ought to
  // add to the chainStringList.

  if(!codes.isEmpty())
    {
      chainStringList->append(codes);
      chainStringListId->append(QString(currentId));

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << "appending codes:"
      // 		 << codes;

      // Clear the codes string to free memory.
      codes.clear();
    }

  // Now feed the chain string list to the translator. The returned
  // string list is allocated. We will transfer its ownership to the
  // dialog, if we use that dialog below.
  QStringList *translatedChainStringList =
    dictionary.translate(*chainStringList);

  if(!translatedChainStringList)
    {
      QMessageBox::critical(this,
                            tr("massXpert - Import PDB Protein Sequence"),
                            tr("Failed to import the sequence(s)."),
                            QMessageBox::Ok);

      delete chainStringList;
      delete chainStringListId;

      return;
    }

  //     for (int iter = 0 ; iter < translatedChainStringList->size(); ++iter)
  //       {
  // 	qDebug() << __FILE__ << __LINE__
  // 		 << chainStringListId->at(iter)
  // 		 << translatedChainStringList->at(iter);
  //       }

  // At this point, we will not need the initial list anymore, we
  // can free it and set it to 0.
  delete chainStringList;
  chainStringList = 0;

  // If there is only one sequence that's been imported (the file
  // format allows for any number of chain sequences), then we have
  // nothing more than to make an actual sequence with it.

  if(translatedChainStringList->size() == 1)
    {
      Sequence sequence(translatedChainStringList->first());

      // Immediately free the string lists and set them to 0 as we
      // are not going to use them anymore.

      delete chainStringListId;
      chainStringListId = 0;
      delete translatedChainStringList;
      translatedChainStringList = 0;

      // Now test if the sequence is correct.

      QList<int> errorList;

      if(sequence.makeMonomerList(
           mpa_polymer->polChemDef(), true, &errorList) == -1)
        {
          SequencePurificationDlg *dlg =
            new SequencePurificationDlg(this,
                                        mp_parentWnd->configSettingsFilePath(),
                                        &sequence,
                                        &errorList);

          dlg->show();

          return;
        }

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

      if(ret != size)
        {
          QMessageBox::critical(this,
                                tr("massXpert - Polymer Sequence Editor"),
                                tr("Failed to import the sequence."),
                                QMessageBox::Ok);
        }

      return;
    }

  // If we are here, then that means that there were more than one
  // sequence imported from the PDB file. We have to display these
  // sequences to the user.

  // First, make sure that we have the same number of items in the
  // chain sequence string list and in the chain id string list.

  if(chainStringListId->size() != translatedChainStringList->size())
    {
      QMessageBox::critical(this,
                            tr("massXpert - Polymer Sequence Editor"),
                            tr("Failed to import the sequence:\n"
                               "Number of chain sequences differs "
                               "from number of chain IDs."),
                            QMessageBox::Ok);
    }

  // We should open the window that is specialized for displaying
  // imported sequences to the user.

  // Note that the translatedChainStringList ownership is transferred to
  // the dialog. We will not delete it.

  SequenceImportDlg *dlg =
    new SequenceImportDlg(this,
                          mp_parentWnd->configSettingsFilePath(),
                          translatedChainStringList,
                          chainStringListId);

  dlg->show();

  return;
}


void
SequenceEditorWnd::clipboardCopy(QClipboard::Mode mode)
{
  CoordinateList coordList;

  if(mpa_editorGraphicsView->selectionIndices(&coordList))
    {

      // We do not do this below, because it turned out to be
      // impractical when working with primary selections.

      int selectionCount = coordList.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(selectionCount > 1)
      //   {
      //     QMessageBox::warning(this,
      //                          tr("massXpert - Polymer Sequence Editor"),
      //                          tr("The sequence exported to the clipboard "
      //                             "will comprise %1 region selections.")
      //                          .arg(selectionCount),
      //                          QMessageBox::Ok);
      //   }

      QString *text = new QString;

      for(int iter = 0; iter < selectionCount; ++iter)
        {
          Coordinates *coordinates = coordList.at(iter);

          QString *sequence = mpa_polymer->monomerText(
            coordinates->start(), coordinates->end(), false);

          *text += *sequence;

          delete sequence;
        }

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

      delete text;
    }
}


void
SequenceEditorWnd::clipboardCut(QClipboard::Mode mode)
{
  CoordinateList coordList;

  if(mpa_editorGraphicsView->selectionIndices(&coordList))
    {
      // 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 selectionCount = coordList.size();

      if(selectionCount > 1)
        {
          QMessageBox::information(this,
                                   tr("massXpert - Polymer Sequence Editor"),
                                   tr("Cut operations are not supported "
                                      "in multi-region selection mode."),
                                   QMessageBox::Ok);

          return;
        }

      Coordinates *coordinates = coordList.last();

      QString *text = mpa_polymer->monomerText(
        coordinates->start(), coordinates->end(), false);
      Q_ASSERT(text);

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

      delete text;

      mpa_editorGraphicsView->removeSelectedOligomer();
    }
}


void
SequenceEditorWnd::clipboardPaste(QClipboard::Mode mode)
{
  CoordinateList coordList;

  if(mpa_editorGraphicsView->selectionIndices(&coordList))
    {
      // 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 selectionCount = coordList.size();

      if(selectionCount > 1)
        {
          QMessageBox::information(this,
                                   tr("massXpert - Polymer Sequence Editor"),
                                   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;

  Sequence sequence(text);

  QList<int> errorList;

  if(sequence.makeMonomerList(mpa_polymer->polChemDef(), true, &errorList) ==
     -1)
    {
      SequencePurificationDlg *dlg = new SequencePurificationDlg(
        this, mp_parentWnd->configSettingsFilePath(), &sequence, &errorList);

      dlg->show();

      return;
    }

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

  if(ret != size)
    {
      QMessageBox::critical(this,
                            tr("massXpert - Polymer Sequence Editor"),
                            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, mpa_polymer, mp_parentWnd->configSettingsFilePath());

  dlg->show();

  return;
}


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

  if(!m_postInitialized)
    return;

  mpa_editorGraphicsView->requestVignetteSize(size);
}


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


void
SequenceEditorWnd::modifMonomer()
{
  MonomerModificationDlg *dlg =
    new MonomerModificationDlg(this, mp_parentWnd->configSettingsFilePath());

  dlg->show();
}


void
SequenceEditorWnd::modifPolymer()
{

  PolymerModificationDlg *dlg =
    new PolymerModificationDlg(this, mp_parentWnd->configSettingsFilePath());

  dlg->show();
}


void
SequenceEditorWnd::modifLeftEnd()
{
  PolymerModificationDlg *dlg = new PolymerModificationDlg(
    this, mp_parentWnd->configSettingsFilePath(), END_LEFT);

  dlg->show();
}


void
SequenceEditorWnd::modifRightEnd()
{
  PolymerModificationDlg *dlg = new PolymerModificationDlg(
    this, mp_parentWnd->configSettingsFilePath(), END_RIGHT);

  dlg->show();
}


void
SequenceEditorWnd::crossLinkMonomers()
{
  MonomerCrossLinkDlg *dlg =
    new MonomerCrossLinkDlg(this, mp_parentWnd->configSettingsFilePath());

  dlg->show();

  // 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(mpa_polymer,
                   SIGNAL(crossLinkChangedSignal(Polymer *)),
                   dlg,
                   SLOT(crossLinkChangedSlot(Polymer *)));

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


void
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 =
    mpa_polymer->md5Sum(HASH_ACCOUNT_SEQUENCE | HASH_ACCOUNT_MONOMER_MODIF);

  CleavageDlg *dlg = new CleavageDlg(this,
                                     mpa_polymer,
                                     mpa_polymer->polChemDef(),
                                     mp_parentWnd->configSettingsFilePath(),
                                     hash,
                                     m_calcOptions,
                                     &m_ionizeRule);

  dlg->show();
}


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

  CoordinateList coordList;

  bool res = mpa_editorGraphicsView->selectionIndices(&coordList);

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

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

  if(m_calcOptions.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
    {
      // 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 startIndex = coordList[0]->start();
      int endIndex   = coordList[0]->end();

      // Get the cross-links for the region.
      QList<CrossLink *> crossLinkList;

      int partials = 0;

      mpa_polymer->crossLinkList(
        startIndex, endIndex, &crossLinkList, &partials);

      if(partials)
        {
          QMessageBox::warning(this,
                               tr("massxpert"),
                               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.

  //    m_calcOptions.setCoordinateList(coordList);

  FragmentationDlg *dlg =
    new FragmentationDlg(this,
                         mpa_polymer,
                         mpa_polymer->polChemDef(),
                         mp_parentWnd->configSettingsFilePath(),
                         m_calcOptions,
                         &m_ionizeRule);

  dlg->show();
}


void
SequenceEditorWnd::massSearch()
{
  MassSearchDlg *dlg = new MassSearchDlg(this,
                                         mpa_polymer,
                                         mp_parentWnd->configSettingsFilePath(),
                                         m_calcOptions,
                                         &m_ionizeRule);

  dlg->show();
}


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

  CoordinateList coordList;
  double mono = 0;
  double avg  = 0;

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

      selectedSequenceMasses(&mono, &avg);
    }
  else
    {
      wholeSequenceMasses(&mono, &avg);
      qDebug() << __FILE__ << __LINE__;
    }

  qDebug() << __FILE__ << __LINE__ << "mono:" << mono << "avg:" << avg;


  MzCalculationDlg *dlg =
    new MzCalculationDlg(this,
                         mp_parentWnd->configSettingsFilePath(),
                         mpa_polymer->polChemDef(),
                         &m_ionizeRule,
                         mono,
                         avg);

  dlg->show();
}


void
SequenceEditorWnd::compositions()
{
  CompositionsDlg *dlg =
    new CompositionsDlg(this,
                        mpa_polymer,
                        mp_parentWnd->configSettingsFilePath(),
                        &m_calcOptions,
                        &m_ionizeRule);

  dlg->show();
}


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

  // Where is the data file?
  QString filePath = mpa_polymer->polChemDef()->dirPath() + "/pka_ph_pi.xml";

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

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

  QList<Monomer *> *monomerList = new(QList<Monomer *>);
  QList<Modif *> *modifList     = new(QList<Modif *>);

  // Create the parser using the above filePath.
  PkaPhPiDataParser parser(mpa_polymer->polChemDef(), filePath);

  // Ask that the rendering be performed.
  if(!parser.renderXmlFile(monomerList, modifList))
    {
      delete monomerList;
      delete modifList;

      QMessageBox::warning(this,
                           tr("massXpert - pKa pH pI"),
                           tr("%1@%2\n"
                              "Failed to render xml file(%3).")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(filePath),
                           QMessageBox::Ok);
      return;
    }

  // Allocate a new PkaPhPi instance that we'll pass to the
  // dialog. Ownership of that object is taken by the dialog, which
  // will free it.
  PkaPhPi *pkaPhPi =
    new PkaPhPi(*mpa_polymer, m_calcOptions, monomerList, modifList);

  PkaPhPiDlg *dlg = new PkaPhPiDlg(this,
                                   mpa_polymer,
                                   mp_parentWnd->configSettingsFilePath(),
                                   pkaPhPi,
                                   m_calcOptions);

  dlg->show();
}


void
SequenceEditorWnd::decimalPlacesOptions()
{
  DecimalPlacesOptionsDlg *dlg =
    new DecimalPlacesOptionsDlg(this, mp_parentWnd->configSettingsFilePath());

  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 = m_ui.monoWholeMassLineEdit->text();
  QString avg  = m_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 = m_ui.monoSelectionMassLineEdit->text();
  QString avg  = m_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 = mp_polChemDef->filePath();

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

  new CalculatorWnd(mp_parentWnd, filePath, mono, avg);
}


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


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


void
SequenceEditorWnd::updatePolymerEndsModifs()
{

  QString text;

  text = mpa_polymer->leftEndModif().name();

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

  text = mpa_polymer->rightEndModif().name();

  if(!text.isEmpty())
    {
      m_ui.rightEndModifPushButton->setText(text);
    }
  else
    {
      m_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.

  Sequence sequence(code);
  QList<int> errorList;

  sequence.makeMonomerList(mp_polChemDef, false, &errorList);

  mpa_editorGraphicsView->insertSequenceAtPoint(sequence);
}

} // namespace msXpSmassXpert
