/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.


   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


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

/////////////////////// Local includes
#include "globals.hpp"
#include "application.hpp"
#include "sequenceEditorWnd.hpp"
#include "sequenceEditorGraphicsView.hpp"
#include "monomerModificationDlg.hpp"
#include "polymerModificationDlg.hpp"
#include "monomerCrossLinkDlg.hpp"
#include "cleavageDlg.hpp"
#include "fragmentationDlg.hpp"
#include "massSearchDlg.hpp"
#include "sequencePurificationDlg.hpp"
#include "sequenceImportDlg.hpp"
#include "mzCalculationDlg.hpp"
#include "compositionsDlg.hpp"
#include "pkaPhPi.hpp"
#include "pkaPhPiDataParser.hpp"
#include "pkaPhPiDlg.hpp"
#include "crossLink.hpp"
#include "crossLinkerSpec.hpp"
#include "decimalPlacesOptionsDlg.hpp"
#include "monomerDictionary.hpp"


namespace massXpert
{

  SequenceEditorWnd::SequenceEditorWnd()
  {
    mpa_polymer = 0;

    m_forciblyClose = false;
    m_noDelistWnd = false;
    m_postInitialized = false;

    if (!preInitialize())
      {
        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
      (static_cast<Application *>(qApp)->configSettingsFilePath(),
       QSettings::IniFormat);

    settings.beginGroup("sequence_editor_wnd");
    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
      (static_cast<Application *>(qApp)->configSettingsFilePath(),
       QSettings::IniFormat);

    settings.beginGroup("sequence_editor_wnd");

    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)
  {
    Application *application = static_cast<Application *>(qApp);

    // We are asked to close the window even if it has unsaved data.
    if (m_forciblyClose)
      {
        m_forciblyClose = false;

        // We have to delist the window.
        if(!m_noDelistWnd)
          {
            m_noDelistWnd = false;
            int index = application->sequenceEditorWndList()->indexOf(this);

            application->sequenceEditorWndList()->takeAt(index);
          }

        writeSettings();

        event->accept();

        return;
      }

    if (maybeSave())
      {
        // We are asked not to remove this window from the list of all
        // the polymer chemistry definition windows. This occurs when
        // all the windows are closed in a raw in the application
        // object.

        if(m_noDelistWnd)
          {
            m_noDelistWnd = false;

            writeSettings();

            event->accept();

            return;
          }

        // We must remove this window from the application's list of
        // such windows:

        int index = application->sequenceEditorWndList()->indexOf(this);

        application->sequenceEditorWndList()->takeAt(index);

        writeSettings();

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


  void
  SequenceEditorWnd::focusInEvent(QFocusEvent *event)
  {
    if (!event)
      printf("%s", "");

    Application *application = static_cast<Application *>(qApp);
    application->setLastFocusedWnd(this);
  }


  void
  SequenceEditorWnd::focusOutEvent(QFocusEvent *event)
  {
    if (!event)
      printf("%s", "");

    Application *application = static_cast<Application *>(qApp);
    application->setLastFocusedWnd(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() &
                    MXT_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() &
               MXT_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() &
               MXT_POLYMER_CHEMENT_LEFT_END_MODIF ||
               m_calcOptions.polymerEntities() &
               MXT_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() &
                   MXT_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() &
                   MXT_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() &
               MXT_MONOMER_CHEMENT_MODIF);

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

    *mpa_resultsString += *sequence;

    delete sequence;

    entities =(m_calcOptions.monomerEntities() &
               MXT_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::preInitialize()
  {
    m_ui.setupUi(this);

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

    QPixmap pixmap(":/images/massxpert-icon-32.png");
    QIcon icon(pixmap);
    setWindowIcon(icon);

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

    Application *application = static_cast<Application *>(qApp);

    mpa_polymer = new Polymer(mp_polChemDef, "NOT_SET", "NOT_SET",
                              application->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)
  {
    Application *application = static_cast<Application *>(qApp);


    // We get the filePath of the polymer chemistry definition file.

    PolChemDefSpec *polChemDefSpec =
      application->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",
                              application->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)
  {
    Application *application = static_cast<Application *>(qApp);

    // Is a polymer definition already available, or shall we have to
    // load one first ?
    PolChemDef *polChemDef = application->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 =
          application->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() + QDir::separator() +
          "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() + QDir::separator() +
          "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() + QDir::separator() +
          "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.
        application->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(application->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() << MXT_CAP_NONE << MXT_CAP_LEFT
    // << MXT_CAP_RIGHT << MXT_CAP_BOTH;

    m_ui.leftCapCheckBox->setChecked(m_calcOptions.capping() & MXT_CAP_LEFT);
    m_ui.rightCapCheckBox->setChecked(m_calcOptions.capping() & MXT_CAP_RIGHT);

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

    m_ui.leftModifCheckBox->setChecked(m_calcOptions.polymerEntities() &
                                       MXT_POLYMER_CHEMENT_LEFT_END_MODIF);
    m_ui.rightModifCheckBox->setChecked(m_calcOptions.polymerEntities() &
                                        MXT_POLYMER_CHEMENT_RIGHT_END_MODIF);
  }



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

    // CAPPING
    int value = 0;

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

    if (m_ui.rightCapCheckBox->checkState() == Qt::Checked)
      value |= MXT_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<Atom *> &refList = polChemDef->atomList();

        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 |= MXT_POLYMER_CHEMENT_LEFT_END_MODIF;
      }
    else
      {
        flags &= ~MXT_POLYMER_CHEMENT_LEFT_END_MODIF;

        if(m_ui.forceLeftModifCheckBox->checkState() == Qt::Checked)
          {
            m_ui.forceLeftModifCheckBox->toggle();
            flags &= ~MXT_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 |= MXT_POLYMER_CHEMENT_LEFT_END_MODIF;
          }

        flags |= MXT_POLYMER_CHEMENT_FORCE_LEFT_END_MODIF;
      }
    else
      {
        flags &= ~MXT_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 |= MXT_POLYMER_CHEMENT_RIGHT_END_MODIF;
      }
    else
      {
        flags &= ~MXT_POLYMER_CHEMENT_RIGHT_END_MODIF;

        if(m_ui.forceRightModifCheckBox->checkState() == Qt::Checked)
          {
            m_ui.forceRightModifCheckBox->toggle();
            flags &= ~MXT_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 |= MXT_POLYMER_CHEMENT_RIGHT_END_MODIF;
          }

        flags |= MXT_POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF;
      }
    else
      {
        flags &= ~MXT_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 |= MXT_MONOMER_CHEMENT_MODIF;
    else
      flags &= ~MXT_MONOMER_CHEMENT_MODIF;

    //   if (flags & MXT_MONOMER_CHEMENT_MODIF)
    //     qDebug() << __FILE__ << __LINE__
    // 	      << "monomerEntities set for MXT_MONOMER_CHEMENT_MODIF";
    //   else
    //     qDebug() << __FILE__ << __LINE__
    // 	      << "monomerEntities NOT set for MXT_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 |= MXT_MONOMER_CHEMENT_CROSS_LINK;
    else
      {
        flags &= ~MXT_MONOMER_CHEMENT_CROSS_LINK;

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

    //   if (flags & MXT_MONOMER_CHEMENT_CROSS_LINK)
    //     qDebug() << __FILE__ << __LINE__
    // 	      << "monomerEntities set for MXT_MONOMER_CHEMENT_CROSS_LINK";
    //   else
    //     qDebug() << __FILE__ << __LINE__
    // 	      << "monomerEntities NOT set for MXT_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 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);

    Application *application = static_cast<Application *>(qApp);

    m_ui.monoWholeMassLineEdit->
      setText(mpa_polymer->mono(application->locale(),
                                MXP_POLYMER_DEC_PLACES));
    m_ui.avgWholeMassLineEdit->
      setText(mpa_polymer->avg(application->locale(),
                               MXP_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);

    Application *application = static_cast<Application *>(qApp);

    m_ui.monoSelectionMassLineEdit->
      setText(mpa_polymer->mono(application->locale(),
                                MXP_OLIGOMER_DEC_PLACES));
    m_ui.avgSelectionMassLineEdit->
      setText(mpa_polymer->avg(application->locale(),
                               MXP_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;
    Application *application = static_cast<Application *>(qApp);

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

    if(mono)
      {
        mass = m_ui.monoWholeMassLineEdit->text();
        *mono = application->locale().toDouble(mass, &ok);
      }

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


  }



  void
  SequenceEditorWnd::selectedSequenceMasses(double *mono, double *avg)
  {
    QString mass;
    bool ok = false;
    Application *application = static_cast<Application *>(qApp);

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

    if(avg)
      {
        mass = m_ui.avgSelectionMassLineEdit->text();
        *avg = application->locale().toDouble(mass, &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, &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.

    Application *application = static_cast<Application *>(qApp);
    ConfigSettings *configurationSettings = application->configSettings();

    QString dictionaryFilePath(configurationSettings->systemDataDir() +
                               QDir::separator() + "dictionaries" +
                               QDir::separator() + "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, &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,
                                                   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, &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);

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

    dlg->show();
  }


  void
  SequenceEditorWnd::modifPolymer()
  {

    PolymerModificationDlg *dlg = new PolymerModificationDlg(this);

    dlg->show();
  }


  void
  SequenceEditorWnd::modifLeftEnd()
  {

    PolymerModificationDlg *dlg =
      new PolymerModificationDlg(this, MXT_END_LEFT);

    dlg->show();
  }


  void
  SequenceEditorWnd::modifRightEnd()
  {

    PolymerModificationDlg *dlg =
      new PolymerModificationDlg(this, MXT_END_RIGHT);

    dlg->show();
  }



  void
  SequenceEditorWnd::crossLinkMonomers()
  {
    MonomerCrossLinkDlg *dlg = new MonomerCrossLinkDlg(this);

    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,
                     SIGNAL(polymerSequenceModifiedSignal()),
                     dlg,
                     SLOT(polymerSequenceModifiedSlot()));
  }


  void
  SequenceEditorWnd::cleave()
  {
    CleavageDlg *dlg = new CleavageDlg(this,
                                       mpa_polymer,
                                       mpa_polymer->polChemDef(),
                                       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() & MXT_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(),
                           m_calcOptions,
                           &m_ionizeRule);

    dlg->show();
  }


  void
  SequenceEditorWnd::massSearch()
  {
    MassSearchDlg *dlg = new MassSearchDlg(this,
                                           mpa_polymer,
                                           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,
                           mpa_polymer->polChemDef(),
                           &m_ionizeRule, mono, avg);

    dlg->show();
  }


  void
  SequenceEditorWnd::compositions()
  {
    CompositionsDlg *dlg =
      new CompositionsDlg(this,
                          mpa_polymer,
                          &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() +
      QDir::separator() + QString("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,
                                     pkaPhPi,
                                     *mpa_polymer,
                                     m_calcOptions);

    dlg->show();
  }


  void
  SequenceEditorWnd::decimalPlacesOptions()
  {
    DecimalPlacesOptionsDlg *dlg = new DecimalPlacesOptionsDlg(this);

    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.

    CalculatorWnd *calculatorWnd = new CalculatorWnd(filePath, mono, avg);

    // At this time we have a calculator window that is fully
    // initialized and functional. We can add its pointer to the list
    // of such windows that is stored in the application object.
    Application *application = static_cast<Application *>(qApp);
    application->calculatorWndList()->append(calculatorWnd);
  }


  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 massXpert
