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


/////////////////////// Qt includes
#include <QMessageBox>
#include <QFileDialog>
#include <QApplication>
#include <QSettings>

/////////////////////// Local includes
#include "MassSearchDlg.hpp"


namespace MsXpS
{

namespace MassXpert
{


MassSearchDlg::MassSearchDlg(SequenceEditorWnd *editor_wnd_p,
                             libXpertMassCore::PolymerQSPtr polymer_sp,
                             /* no libXpertMassCore::PolChemDef **/
                             const QString &config_settings_file_path,
                             const QString &application_name,
                             const QString &description,
                             const libXpertMassCore::CalcOptions &calc_options,
                             const libXpertMassCore::Ionizer &ionizer_p)
  : AbstractSeqEdWndDependentDlg(editor_wnd_p,
                                 polymer_sp,
                                 nullptr, /*polChemDef*/
                                 config_settings_file_path,
                                 "MassSearchDlg",
                                 application_name,
                                 description),
    m_calcOptions{calc_options},
    m_searchIndexRange(m_calcOptions.getIndexRangeCollectionCstRef()
                         .mostInclusiveLeftRightIndexRange()),
    m_ionizer(ionizer_p)
{
  if(polymer_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(!initialize())
    qFatal() << "Programming error. Failed to initialize dialog window.";

  qDebug() << "Calculation options:" << m_calcOptions.toString();
}


MassSearchDlg::~MassSearchDlg()
{
  delete mpa_resultsString;

  delete mpa_monoOligomerTableViewModel;

  delete mpa_monoProxyModel;

  delete mpa_avgOligomerTableViewModel;

  delete mpa_avgProxyModel;

  clearOligomerCollections();
}


void
MassSearchDlg::readSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.oligomersSplitter->restoreState(
    settings.value("oligomersSplitter").toByteArray());
  m_ui.oligoDetailsSplitter->restoreState(
    settings.value("oligoDetailsSplitter").toByteArray());
  m_ui.tableViewsSplitter->restoreState(
    settings.value("tableViewsSplitter").toByteArray());
  settings.endGroup();
}


void
MassSearchDlg::writeSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);
  settings.beginGroup(m_wndTypeName);
  settings.setValue("geometry", saveGeometry());
  settings.setValue("oligomersSplitter", m_ui.oligomersSplitter->saveState());
  settings.setValue("oligoDetailsSplitter",
                    m_ui.oligoDetailsSplitter->saveState());
  settings.setValue("tableViewsSplitter", m_ui.tableViewsSplitter->saveState());
  settings.endGroup();
}


bool
MassSearchDlg::initialize()
{
  m_ui.setupUi(this);

  setWindowIcon(qApp->windowIcon());

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

  m_ui.delimiterLineEdit->setText("$");

  // Set the current whole/selected sequence data in the
  // m_calcOptions instance and in the corresponding widgets (true =
  // read from sequence editor window).
  updateSelectionData(true);

  m_monoTolerance = 0;
  m_avgTolerance  = 0;

  m_ionizeStart = 0;
  m_ionizeStop  = 0;

  m_currentMass      = 0;
  m_foundOligosCount = 0;

  m_abort            = false;
  m_sequenceEmbedded = false;

  populateToleranceTypeComboBoxes();

  setupTableViews();

  m_ui.ionizationStartSpinBox->setRange(-1000000000, 1000000000);
  m_ui.ionizationStartSpinBox->setValue(1);

  m_ui.ionizationEndSpinBox->setRange(-1000000000, 1000000000);
  m_ui.ionizationEndSpinBox->setValue(1);

  m_ui.massSearchProgressBar->setRange(0, 100);
  m_ui.massSearchProgressBar->setValue(0);

  m_ui.avgToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));
  m_ui.monoToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));

  m_ui.monoMassLineEdit->setText("1");
  m_ui.avgMassLineEdit->setText("1");


  // The tolerance when filtering mono/avg masses...
  QStringList stringList;

  stringList << tr("AMU") << tr("PCT") << tr("PPM");

  m_ui.monoFilterToleranceComboBox->insertItems(0, stringList);

  m_ui.monoFilterToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));

  m_ui.avgFilterToleranceComboBox->insertItems(0, stringList);

  m_ui.avgFilterToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));

  m_ui.monoFilterToleranceLineEdit->setText("1");
  m_ui.avgFilterToleranceLineEdit->setText("1");

  monoFilterAct = new QAction(tr("Toggle Mono Filtering"), this);
  monoFilterAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_F));
  this->addAction(monoFilterAct);
  connect(
    monoFilterAct, SIGNAL(triggered()), this, SLOT(monoFilterOptionsToggled()));

  m_ui.monoFilteringOptionsGroupBox->addAction(monoFilterAct);

  avgFilterAct = new QAction(tr("Toggle Avg Filtering"), this);
  avgFilterAct->setShortcut(
    QKeySequence(Qt::CTRL | Qt::Key_A, Qt::CTRL | Qt::Key_F));
  this->addAction(avgFilterAct);
  connect(
    avgFilterAct, SIGNAL(triggered()), this, SLOT(avgFilterOptionsToggled()));

  m_ui.avgFilteringOptionsGroupBox->addAction(avgFilterAct);

  // When the dialog box is created it is created with the groupbox
  // unchecked.
  m_ui.monoFilteringOptionsFrame->setVisible(false);
  m_ui.avgFilteringOptionsFrame->setVisible(false);

  // When the filtering group box will be opened, the focus will be on
  // the first widget of the groupbox:
  mp_monoFocusWidget = m_ui.monoFilterSearchedLineEdit;
  mp_avgFocusWidget  = m_ui.avgFilterSearchedLineEdit;

  // The results-exporting menus. ////////////////////////////////

  QStringList comboBoxItemList;

  comboBoxItemList << tr("To Clipboard") << tr("To File") << tr("Select File");

  m_ui.exportResultsComboBox->addItems(comboBoxItemList);

  connect(m_ui.exportResultsComboBox,
          SIGNAL(activated(int)),
          this,
          SLOT(exportResults(int)));

  mpa_resultsString = new QString();

  //////////////////////////////////// The results-exporting menus.

  readSettings();

  connect(m_ui.searchPushButton, SIGNAL(clicked()), this, SLOT(search()));

  connect(m_ui.abortPushButton, SIGNAL(clicked()), this, SLOT(abort()));

  connect(m_ui.updateCurrentSelectionDataPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(updateWholeSelectedSequenceData()));


  // MONO set of filtering widgets
  connect(m_ui.monoFilterSearchedLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterSearched()));

  connect(m_ui.monoFilterErrorLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterError()));

  connect(m_ui.monoFilterMonoMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterMonoMass()));

  connect(m_ui.monoFilterAvgMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterAvgMass()));

  connect(m_ui.monoFilteringOptionsGroupBox,
          SIGNAL(clicked(bool)),
          this,
          SLOT(monoFilterOptions(bool)));

  // AVG set of filtering widgets
  connect(m_ui.avgFilterSearchedLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterSearched()));

  connect(m_ui.avgFilterErrorLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterError()));

  connect(m_ui.avgFilterMonoMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterMonoMass()));

  connect(m_ui.avgFilterAvgMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterAvgMass()));

  connect(m_ui.avgFilteringOptionsGroupBox,
          SIGNAL(clicked(bool)),
          this,
          SLOT(avgFilterOptions(bool)));

  return true;
}


bool
MassSearchDlg::populateToleranceTypeComboBoxes()
{
  QStringList stringList;

  stringList << tr("AMU") << tr("PCT") << tr("PPM");

  m_ui.monoToleranceComboBox->insertItems(0, stringList);
  m_ui.avgToleranceComboBox->insertItems(0, stringList);

  return true;
}

void
MassSearchDlg::updateWholeSelectedSequenceData()
{
  updateSelectionData(true);
}


bool
MassSearchDlg::updateSelectionData(bool fromSequenceEditor)
{
  // There are two situations:

  // 1. This function is called from the constructor to setup data
  // in the whole/selected sequence groupBox widget. It gets the
  // info directly from the sequence editor window
  // (fromSequenceEditor = true). By default the param is true,
  // because this is expected for when this slot is called upon
  // clicking on the Update button.

  // 2. This function is called only to set the whole/selected
  // sequence data in the calculation options instance. The data are
  // read directly from the widgets and the sequence editor window
  // is not probed.

  bool actual_selection = false;

  if(!fromSequenceEditor)
    {
      // Only update that whole/selected sequence data in the
      // m_searchIndexRange from the widgetry and not from the
      // sequence. Because the user cannot modify the selection text
      // in the line edit widget, all we need to know is: whole or
      // selected sequence.

      if(m_ui.wholeSequenceRadioButton->isChecked())
        {
          // Set the "selection" to be the whole sequence.

          m_searchIndexRange.m_start = 0;
          m_searchIndexRange.m_stop  = msp_polymer->size() - 1;

          m_ui.wholeSequenceRadioButton->setChecked(true);

          return true;
        }
      // Else, we do not need to do anything. The selection is
      // updated by clicking onto the Update button.

      return true;
    }
  else
    {
      // We are asked to update the whole/selected
      // sequence data directly from the sequence editor window. So get
      // all these coordinates !

      libXpertMassCore::IndexRangeCollection index_range_collection;

      actual_selection = mp_editorWnd->mpa_editorGraphicsView->selectionIndices(
        index_range_collection);

      if(actual_selection)
        {
          // Whatever the contents of the index range collection, we
          // resolve that into a single most inclusive range.
          libXpertMassCore::IndexRange *index_range_p =
            index_range_collection.mostInclusiveLeftRightIndexRange();
          m_searchIndexRange.initialize(*index_range_p);
          delete index_range_p;

          m_ui.currentSelectionLineEdit->setText(
            m_searchIndexRange.indicesAsText());

          m_ui.currentSelectionRadioButton->setChecked(true);
        }
      else
        {
          // There is no real selection, set the "selection" to be the
          // whole sequence.

          m_searchIndexRange.m_start = 0;
          m_searchIndexRange.m_stop  = msp_polymer->size() - 1;

          m_ui.wholeSequenceRadioButton->setChecked(true);
        }
    }

  // Return if there was a selection(multiple-region or not) or
  // not.
  return actual_selection;
}


bool
MassSearchDlg::checkTolerance(libXpertMassCore::Enums::MassType mass_type)
{
  double nominal = 0;
  bool ok        = false;

  QString text;

  if(static_cast<int>(mass_type) &
     static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
    {
      text = m_ui.monoMassLineEdit->text();
    }
  else if(static_cast<int>(mass_type) &
          static_cast<int>(libXpertMassCore::Enums::MassType::AVG))
    {
      text = m_ui.avgMassLineEdit->text();
    }
  else
    Q_ASSERT(0);

  if(text.isEmpty())
    return false;

  nominal = text.toDouble(&ok);

  if(!nominal && !ok)
    return false;

  return true;
}

bool
MassSearchDlg::calculateTolerance(double value,
                                  libXpertMassCore::Enums::MassType mass_type)
{
  int tolerance_type_combo_index = 0;
  QString text;

  if(static_cast<int>(mass_type) &
     static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
    {
      tolerance_type_combo_index = m_ui.monoToleranceComboBox->currentIndex();
      text                       = m_ui.monoMassLineEdit->text();
    }
  else if(static_cast<int>(mass_type) &
          static_cast<int>(libXpertMassCore::Enums::MassType::AVG))
    {
      tolerance_type_combo_index = m_ui.avgToleranceComboBox->currentIndex();
      text                       = m_ui.avgMassLineEdit->text();
    }
  else
    Q_ASSERT(0);


  bool ok                        = false;
  double nominal_tolerance_value = 0;
  nominal_tolerance_value        = text.toDouble(&ok);

  if(!nominal_tolerance_value && !ok)
    return false;

  if(tolerance_type_combo_index == 0)
    {
      // MASS_TOLERANCE_AMU

      if(static_cast<int>(mass_type) &
         static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
        m_monoTolerance = nominal_tolerance_value;
      else
        m_avgTolerance = nominal_tolerance_value;

      return true;
    }
  else if(tolerance_type_combo_index == 1)
    {
      // MASS_TOLERANCE_PCT

      if(static_cast<int>(mass_type) &
         static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
        m_monoTolerance = nominal_tolerance_value * (value / 100);
      else
        m_avgTolerance = nominal_tolerance_value * (value / 100);

      return true;
    }
  else if(tolerance_type_combo_index == 2)
    {
      // MASS_TOLERANCE_PPM

      if(static_cast<int>(mass_type) &
         static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
        m_monoTolerance = nominal_tolerance_value * (value / 1000000);
      else
        m_avgTolerance = nominal_tolerance_value * (value / 1000000);

      return true;
    }
  else
    Q_ASSERT(0);

  return false;
}


bool
MassSearchDlg::parseMassText(libXpertMassCore::Enums::MassType mass_type)
{
  if(mass_type == libXpertMassCore::Enums::MassType::BOTH)
    qFatal() << "Programming error.";

  QString text;

  if(static_cast<int>(mass_type) &
     static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
    text = m_ui.monoMassTextEdit->toPlainText();
  else if(static_cast<int>(mass_type) &
          static_cast<int>(libXpertMassCore::Enums::MassType::AVG))
    text = m_ui.avgMassTextEdit->toPlainText();
  else
    Q_ASSERT(0);

  QStringList splitList =
    text.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);

  for(int iter = 0; iter < splitList.size(); ++iter)
    {
      bool ok = false;

      double value = splitList.at(iter).toDouble(&ok);

      if(!value && !ok)
        {
          qDebug() << "Error in mass list";

          return false;
        }

      if(static_cast<int>(mass_type) &
         static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
        m_monoMassesList.append(value);
      else
        m_avgMassesList.append(value);
    }

  return true;
}


bool
MassSearchDlg::searchMass(double value,
                          libXpertMassCore::Enums::MassType mass_type)
{
  // We are asked to search for mass 'value' of type 'mass_type'.
  // The mass will be searched in polymer sequence interval m_searchIndexRange.
  // The mass calculations are configured using m_calcOptions.

  if(mass_type == libXpertMassCore::Enums::MassType::BOTH)
    qFatal() << "Programming error.";

  qDebug() << "Searching mass(es), for index_range:"
           << m_searchIndexRange.m_start << "--" << m_searchIndexRange.m_stop;

  // Copy the ionizer we got at construction time of this dialog window
  // and make sure the ionizer is reset to NO charge whatsoever (that is,
  // neither _current state_ nor _to come_).
  libXpertMassCore::Ionizer ionizer(m_ionizer);
  ionizer.setLevel(0);
  ionizer.forceCurrentStateLevel(0);

  int count = 0;
  int cycle = 0;

  double max_searched_mass_value =
    static_cast<int>(mass_type) &
        static_cast<int>(libXpertMassCore::Enums::MassType::MONO)
      ? value + m_monoTolerance
      : value + m_avgTolerance;

  double min_searched_mass_value =
    static_cast<int>(mass_type) &
        static_cast<int>(libXpertMassCore::Enums::MassType::MONO)
      ? value - m_monoTolerance
      : value - m_avgTolerance;

  if(min_searched_mass_value < 0)
    min_searched_mass_value = 0;

  m_currentMass = value;
  updateProgressDetails(mass_type);

  for(qsizetype iter = m_searchIndexRange.m_start;
      iter < m_searchIndexRange.m_stop + 1;
      ++iter)
    {
      if(m_abort)
        return true;

      for(qsizetype jter = 0; jter < m_searchIndexRange.m_stop + 1 - iter;
          ++jter)
        {
          // Note that the ionization starts from a low value and goes
          // to a higher value, that means that while this loop runs
          // for a given oligomer, the m/z ratio goes diminishing.

          for(int kter = m_ionizeStart; kter < m_ionizeStop + 1;
              ++kter, ++cycle)
            {
              ++m_testedOligosCount;

              if(cycle == 200)
                {
                  cycle = 0;
                  updateProgressDetails(mass_type);
                }

              libXpertMassCore::CalcOptions calc_options(m_calcOptions);
              calc_options.setIndexRange(iter, iter + jter);

              ionizer.setLevel(kter);

              // qDebug() << "Creating Oligomer with calculation options:"
              //          << calc_options.toString();

              libXpertMassCore::Oligomer test_oligomer(
                msp_polymer,
                "NOT_SET" /*name*/,
                QString("") /*description*/,
                false /*modified*/,
                ionizer,
                calc_options);

              test_oligomer.getCrossLinksRef().assign(
                msp_polymer->getCrossLinksRef().begin(),
                msp_polymer->getCrossLinksRef().end());

              // qDebug() << "Going to calculate masses for the "
              //             "oligomer.";

              test_oligomer.calculateMasses();

              double mz = test_oligomer.getMass(mass_type);

              // qDebug() << "Done calculating the masses for the oligomer:" << mz;

              if(mz < min_searched_mass_value)
                // Oligomer's m/z already too small. Break
                // immediately as this cannot get any better by
                // continuing to increase the ionization level.
                break;

              if(mz > max_searched_mass_value)
                // Oligomer's m/z is still too big. Continue
                // incrementing the ionization level, so that its
                // m/z will decrease...
                continue;


              // At this point the oligomer has correct masses.

              libXpertMassCore::Oligomer *new_oligomer =
                new libXpertMassCore::Oligomer(test_oligomer);

              ++count;

              int totalCharge = ionizer.charge();

              QString name =
                QString("%1-z%2#%3").arg(value).arg(totalCharge).arg(count);

              new_oligomer->setName(name);

              // Set the searched mz in a prop.

              libXpertMassCore::DoubleProp *prop =
                new libXpertMassCore::DoubleProp("SEARCHED_MZ", value);

              new_oligomer->appendProp(prop);

#ifdef QT_DEBUG
              // Sanity check:
              libXpertMassCore::Prop *the_prop =
                new_oligomer->prop("SEARCHED_MZ");
              Q_ASSERT(the_prop);
              double mzSearchedDouble =
                *static_cast<const double *>(the_prop->data());

              qDebug() << "The searched m/z:" << mzSearchedDouble;

              if(mzSearchedDouble != value)
                qFatal() << "Programming error.";
#endif

              // Set the error mz in a prop.

              double error = mz - value;

              prop = new libXpertMassCore::DoubleProp("ERROR_MZ", error);

              new_oligomer->appendProp(prop);

              static_cast<int>(mass_type) &
                  static_cast<int>(libXpertMassCore::Enums::MassType::MONO)
                ? mpa_monoOligomerTableViewModel->addOligomer(new_oligomer)
                : mpa_avgOligomerTableViewModel->addOligomer(new_oligomer);

#ifdef QT_DEBUG
              if(static_cast<bool>(mass_type &
                                   libXpertMassCore::Enums::MassType::MONO))
                {
                  qDebug()
                    << "The added oligomer:"
                    << m_monoOligomers.getOligomersCstRef().back()->toString();
                }
              else
                {
                  qDebug()
                    << "The added oligomer:"
                    << m_avgOligomers.getOligomersCstRef().back()->toString();
                }
#endif
              ++m_foundOligosCount;

              updateProgressDetails(mass_type, false, new_oligomer);
            }
          // End of
          // for (int kter = m_ionizeStart; kter < m_ionizeStop + 1; ++kter)
        }
      // End of
      // for (int jter = 0; jter < endIndex + 1 - iter; ++jter)
    }
  // End of
  // for (int iter = startIndex; iter < endIndex + 1; ++iter)

  return true;
}


bool
MassSearchDlg::searchMasses(libXpertMassCore::Enums::MassType mass_type)
{
  if(!msp_polymer->size())
    return false;

  qDebug() << "Now searching masses in the polymer sequence index range:"
           << m_searchIndexRange.indicesAsText();

  if(static_cast<int>(mass_type) &
     static_cast<int>(libXpertMassCore::Enums::MassType::MONO))
    {
      for(int iter = 0; iter < m_monoMassesList.size(); ++iter)
        {
          double value = m_monoMassesList.at(iter);

          if(!calculateTolerance(value,
                                 libXpertMassCore::Enums::MassType::MONO))
            return false;

          if(!searchMass(value, libXpertMassCore::Enums::MassType::MONO))
            return false;

          m_ui.massSearchProgressBar->setValue(++m_progressValue);

          if(m_abort)
            return true;
        }
    }
  else if(static_cast<int>(mass_type) &
          static_cast<int>(libXpertMassCore::Enums::MassType::AVG))
    {
      for(int iter = 0; iter < m_avgMassesList.size(); ++iter)
        {
          double value = m_avgMassesList.at(iter);

          if(!calculateTolerance(value, libXpertMassCore::Enums::MassType::AVG))
            return false;

          if(!searchMass(value, libXpertMassCore::Enums::MassType::AVG))
            return false;

          m_ui.massSearchProgressBar->setValue(++m_progressValue);

          if(m_abort)
            return true;
        }
    }
  else
    Q_ASSERT(0);

  return true;
}


void
MassSearchDlg::search()
{
  // Update the options from the parent window.
  m_calcOptions.initialize(*mp_editorWnd->getCalcOptions());

  // Set the current whole/selected sequence data in the
  // m_calcOptions instance (false = no read from sequence editor
  // window).
  updateSelectionData(false);

  updateIonizationData();

  // Empty the treeviews and the lists of oligomers.
  mpa_monoOligomerTableViewModel->removeOligomers(
    0, mpa_monoOligomerTableViewModel->rowCount() - 1);
  mpa_avgOligomerTableViewModel->removeOligomers(
    0, mpa_avgOligomerTableViewModel->rowCount() - 1);

  emptyAllMassLists();

  m_abort = false;

  if(!parseMassText(libXpertMassCore::Enums::MassType::MONO))
    {
      QMessageBox::warning(
        this,
        tr("MassXpert3 - libXpertMassCore::Polymer Mass Search"),
        tr("%1@%2\n"
           "Failed to parse the masses to search for MONO.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return;
    }

  if(!parseMassText(libXpertMassCore::Enums::MassType::AVG))
    {
      QMessageBox::warning(
        this,
        tr("MassXpert3 - libXpertMassCore::Polymer Mass Search"),
        tr("%1@%2\n"
           "Failed to parse the masses to search for AVG.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return;
    }

  if(m_monoMassesList.size())
    {
      if(!checkTolerance(libXpertMassCore::Enums::MassType::MONO))
        {
          QMessageBox::information(this,
                                   tr("MassXpert3 - Mass Search"),
                                   tr("Enter a valid MONO tolerance value"),
                                   QMessageBox::Ok);
          return;
        }
    }

  if(m_avgMassesList.size())
    {
      if(!checkTolerance(libXpertMassCore::Enums::MassType::AVG))
        {
          QMessageBox::information(this,
                                   tr("MassXpert3 - Mass Search"),
                                   tr("Enter a valid AVG tolerance value"),
                                   QMessageBox::Ok);
          return;
        }
    }

  setCursor(Qt::WaitCursor);

  m_testedOligosCount = 0;
  m_foundOligosCount  = 0;

  int masses_to_search_count = m_monoMassesList.size() + m_avgMassesList.size();
  m_progressValue            = 0;
  m_ui.massSearchProgressBar->setRange(0, masses_to_search_count);

  updateProgressDetails(libXpertMassCore::Enums::MassType::MONO, true);
  updateProgressDetails(libXpertMassCore::Enums::MassType::AVG, true);

  if(!searchMasses(libXpertMassCore::Enums::MassType::MONO))
    {
      QMessageBox::warning(
        this,
        tr("MassXpert3 - libXpertMassCore::Polymer Mass Search"),
        tr("%1@%2\n"
           "Failed to search for the MONO masses.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      setCursor(Qt::ArrowCursor);
      updateMassSearchDetails(m_calcOptions);

      return;
    }

  if(!searchMasses(libXpertMassCore::Enums::MassType::AVG))
    {
      QMessageBox::warning(
        this,
        tr("MassXpert3 - libXpertMassCore::Polymer Mass Search"),
        tr("%1@%2\n"
           "Failed to search for the AVG masses.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      setCursor(Qt::ArrowCursor);
      updateMassSearchDetails(m_calcOptions);

      return;
    }

  setCursor(Qt::ArrowCursor);

  updateMassSearchDetails(m_calcOptions);
}


void
MassSearchDlg::setupTableViews()
{
  // Model stuff all thought for sorting.
  mpa_monoOligomerTableViewModel =
    new MassSearchOligomerTableViewModel(&m_monoOligomers, this);

  mpa_avgOligomerTableViewModel =
    new MassSearchOligomerTableViewModel(&m_avgOligomers, this);

  mpa_monoProxyModel = new MassSearchOligomerTableViewSortProxyModel(this);
  mpa_monoProxyModel->setSourceModel(mpa_monoOligomerTableViewModel);
  mpa_monoProxyModel->setFilterKeyColumn(-1);

  mpa_avgProxyModel = new MassSearchOligomerTableViewSortProxyModel(this);
  mpa_avgProxyModel->setSourceModel(mpa_avgOligomerTableViewModel);
  mpa_avgProxyModel->setFilterKeyColumn(-1);

  m_ui.monoOligomerTableView->setModel(mpa_monoProxyModel);
  m_ui.monoOligomerTableView->setParentDlg(this);
  m_ui.monoOligomerTableView->setOligomerCollection(&m_monoOligomers);
  mpa_monoOligomerTableViewModel->setTableView(m_ui.monoOligomerTableView);

  m_ui.avgOligomerTableView->setModel(mpa_avgProxyModel);
  m_ui.avgOligomerTableView->setParentDlg(this);
  m_ui.avgOligomerTableView->setOligomerCollection(&m_avgOligomers);
  mpa_avgOligomerTableViewModel->setTableView(m_ui.avgOligomerTableView);
}


void
MassSearchDlg::updateIonizationData()
{
  m_ionizeStart = m_ui.ionizationStartSpinBox->value();
  m_ionizeStop  = m_ui.ionizationEndSpinBox->value();

  if(m_ionizeStart > m_ionizeStop)
    std::swap(m_ionizeStart, m_ionizeStop);
}


void
MassSearchDlg::updateProgressDetails(
  libXpertMassCore::Enums::MassType mass_type,
  bool reset,
  libXpertMassCore::Oligomer *oligomer_p)
{
  qDebug();

  if(reset)
    {
      m_ui.lastOligoNameLineEdit->setText("");
      m_ui.lastOligoCoordinatesLineEdit->setText("");
      m_ui.lastOligoMonoMassLineEdit->setText("");
      m_ui.lastOligoAvgMassLineEdit->setText("");

      m_ui.currentMassLineEdit->setText("");

      m_ui.lastOligoMassTypeLineEdit->setText("");

      m_ui.testedCountLabel->setText(QString().setNum(0));
      m_ui.foundCountLabel->setText(QString().setNum(0));

      m_ui.massSearchProgressBar->setValue(0);

      QApplication::processEvents();

      return;
    }

  qDebug();

  if(oligomer_p)
    {
      qDebug();

      m_ui.lastOligoNameLineEdit->setText(oligomer_p->getName());
      qDebug();

      const libXpertMassCore::IndexRangeCollection &index_range_collection =
        oligomer_p->getCalcOptionsCstRef().getIndexRangeCollectionCstRef();

      if(index_range_collection.size())
        {
          QString coordinates(
            tr("[ %1 - %2 ]")
              .arg(index_range_collection.getRangesCstRef().back()->m_start + 1)
              .arg(index_range_collection.getRangesCstRef().back()->m_stop +
                   1));

          m_ui.lastOligoCoordinatesLineEdit->setText(coordinates);
        }
      else
        qFatal()
          << "Programming error. Cannot be that Oligomer has not a "
             "single index range.";

      QString mass = QString::number(
        oligomer_p->getMass(libXpertMassCore::Enums::MassType::MONO),
        'f',
        libXpertMassCore::OLIGOMER_DEC_PLACES);

      m_ui.lastOligoMonoMassLineEdit->setText(mass);

      mass = QString::number(
        oligomer_p->getMass(libXpertMassCore::Enums::MassType::AVG),
        'f',
        libXpertMassCore::OLIGOMER_DEC_PLACES);
      m_ui.lastOligoAvgMassLineEdit->setText(mass);
    }

  qDebug();

  m_ui.currentMassLineEdit->setText(QString().setNum(m_currentMass, 'g', 10));

  if(mass_type == libXpertMassCore::Enums::MassType::MONO)
    m_ui.lastOligoMassTypeLineEdit->setText(tr("MONO"));
  else
    m_ui.lastOligoMassTypeLineEdit->setText(tr("AVG"));

  m_ui.testedCountLabel->setText(QString().setNum(m_testedOligosCount));
  m_ui.foundCountLabel->setText(QString().setNum(m_foundOligosCount));

  m_ui.massSearchProgressBar->setValue(m_progressValue);

  qDebug();

  QApplication::processEvents();
}


void
MassSearchDlg::updateMassSearchDetails(
  const libXpertMassCore::CalcOptions &calcOptions)
{
  if(static_cast<int>(calcOptions.getMonomerEntities()) &
     static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::MODIF))
    m_ui.modificationsCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.modificationsCheckBox->setCheckState(Qt::Unchecked);

  if(static_cast<int>(calcOptions.getMonomerEntities()) &
     static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER))
    m_ui.crossLinksCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.crossLinksCheckBox->setCheckState(Qt::Unchecked);

  if(static_cast<int>(calcOptions.getPolymerEntities()) &
     static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF))
    m_ui.leftModifCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.leftModifCheckBox->setCheckState(Qt::Unchecked);

  if(static_cast<int>(calcOptions.getPolymerEntities()) &
     static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF))
    m_ui.rightModifCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.rightModifCheckBox->setCheckState(Qt::Unchecked);
}


void
MassSearchDlg::updateOligomerSequence(QString *text)
{
  Q_ASSERT(text);

  m_ui.oligomerSequenceTextEdit->clear();
  m_ui.oligomerSequenceTextEdit->append(*text);
}


void
MassSearchDlg::clearOligomerCollections()
{
  m_monoOligomers.clear();
  m_avgOligomers.clear();
}


void
MassSearchDlg::emptyAllMassLists()
{
  while(!m_monoMassesList.isEmpty())
    m_monoMassesList.removeFirst();

  while(!m_avgMassesList.isEmpty())
    m_avgMassesList.removeFirst();
}


void
MassSearchDlg::abort()
{
  m_abort = true;
}


bool
MassSearchDlg::calculateFilterTolerance(
  double mass, libXpertMassCore::Enums::MassType mass_type)
{
  if(mass_type == libXpertMassCore::Enums::MassType::BOTH)
    qFatal() << "Programming error.";

  // Get the tolerance that is in its lineEdit.
  QString text;

  if(mass_type == libXpertMassCore::Enums::MassType::MONO)
    text = m_ui.monoFilterToleranceLineEdit->text();
  else
    text = m_ui.avgFilterToleranceLineEdit->text();

  double tolerance = 0;
  bool ok          = false;

  if(!text.isEmpty())
    {
      // Convert the string to a double.
      ok        = false;
      tolerance = text.toDouble(&ok);

      if(!tolerance && !ok)
        return false;
    }
  else
    {
      m_filterTolerance = 0;
    }

  // What's the item currently selected in the comboBox?
  int index = 0;

  if(mass_type == libXpertMassCore::Enums::MassType::MONO)
    index = m_ui.monoFilterToleranceComboBox->currentIndex();
  else
    index = m_ui.avgFilterToleranceComboBox->currentIndex();

  if(index == 0)
    {
      // MASS_TOLERANCE_AMU
      m_filterTolerance = tolerance;
    }
  else if(index == 1)
    {
      // MASS_TOLERANCE_PCT
      m_filterTolerance = (tolerance / 100) * mass;
    }
  else if(index == 2)
    {
      // MASS_TOLERANCE_PPM
      m_filterTolerance = (tolerance / 1000000) * mass;
    }
  else
    Q_ASSERT(0);

  qDebug() << "The tolerance is configured:" << m_filterTolerance;

  return true;
}


void
MassSearchDlg::monoFilterOptions(bool checked)
{
  if(!checked)
    {
      mpa_monoProxyModel->setFilterKeyColumn(-1);

      mpa_monoProxyModel->applyNewFilter();

      m_ui.monoFilteringOptionsFrame->setVisible(false);
    }
  else
    {
      m_ui.monoFilteringOptionsFrame->setVisible(true);

      // In this case, set focus to the last focused widget in the
      // groupbox or the first widget in the groubox if this is the
      // first time the filtering is used.
      mp_monoFocusWidget->setFocus();
    }
}


void
MassSearchDlg::monoFilterOptionsToggled()
{
  bool isChecked = m_ui.monoFilteringOptionsGroupBox->isChecked();

  m_ui.monoFilteringOptionsGroupBox->setChecked(!isChecked);
  monoFilterOptions(!isChecked);
}


void
MassSearchDlg::avgFilterOptions(bool checked)
{
  if(!checked)
    {
      mpa_avgProxyModel->setFilterKeyColumn(-1);

      mpa_avgProxyModel->applyNewFilter();

      m_ui.avgFilteringOptionsFrame->setVisible(false);
    }
  else
    {
      m_ui.avgFilteringOptionsFrame->setVisible(true);

      // In this case, set focus to the last focused widget in the
      // groupbox or the first widget in the groubox if this is the
      // first time the filtering is used.
      mp_avgFocusWidget->setFocus();
    }
}


void
MassSearchDlg::avgFilterOptionsToggled()
{
  bool isChecked = m_ui.avgFilteringOptionsGroupBox->isChecked();

  m_ui.avgFilteringOptionsGroupBox->setChecked(!isChecked);
  avgFilterOptions(!isChecked);
}


void
MassSearchDlg::monoFilterSearched()
{
  // First off, we have to get the mass that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  mpa_monoProxyModel->setSearchedFilter(mass);

  mpa_monoProxyModel->setFilterKeyColumn(0);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterSearchedLineEdit;
}


void
MassSearchDlg::avgFilterSearched()
{
  // First off, we have to get the mass that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  mpa_avgProxyModel->setSearchedFilter(mass);

  mpa_avgProxyModel->setFilterKeyColumn(0);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterSearchedLineEdit;
}


void
MassSearchDlg::monoFilterError()
{
  // First off, we have to get the mass error that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, libXpertMassCore::Enums::MassType::MONO))
    {
      QMessageBox::information(this,
                               m_applicationName,
                               "Failed to calculate the tolerance.",
                               QMessageBox::Ok);
      return;
    }

  mpa_monoProxyModel->setErrorFilter(mass);
  mpa_monoProxyModel->setTolerance(m_filterTolerance);

  mpa_monoProxyModel->setFilterKeyColumn(3);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterErrorLineEdit;
}


void
MassSearchDlg::avgFilterError()
{
  // First off, we have to get the mass error that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, libXpertMassCore::Enums::MassType::AVG))
    {
      QMessageBox::information(this,
                               m_applicationName,
                               "Failed to calculate the tolerance.",
                               QMessageBox::Ok);
      return;
    }

  mpa_avgProxyModel->setErrorFilter(mass);
  mpa_avgProxyModel->setTolerance(m_filterTolerance);

  mpa_avgProxyModel->setFilterKeyColumn(3);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterErrorLineEdit;
}


void
MassSearchDlg::monoFilterMonoMass()
{
  // First off, we have to get the mass that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, libXpertMassCore::Enums::MassType::MONO))
    {
      QMessageBox::information(this,
                               m_applicationName,
                               "Failed to calculate the tolerance.",
                               QMessageBox::Ok);
      return;
    }

  mpa_monoProxyModel->setMonoFilter(mass);
  mpa_monoProxyModel->setTolerance(m_filterTolerance);

  mpa_monoProxyModel->setFilterKeyColumn(4);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterMonoMassLineEdit;
}


void
MassSearchDlg::avgFilterMonoMass()
{
  // First off, we have to get the mass that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, libXpertMassCore::Enums::MassType::AVG))
    {
      QMessageBox::information(this,
                               m_applicationName,
                               "Failed to calculate the tolerance.",
                               QMessageBox::Ok);
      return;
    }

  mpa_avgProxyModel->setMonoFilter(mass);
  mpa_avgProxyModel->setTolerance(m_filterTolerance);

  mpa_avgProxyModel->setFilterKeyColumn(4);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterMonoMassLineEdit;
}


void
MassSearchDlg::monoFilterAvgMass()
{
  // First off, we have to get the mass that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, libXpertMassCore::Enums::MassType::MONO))
    {
      QMessageBox::information(this,
                               m_applicationName,
                               "Failed to calculate the tolerance.",
                               QMessageBox::Ok);
      return;
    }

  mpa_monoProxyModel->setAvgFilter(mass);
  mpa_monoProxyModel->setTolerance(m_filterTolerance);

  mpa_monoProxyModel->setFilterKeyColumn(5);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterAvgMassLineEdit;
}


void
MassSearchDlg::avgFilterAvgMass()
{
  // First off, we have to get the mass that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, libXpertMassCore::Enums::MassType::AVG))
    {
      QMessageBox::information(this,
                               m_applicationName,
                               "Failed to calculate the tolerance.",
                               QMessageBox::Ok);
      return;
    }

  mpa_avgProxyModel->setAvgFilter(mass);
  mpa_avgProxyModel->setTolerance(m_filterTolerance);

  mpa_avgProxyModel->setFilterKeyColumn(5);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterAvgMassLineEdit;
}


// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
void
MassSearchDlg::exportResults(int index)
{
  // Remember that we had set up the combobox with the following strings:
  // << tr("To &Clipboard")
  // << tr("To &File")
  // << tr("&Select File");

  if(index == 0)
    {
      exportResultsClipboard();
    }
  else if(index == 1)
    {
      exportResultsFile();
    }
  else if(index == 2)
    {
      selectResultsFile();
    }
  else
    Q_ASSERT(0);
}


void
MassSearchDlg::prepareResultsTxtString()
{
  // Set the delimiter to the char/string that is in the
  // corresponding text line edit widget.

  QString delimiter = m_ui.delimiterLineEdit->text();
  // If delimiter is empty, the function that will prepare the
  // string will put the default character, that is '$'.

  mpa_resultsString->clear();

  // We only put the header info if the user does not want to have
  // the data formatted for XpertMiner, which only can get the data
  // in the format :
  //
  // mass <delim> charge <delim> name <delim> coords
  //
  // Note that name and coords are optional.
  bool forXpertMiner = false;
  libXpertMassCore::Enums::MassType massType;

  forXpertMiner = m_ui.forXpertMinerCheckBox->isChecked();

  if(!forXpertMiner)
    {
      *mpa_resultsString += QObject::tr(
        "# \n"
        "# ---------------------------\n"
        "# Mass Search: \n"
        "# ---------------------------\n");
    }

  // Then, we should ask whether the masses to be exported are
  // the mono or avg masses.

  if(m_ui.monoRadioButton->isChecked())
    massType = libXpertMassCore::Enums::MassType::MONO;
  else
    massType = libXpertMassCore::Enums::MassType::AVG;

  // Should the oligomers have their sequence displayed?
  bool withSequence = m_ui.withSequenceCheckBox->isChecked();

  QString masses;

  // We only show the list of masses if we are exporting not for
  // XpertMiner.

  if(!forXpertMiner)
    {
      // Get the mono masses searched:
      masses = m_ui.monoMassTextEdit->toPlainText();
      if(!masses.isEmpty())
        {
          *mpa_resultsString += QObject::tr(
                                  "\nSearched mono masses:\n"
                                  "%1\n")
                                  .arg(masses);
        }
    }

  QString *text = 0;

  // We export mono data only in two situations: when the data is
  // for XpertMiner and the user has selected the Mono radio button
  // or when the data is not for XpertMiner.

  if((!forXpertMiner) || (forXpertMiner && m_ui.monoRadioButton->isChecked()))
    {
      // The mono treeview:

      text = m_ui.monoOligomerTableView->describeSelectedOligomersAsPlainText(
        delimiter, withSequence, forXpertMiner, massType);

      *mpa_resultsString += *text;

      delete text;
    }

  if(!forXpertMiner)
    {
      // Get the avg masses searched:
      masses = m_ui.avgMassTextEdit->toPlainText();
      if(!masses.isEmpty())
        {
          *mpa_resultsString += QObject::tr(
                                  "\nSearched average masses:\n"
                                  "%1\n")
                                  .arg(masses);
        }
    }


  // We export mono data only in two situations: when the data is
  // for XpertMiner and the user has selected the Mono radio button
  // or when the data is not for XpertMiner.

  if((!forXpertMiner) || (forXpertMiner && m_ui.avgRadioButton->isChecked()))
    {
      // The avg treeview:

      text = m_ui.avgOligomerTableView->describeSelectedOligomersAsPlainText(
        delimiter, withSequence, forXpertMiner, massType);

      *mpa_resultsString += *text;

      delete text;
    }
}


bool
MassSearchDlg::exportResultsClipboard()
{
  prepareResultsTxtString();

  QClipboard *clipboard = QApplication::clipboard();

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

  return true;
}


bool
MassSearchDlg::exportResultsFile()
{
  if(m_resultsFilePath.isEmpty())
    {
      if(!selectResultsFile())
        return false;
    }

  QFile file(m_resultsFilePath);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
    {
      QMessageBox::information(0,
                               tr("MassXpert3 - Export Data"),
                               tr("Failed to open file in append mode."),
                               QMessageBox::Ok);
      return false;
    }

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

  prepareResultsTxtString();

  stream << *mpa_resultsString;

  file.close();

  return true;
}


bool
MassSearchDlg::selectResultsFile()
{
  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.

} // namespace MassXpert

} // namespace MsXpS
