1 /* massXpert - the true massist's program.
2    --------------------------------------
3    Copyright(C) 2006,2007 Filippo Rusconi
4 
5    http://www.filomace.org/massXpert
6 
7    This file is part of the massXpert project.
8 
9    The massxpert project is the successor to the "GNU polyxmass"
10    project that is an official GNU project package(see
11    www.gnu.org). The massXpert project is not endorsed by the GNU
12    project, although it is released ---in its entirety--- under the
13    GNU General Public License. A huge part of the code in massXpert
14    is actually a C++ rewrite of code in GNU polyxmass. As such
15    massXpert was started at the Centre National de la Recherche
16    Scientifique(FRANCE), that granted me the formal authorization to
17    publish it under this Free Software License.
18 
19    This software is free software; you can redistribute it and/or
20    modify it under the terms of the GNU  General Public
21    License version 3, as published by the Free Software Foundation.
22 
23 
24    This software is distributed in the hope that it will be useful,
25    but WITHOUT ANY WARRANTY; without even the implied warranty of
26    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
27    General Public License for more details.
28 
29    You should have received a copy of the GNU General Public License
30    along with this software; if not, write to the
31 
32    Free Software Foundation, Inc.,
33 
34    51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
35 */
36 
37 
38 /////////////////////// Qt includes
39 #include<QtGui>
40 #include<QMessageBox>
41 #include<QFileDialog>
42 
43 /////////////////////// Std includes
44 #include <math.h>
45 #include <algorithm>
46 #include <limits> // for std::numeric_limits
47 
48 using namespace std;
49 
50 /////////////////////// Local includes
51 #include "spectrumCalculationDlg.hpp"
52 #include "isotopicPatternCalculator.hpp"
53 #include "application.hpp"
54 
55 
56 namespace massXpert
57 {
58 
59   enum
60     {
61       TAB_WIDGET_INPUT_DATA,
62       TAB_WIDGET_LOG,
63       TAB_WIDGET_RESULTS,
64     };
65 
66 
SpectrumCalculationDlg(QWidget * parent,const QList<Atom * > & atomList,SpectrumCalculationMode mode)67   SpectrumCalculationDlg::SpectrumCalculationDlg
68   (QWidget *parent,
69    const QList<Atom *> &atomList,
70    SpectrumCalculationMode mode)
71    : QDialog(parent),
72      m_mode(mode),
73      m_atomList(atomList),
74      m_polChemDef(static_cast<CalculatorWnd *>(parent)->polChemDef())
75      {
76     Q_ASSERT(parent);
77 
78     setWindowTitle("massXpert: Spectrum Calculator");
79 
80     m_aborted = false;
81 
82     // When the window is created the formula line edit is empty, so
83     // at least that data has an error. The ionization data is most
84     // probably not erroneous because it comes from the polymer
85     // chemistry definition or from the caller.
86     m_validationErrors = MXP_VALIDATION_FORMULA_ERRORS;
87 
88     m_filePath = "";
89 
90     m_ui.setupUi(this);
91 
92     // Set the oligomer list pointer to 0, so that we know if it was
93     // set or not later.
94     mp_oligomerList = 0;
95 
96     setupDialog();
97 
98     QSettings settings
99      (static_cast<Application *>(qApp)->configSettingsFilePath(),
100        QSettings::IniFormat);
101 
102     settings.beginGroup("isotopic_pattern_calculation_dlg");
103 
104     restoreGeometry(settings.value("geometry").toByteArray());
105 
106     m_ui.splitter->restoreState(settings.value("splitter").toByteArray());
107 
108     settings.endGroup();
109 
110     connect(m_ui.chargeSpinBox,
111             SIGNAL(valueChanged(int)),
112             this,
113             SLOT(chargeChanged(int)));
114 
115     connect(m_ui.formulaLineEdit,
116             SIGNAL(textEdited(const QString &)),
117             this,
118             SLOT(formulaEdited(const QString &)));
119 
120     connect(m_ui.pointNumberSpinBox,
121             SIGNAL(valueChanged(int)),
122             this,
123             SLOT(pointsChanged(int)));
124 
125     connect(m_ui.resolutionSpinBox,
126             SIGNAL(valueChanged(int)),
127             this,
128             SLOT(resolutionChanged(int)));
129 
130     connect(m_ui.fwhmLineEdit,
131             SIGNAL(textEdited(const QString &)),
132             this,
133             SLOT(fwhmEdited(const QString &)));
134 
135     connect(m_ui.monoRadioButton,
136             SIGNAL(toggled(bool)),
137             this,
138             SLOT(massTypeRadioButtonToggled(bool)));
139 
140     connect(m_ui.avgRadioButton,
141             SIGNAL(toggled(bool)),
142             this,
143             SLOT(massTypeRadioButtonToggled(bool)));
144 
145     connect(m_ui.avgRadioButton,
146             SIGNAL(toggled(bool)),
147             this,
148             SLOT(massTypeRadioButtonToggled(bool)));
149 
150     connect(m_ui.isotopicClusterCheckBox,
151             SIGNAL(toggled(bool)),
152             this,
153             SLOT(isotopicClusterCheckBoxToggled(bool)));
154 
155     connect(m_ui.executePushButton,
156 	     SIGNAL(clicked()),
157 	     this,
158 	     SLOT(execute()));
159 
160     connect(m_ui.abortPushButton,
161 	     SIGNAL(clicked()),
162 	     this,
163 	     SLOT(abort()));
164 
165     connect(m_ui.outputFilePushButton,
166 	     SIGNAL(clicked()),
167 	     this,
168 	     SLOT(outputFile()));
169   }
170 
171 
172   void
closeEvent(QCloseEvent * event)173   SpectrumCalculationDlg::closeEvent(QCloseEvent *event)
174   {
175     if (event)
176       printf("%s", "");
177 
178     QSettings settings
179      (static_cast<Application *>(qApp)->configSettingsFilePath(),
180        QSettings::IniFormat);
181 
182     settings.beginGroup("isotopic_pattern_calculation_dlg");
183 
184     settings.setValue("geometry", saveGeometry());
185 
186     settings.setValue("splitter", m_ui.splitter->saveState());
187 
188     settings.endGroup();
189   }
190 
191 
~SpectrumCalculationDlg()192   SpectrumCalculationDlg::~SpectrumCalculationDlg()
193   {
194     while(m_patternPointList.size())
195       delete m_patternPointList.takeFirst();
196   }
197 
198 
199   void
setOligomerList(OligomerList * list)200   SpectrumCalculationDlg::setOligomerList(OligomerList *list)
201   {
202     if(!list)
203       qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
204 
205     mp_oligomerList = list;
206   }
207 
208 
209   void
setupDialog()210   SpectrumCalculationDlg::setupDialog()
211   {
212     // Always start the dialog with the first page of the tab widget,
213     // the input data page.
214     m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_INPUT_DATA);
215 
216     // By default we want a gaussian-type shape.
217     m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN);
218 
219     // Throughout of *this* dialog window, the normalization factor
220     // for the peak shapes (Gaussian, specifically) is going to be 1.
221 
222     // Charge stuff.
223     m_ui.chargeSpinBox->setRange(1, 10000000);
224     m_ui.chargeSpinBox->setValue(1);
225     m_charge = m_ui.chargeSpinBox->value();
226 
227     // Isotopic peak probability stuff.
228     m_ui.minimumProbabilityLineEdit->setText("0.0000001");
229     m_minimumProbability = 0.0000001;
230 
231     // Max number of peaks in the isotopic cluster.
232     m_ui.maximumPeaksSpinBox->setRange(0, 1000);
233     m_ui.maximumPeaksSpinBox->setValue(100);
234     m_maximumPeaks = 100;
235 
236     // Resolution and FWHM stuff
237     m_ui.resolutionSpinBox->setRange(0, 1000000);
238     m_ui.resolutionSpinBox->setValue(0);
239     m_resolution = 0;
240 
241     // Get the number of points used to craft the curve.
242     m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
243 
244     // Since both values above are not correct, set the corresponding
245     // bits to the validation error int.
246     m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS;
247     m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;
248 
249     // Now, depending on the kind of operation required for the
250     // dialog, adapt some behaviour.
251 
252 
253     if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
254       {
255         // In isotopic cluster calculation mode, the m/z calculated
256         // starting from the formula and the charge is of course mono!
257         // Thus, we set it checked and disable the enclosing group box.
258 
259         m_ui.monoRadioButton->setChecked(true);
260         m_ui.massTypeGroupBox->setDisabled(true);
261 
262         // Of course, the computeIsotopicClusterCheckBox should be
263         // checked and disabled.
264         m_ui.isotopicClusterCheckBox->setChecked(true);
265         m_ui.isotopicClusterCheckBox->setDisabled(true);
266 
267         // And all the related widgets should be active;
268 
269         m_ui.pointNumberSpinBox->setDisabled(false);
270         m_ui.minimumProbabilityLineEdit->setDisabled(false);
271         m_ui.maximumPeaksSpinBox->setDisabled(false);
272 
273       }
274     else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM)
275       {
276         // In spectrum mode, the formula, z and m/z are not active.
277         m_ui.mzGroupBox->setDisabled(true);
278 
279         // By default, we work on mono masses.
280         m_withMonoMass= true;
281         m_ui.monoRadioButton->setChecked(m_withMonoMass);
282 
283         // By default, we do not want isotopic cluster calculations.
284         m_withCluster = false;
285         m_ui.isotopicClusterCheckBox->setChecked(m_withCluster);
286 
287         // Not willing to compute isotopic clusters, thus all the
288         // related widgets should be inactive.
289         m_ui.pointNumberSpinBox->setDisabled(true);
290         m_ui.minimumProbabilityLineEdit->setDisabled(true);
291         m_ui.maximumPeaksSpinBox->setDisabled(true);
292       }
293   }
294 
295 
296   bool
fetchValidateInputData()297   SpectrumCalculationDlg::fetchValidateInputData()
298   {
299     if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
300       return fetchValidateInputDataIsotopicClusterMode();
301     else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM)
302       return fetchValidateInputDataSpectrumMode();
303     else
304       return false;
305   }
306 
307 
308   bool
fetchValidateInputDataIsotopicClusterMode()309   SpectrumCalculationDlg::fetchValidateInputDataIsotopicClusterMode()
310   {
311     Application *application = static_cast<Application *>(qApp);
312 
313     // The ionization stuff, the formula for which the isotopic
314     // pattern is to be calculated, the mz ratio (in m_mono) all have
315     // been checked previously by automated procedures (see the
316     // "changed" slots). We have to make sure that either the
317     // resolution or the FWHM values are OK. All the other bits must
318     // be clear. Only either the RESOLUTION or the FWHM bit might be
319     // set.
320 
321     int testBitset = 0;
322     testBitset |= MXP_VALIDATION_RESOLUTION_ERRORS;
323     testBitset |= MXP_VALIDATION_FWHM_ERRORS;
324 
325     // debugPutStdErrBitset(__FILE__, __LINE__,
326     //                      testBitset, "testBitset");
327 
328     if(m_validationErrors == testBitset)
329       return false;
330 
331     if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) ==
332        MXP_VALIDATION_FORMULA_ERRORS)
333       return false;
334 
335     // Now that we know that the formula was validated, we can ask
336     // that all the isotopes be deep-copied into the formula.
337 
338     // The list of atomCount objects has to be made with fully detailed
339     // isotopes...
340     if (!m_formula.deepAtomCopy(m_atomList))
341       {
342 	QMessageBox::warning(0,
343                              tr("massXpert: Spectrum Calculator"),
344                              tr("Failed to deep-copy atom list."),
345                              QMessageBox::Ok);
346 
347 	return false;
348       }
349 
350     // We still have to fetch a number of parameters.
351 
352     // Get to know if we want gaussian or lorentzian shapes.
353     if(m_ui.gaussianRadioButton->isChecked())
354       {
355         m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN);
356       }
357     else
358       {
359         m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_LORENTZIAN);
360       }
361 
362     // Shall we use localization for all the numerical output?
363     bool isUsingLocale = m_ui.localeCheckBox->checkState() == Qt::Checked ?
364       true : false;
365 
366     int totAtoms = m_formula.totalAtoms();
367     m_ui.progressBar->setRange(0, totAtoms);
368     m_ui.progressBar->setValue(0);
369 
370     int totIsotopes = m_formula.totalIsotopes(m_atomList);
371 
372     QString textEditText;
373 
374     if (isUsingLocale)
375       textEditText += tr("INPUT\n=====\n\n"
376                          "Formula: %1\n"
377                          "Charge: %2\n"
378                          "Mono Mass: %3 \t Avg mass: %4\n"
379                          "Total number of atoms: %5\t"
380                          "Total number of isotopes: %6\n\n")
381         .arg(m_formula.text())
382 	.arg(m_charge)
383         .arg(application->locale().
384              toString(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES))
385 	.arg(application->locale().
386              toString(m_avg, 'f', MXP_OLIGOMER_DEC_PLACES))
387         .arg(totAtoms)
388 	.arg(totIsotopes);
389     else
390       textEditText += tr("INPUT\n=====\n\n"
391                          "Formula: %1\n"
392                          "Charge: %2\n"
393                          "Mono Mass: %3 \t Avg mass: %4\n"
394                          "Total number of atoms: %5\t"
395                          "Total number of isotopes: %6\n\n")
396 	.arg(m_formula.text())
397         .arg(m_charge)
398 	.arg(QString().setNum(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES))
399 	.arg(QString().setNum(m_avg, 'f', MXP_OLIGOMER_DEC_PLACES))
400 	.arg(totAtoms)
401 	.arg(totIsotopes);
402 
403     // Put that description in the text edit widget.
404     m_ui.resultPlainTextEdit->appendPlainText(textEditText);
405 
406     // qDebug() << __FILE__ << __LINE__
407     //          << textEditText;
408 
409     // At this point we have to sort out wether the user wants to use
410     // the resolution or the FWHM for the calculations. This is
411     // automatically known by looking at which value is 0 (see the
412     // corresponding changed() slots).
413 
414     // Get the points value.
415     m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
416 
417     // How about setting the increment, that is the size of the gap
418     // between two points ? If it is not set (its value is 0), then
419     // increment is fwhm() / m_pointNumber;
420 
421     // First however, see if the user wants to use the FWHM value or
422     // the resolution.
423 
424     if(m_config.fwhm())
425       {
426         // FWHM is not zero, which means the user has set a value for
427         // it. Keep it.
428       }
429     else
430       {
431         // We have to compute the FWHM starting from the mz ratio and
432         // the resolution.
433 
434         m_config.setFwhm(m_mono / m_resolution);
435       }
436 
437     // Set the maximum number of peaks in the isotopic curve.
438     m_maximumPeaks = m_ui.maximumPeaksSpinBox->value();
439 
440     // Set the minimum probability each isotopic peak must have to be
441     // retained in the final curve.
442 
443     QString minProbString = m_ui.minimumProbabilityLineEdit->text();
444     bool ok = false;
445 
446     double minProb = minProbString.toDouble(&ok);
447 
448     if(!minProb && !ok)
449       {
450 	QMessageBox::warning(0,
451                              tr("massXpert: Spectrum Calculator"),
452                              tr("Please, fix the minimum probability."),
453                              QMessageBox::Ok);
454 
455 	return false;
456       }
457 
458     m_minimumProbability = minProb;
459 
460     return true;
461   }
462 
463 
464   bool
fetchValidateInputDataSpectrumMode()465   SpectrumCalculationDlg::fetchValidateInputDataSpectrumMode()
466   {
467     // The ionization stuff, the formula for which the isotopic
468     // pattern is to be calculated, the mz ratio (in m_mono) all have
469     // been checked previously by automated procedures (see the
470     // "changed" slots). We have to make sure that either the
471     // resolution or the FWHM values are OK. All the other bits must
472     // be clear. Only either the RESOLUTION or the FWHM bit might be
473     // set.
474 
475     int testBitset = 0;
476     testBitset |= MXP_VALIDATION_RESOLUTION_ERRORS;
477     testBitset |= MXP_VALIDATION_FWHM_ERRORS;
478 
479     // debugPutStdErrBitset(__FILE__, __LINE__,
480     //                      testBitset, "testBitset");
481 
482     if(m_validationErrors == testBitset)
483       return false;
484 
485     // We do not care of the formula in our spectrum calculation
486     // situation. Each oligomer from a cleavage contains a chemical
487     // composition formula that we'll use each time.
488 
489     // We still have to fetch a number of parameters.
490 
491     // Are we working on the mono or the avg masses of the oligomers?
492 
493     m_withMonoMass = m_ui.monoRadioButton->isChecked();
494 
495     // Get to know if we want gaussian or lorentzian shapes.
496     if(m_ui.gaussianRadioButton->isChecked())
497       {
498         m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN);
499       }
500     else
501       {
502         m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_LORENTZIAN);
503       }
504 
505     // At this point we have to sort out wether the user wants to use
506     // the resolution or the FWHM for the calculations. This is
507     // automatically known by looking at which value is 0 (see the
508     // corresponding changed() slots).
509 
510     // Get the points value.
511     m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
512 
513     // How about setting the increment, that is the size of the gap
514     // between two points ? If it is not set (its value is 0), then
515     // increment is fwhm() / m_pointNumber;
516 
517     // First however, see if the user wants to use the FWHM value or
518     // the resolution.
519 
520     if(m_config.fwhm())
521       {
522         // FWHM is not zero, which means the user has set a value for
523         // it. Keep it.
524       }
525     else
526       {
527         // We cannot compute fwhm starting from the resolution because
528         // we cannot know what is the m/z of the oligomers beforehand.
529 
530         // All we can check is that resolution is non-zero.
531 
532         if(m_resolution <= 0)
533           {
534             QMessageBox::warning(0,
535                                  tr("massXpert: Spectrum Calculator"),
536                                  tr("Please, fix the resolution or the FWHM."),
537                                  QMessageBox::Ok);
538 
539             return false;
540           }
541       }
542 
543     // Set the maximum number of peaks in the isotopic curve.
544     m_maximumPeaks = m_ui.maximumPeaksSpinBox->value();
545 
546     // Set the minimum probability each isotopic peak must have to be
547     // retained in the final curve.
548 
549     QString minProbString = m_ui.minimumProbabilityLineEdit->text();
550     bool ok = false;
551 
552     double minProb = minProbString.toDouble(&ok);
553 
554     if(!minProb && !ok)
555       {
556 	QMessageBox::warning(0,
557                              tr("massXpert: Spectrum Calculator"),
558                              tr("Please, fix the minimum probability."),
559                              QMessageBox::Ok);
560 
561 	return false;
562       }
563 
564     m_minimumProbability = minProb;
565 
566     return true;
567   }
568 
569 
570   // Returns false if formula is bad or aborts if calculation fails..
571   bool
fetchFormulaMass(double * mono,double * avg)572   SpectrumCalculationDlg::fetchFormulaMass(double *mono, double *avg)
573   {
574     // Show what's the value of the errors.
575     // debugPutStdErrBitset(__FILE__, __LINE__,
576     //                      m_validationErrors,
577     //                      "m_validationErrors @ fetchFormulaMass");
578 
579     // Check if there are currently errors set.
580 
581     if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) ==
582        MXP_VALIDATION_FORMULA_ERRORS)
583       return false;
584 
585     double monoMass = 0;
586     double avgMass = 0;
587 
588     // It is impossible that we have an error here, since the formula
589     // was previously validated (MXP_VALIDATION_FORMULA_ERRORS above).
590     if (!m_formula.accountMasses(m_atomList, &monoMass, &avgMass, 1))
591       qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
592 
593     if(mono)
594       *mono = monoMass;
595 
596     if(avg)
597       *avg = avgMass;
598 
599     // qDebug() << __FILE__ << __LINE__
600     //          << "Formula mono/avg masses:"
601     //          << monoMass << "/" << avgMass;
602 
603     // Show what's the value of the errors.
604     // debugPutStdErrBitset(__FILE__, __LINE__,
605     //                      m_validationErrors,
606     //                      "m_validationErrors @ fetchFormulaMass");
607 
608     return true;
609   }
610 
611   // Returns false if the formula is not validated and aborts if an
612   // error occurs while the formula was validated.
613   bool
fetchMzRatio(double * mono,double * avg)614   SpectrumCalculationDlg::fetchMzRatio(double *mono, double *avg)
615   {
616     // Check if there are currently errors set either in the formula
617     // or in the ionization rule.
618 
619     // Show what's the value of the errors.
620     // debugPutStdErrBitset(__FILE__, __LINE__,
621     //                      m_validationErrors,
622     //                      "m_validationErrors @ ENTER fetchMzRatio");
623 
624     if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) ==
625        MXP_VALIDATION_FORMULA_ERRORS)
626       {
627         return false;
628       }
629 
630     // The formula data have already been automatically fetched and
631     // set to m_formula when the data in the widgets changed using the
632     // correponding slot.
633 
634     // We can compute the m/z ratio.
635 
636     double monoMass = 0;
637     double avgMass = 0;
638 
639     fetchFormulaMass(&monoMass, &avgMass);
640 
641     // qDebug() << __FILE__ << __LINE__
642     //          << "monoMass:" << monoMass
643     //          << "avgMass:" << avgMass;
644 
645     // Note that the only acceptable value returned here is mono > 0,
646     // because the formula was validated and thus mass calculation
647     // should not fail. Indeed, if there is a failure in the called
648     // function, qFatal is triggered. No need, thus, to check the
649     // status of the call. It cannot fail.
650 
651     // Compute the m/z ratio.
652     monoMass = monoMass / m_charge;
653     avgMass = avgMass / m_charge;
654 
655     // At this point the ionizable has the right mzRatio.
656 
657     if(mono)
658       *mono = monoMass;
659 
660     if(avg)
661       *avg = avgMass;
662 
663     // Show what's the value of the errors.
664     // debugPutStdErrBitset(__FILE__, __LINE__,
665     //                      m_validationErrors,
666     //                      "m_validationErrors @ EXIT fetchMzRatio");
667 
668     return true;
669   }
670 
671   void
massTypeRadioButtonToggled(bool checked)672   SpectrumCalculationDlg::massTypeRadioButtonToggled(bool checked)
673   {
674     Q_UNUSED(checked);
675 
676     m_withMonoMass = m_ui.monoRadioButton->isChecked();
677 
678     // If the mass type is average, then, of course, no isotopic
679     // cluster calculation can be required.
680 
681     if(!m_withMonoMass)
682       m_ui.isotopicClusterCheckBox->setChecked(false);
683   }
684 
685   void
isotopicClusterCheckBoxToggled(bool checked)686   SpectrumCalculationDlg::isotopicClusterCheckBoxToggled(bool checked)
687   {
688     m_withCluster = checked;
689 
690     if(checked)
691       {
692         // If the isotopic cluster is to be computed for each oligomer,
693         // then that means necessarily that the mass is mono!
694 
695         m_withMonoMass = true;
696         m_ui.monoRadioButton->setChecked(true);
697 
698         m_ui.pointNumberSpinBox->setDisabled(false);
699         m_ui.minimumProbabilityLineEdit->setDisabled(false);
700         m_ui.maximumPeaksSpinBox->setDisabled(false);
701       }
702     else
703       {
704         // Not willing to compute isotopic clusters, thus all the
705         // related widgets should be inactive.
706 
707         m_ui.pointNumberSpinBox->setDisabled(true);
708         m_ui.minimumProbabilityLineEdit->setDisabled(true);
709         m_ui.maximumPeaksSpinBox->setDisabled(true);
710       }
711 
712   }
713 
714 
715   void
formulaEdited(const QString & text)716   SpectrumCalculationDlg::formulaEdited(const QString &text)
717   {
718     // We only handle the automatic recalculations on the basis of
719     // this change if we are in a isotopic cluster calculation task.
720     if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
721       return;
722 
723     // Show what's the value of the errors.
724     // debugPutStdErrBitset(__FILE__, __LINE__,
725     //                      m_validationErrors,
726     //                      "m_validationErrors @ ENTER formulaChanged");
727 
728     // qDebug() << __FILE__ << __LINE__
729     //          << "formulaChanged has text:" << text;
730 
731     // New text contains a new formula. Check it.
732 
733     if(text.isEmpty())
734       {
735         // Set the error bit.
736         m_validationErrors |= MXP_VALIDATION_FORMULA_ERRORS;
737 
738         m_ui.monoMzRatioLineEdit->setText("0.000");
739         m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error"));
740 
741         return;
742       }
743 
744     Formula formula(text);
745 
746     // Do not bother storing all the atoms in the formula, this is
747     // only a check (false param below).  m_atomList is a reference to
748     // the atom list of the polymer chemistry definition currently
749     // used in the caller calculator window.
750 
751     if (!formula.validate(m_atomList, false, true))
752       {
753         // Set the error bit.
754         m_validationErrors |= MXP_VALIDATION_FORMULA_ERRORS;
755 
756         m_ui.monoMzRatioLineEdit->setText("0.000");
757         m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error"));
758 
759         return;
760       }
761 
762     // qDebug() << __FILE__ << __LINE__
763     //          << "Formula:" << formula.formula() << "validated." ;
764 
765     // At this point we know we could actually validate the formula,
766     // so do that work with the member formula:
767 
768     // We want to validate the formula and in the mean time construct
769     // the list of all the AtomCount objects(first true), and since
770     // the formula is reused we also ensure that that list is reset
771     // (second true). m_atomList is a reference to the atom list of
772     // the polymer chemistry definition currently used in the caller
773     // calculator window.
774 
775     m_formula.setFormula(text);
776     m_formula.validate(m_atomList, true, true);
777 
778     // Clear the bit as there is no error here.
779     m_validationErrors &= ~MXP_VALIDATION_FORMULA_ERRORS;
780 
781     m_ui.inputDataFeedbackLineEdit->setText(tr("Formula fine"));
782 
783     // qDebug() << __FILE__ << __LINE__
784     //          << "Going to call updateMzRatio.";
785 
786     updateMzRatio();
787     updateIncrement();
788 
789     // Show what's the value of the errors.
790     // debugPutStdErrBitset(__FILE__, __LINE__,
791     //                      m_validationErrors,
792     //                      "m_validationErrors @ EXIT formulaChanged");
793 
794     return;
795   }
796 
797 
798   void
chargeChanged(int value)799   SpectrumCalculationDlg::chargeChanged(int value)
800   {
801     // We only handle the automatic recalculations on the basis of
802     // this change if we are in a isotopic cluster calculation task.
803     if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
804       return;
805 
806     //The charge has changed, we should compute the mzRatio.
807     m_charge = m_ui.chargeSpinBox->value();
808 
809     updateMzRatio();
810     updateIncrement();
811   }
812 
813 
814   void
pointsChanged(int value)815   SpectrumCalculationDlg::pointsChanged(int value)
816   {
817     // We only handle the automatic recalculations on the basis of
818     // this change if we are in a isotopic cluster calculation task.
819     if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
820       return;
821 
822     //The points have changed, we should compute the increment.
823     m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
824 
825     updateIncrement();
826   }
827 
828 
829   void
resolutionChanged(int value)830   SpectrumCalculationDlg::resolutionChanged(int value)
831   {
832     // If the value changed, that means that the user wants the
833     // resolution to be taken into account for calculation of the peak
834     // width, and not the FWHM. Only if resolution is set to 0, FWHM
835     // will be the value taken into account.
836 
837     // Show what's the value of the errors.
838 
839     // debugPutStdErrBitset(__FILE__, __LINE__,
840     //                      m_validationErrors,
841     //                      "m_validationErrors @ ENTER resolutionChanged");
842 
843     if(value <= 0)
844       {
845         // Tell the user to set a valid FWHM value, then.
846         m_ui.inputDataFeedbackLineEdit->setText
847           (tr("Set a valid FWHM value"));
848 
849         m_ui.resolutionSpinBox->setValue(0);
850 
851         // Set the error bit so that we can let the increment
852         // calculation know that we can not perform that computation
853         // using FWHM.
854 
855         m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS;
856 
857         return;
858       }
859 
860     m_resolution = value;
861 
862     m_ui.inputDataFeedbackLineEdit->setText
863       (tr("Will use the resolution"));
864 
865     // Clear the bit as there is no error here.
866     m_validationErrors &= ~MXP_VALIDATION_RESOLUTION_ERRORS;
867 
868     // We only handle the automatic recalculations on the basis of
869     // this change if we are in a isotopic cluster calculation task
870     // because only in that case do we know before starting the
871     // calculation what will be the mz.
872     if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
873       return;
874 
875     // We can try to compute the fwhm value corresponding to the
876     // current mono m/z value and the resolution.
877 
878     double mono;
879     double avg;
880 
881     if(fetchFormulaMass(&mono, &avg))
882       {
883         Application *application = static_cast<Application *>(qApp);
884 
885         QString value = application->locale().
886           toString(mono / m_resolution, 'f', MXP_OLIGOMER_DEC_PLACES);
887 
888         // Note that the setText call below will not trigger automatic
889         // fwhm setting because the lineEdit widget is connected to a
890         // textEdited signal, not a textChanged signal.
891 
892         m_ui.fwhmLineEdit->setText(value);
893       }
894 
895     // Now, because we want the resolution to be the governing
896     // parameter when validation will occur, we do this:
897 
898     m_config.setFwhm(0);
899     m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;
900 
901     // Show what's the value of the errors.
902 
903     // debugPutStdErrBitset(__FILE__, __LINE__,
904     //                      m_validationErrors,
905     //                      "m_validationErrors @ EXIT resolutionChanged");
906 
907     updateIncrement();
908 
909     return;
910   }
911 
912 
913   void
fwhmEdited(const QString & text)914   SpectrumCalculationDlg::fwhmEdited(const QString &text)
915   {
916     // Show what's the value of the errors.
917     // debugPutStdErrBitset(__FILE__, __LINE__,
918     //                      m_validationErrors,
919     //                      "m_validationErrors @ ENTER fwhmEdited");
920 
921     // If the text was edited in the line edit, that means that the
922     // user wants the FWHM to be taken into account for calculation of
923     // the peak width, and not the resolution. Only if FWHM is set to
924     // 0, resolution will be the value taken into account.
925 
926     QString txt = m_ui.fwhmLineEdit->text();
927 
928     bool ok = false;
929 
930     double fwhmValue = txt.toDouble(&ok);
931 
932     if(!fwhmValue && !ok)
933       {
934         // Set the error bit.
935         m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;
936 
937         m_ui.inputDataFeedbackLineEdit->setText
938           (tr("Fix the FWHM value, please"));
939 
940         return;
941       }
942 
943     // But the value might be faithfully 0, if the user is telling us
944     // that she wants to take the resolution into account and not the
945     // FWHM.
946 
947     if(!fwhmValue)
948       {
949         m_ui.inputDataFeedbackLineEdit->setText
950           (tr("Set a valid resolution value"));
951 
952         // Set the error bit to let the increment calculation know
953         // that it cannot base the calculation on the FWHM value.
954 
955         m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;
956 
957         return;
958       }
959 
960     // At this point we know that fwhmValue contains a proper value.
961 
962     m_ui.inputDataFeedbackLineEdit->setText
963       (tr("Will use the FWHM"));
964 
965     m_validationErrors &= ~MXP_VALIDATION_FWHM_ERRORS;
966 
967     m_config.setFwhm(fwhmValue);
968 
969     // Set the resolution spinbox to 0 and set the error bit
970     // associated with it to let the increment calculation know that
971     // it cannot be based on that value.
972 
973     m_resolution = 0;
974     m_ui.resolutionSpinBox->setValue(0);
975     m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS;
976 
977     // Show what's the value of the errors.
978     // debugPutStdErrBitset(__FILE__, __LINE__,
979     //                      m_validationErrors,
980     //                      "m_validationErrors @ EXIT fwhmEdited");
981 
982     // We only handle the automatic recalculation of the increment on
983     // the basis of this change if we are in a isotopic cluster
984     // calculation task because only in this case do we know the mono
985     // mz value.
986     if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
987       return;
988 
989     updateIncrement();
990 
991     return;
992   }
993 
994 
995   bool
updateMzRatio()996   SpectrumCalculationDlg::updateMzRatio()
997   {
998     // Show what's the value of the errors.
999     // debugPutStdErrBitset(__FILE__, __LINE__,
1000     //                      m_validationErrors,
1001     //                      "m_validationErrors @ ENTER updateMzRatio");
1002 
1003     // Set the mz ratio to our member data variable.
1004     bool res = fetchMzRatio(&m_mono, &m_avg);
1005 
1006     // If res is falsed, that means that the formula is not validated
1007     // at present. Silently return.
1008     if(!res)
1009       {
1010         QMessageBox::warning(0,
1011                              tr("massXpert - Spectrum Calculator"),
1012                              tr("Failed to fetch the mz ratio."),
1013                              QMessageBox::Ok);
1014 
1015       return false;
1016       }
1017 
1018     Application *application = static_cast<Application *>(qApp);
1019 
1020     QString mzRatio = application->locale().
1021       toString(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES);
1022 
1023     m_ui.monoMzRatioLineEdit->setText(mzRatio);
1024 
1025     // Show what's the value of the errors.
1026     // debugPutStdErrBitset(__FILE__, __LINE__,
1027     //                      m_validationErrors,
1028     //                      "m_validationErrors @ EXIT updateMzRatio");
1029 
1030     return true;
1031   }
1032 
1033 
1034   bool
updateIncrement()1035   SpectrumCalculationDlg::updateIncrement()
1036   {
1037     // We can only calculate the increment if we have the number of
1038     // points to construct the curve and either the resolution and the
1039     // mzRatio or the FWHM.
1040 
1041     // If we know the FWHM, then, fine, we can compute the increment
1042     // right away.
1043 
1044     double increment = 0;
1045 
1046     if((m_validationErrors & MXP_VALIDATION_FWHM_ERRORS) !=
1047        MXP_VALIDATION_FWHM_ERRORS)
1048       {
1049         increment =
1050           (MXP_FWHM_PEAK_SPAN_FACTOR * m_config.fwhm()) /
1051           m_config.pointNumber();
1052       }
1053     else if((m_validationErrors & MXP_VALIDATION_RESOLUTION_ERRORS) !=
1054        MXP_VALIDATION_RESOLUTION_ERRORS)
1055       {
1056         // If we know the resolution and we can get the mzRatio, we'll
1057         // be able to first compute the FWHM as (mzRatio / FWHM). From
1058         // there, we'll be able to compute the increment.
1059 
1060         double mono = 0;
1061         double avg = 0;
1062 
1063         // We compute the increment by using the mass of the formula,
1064         // not its mzRatio.
1065         if(fetchFormulaMass(&mono, &avg))
1066           {
1067             double fwhm = mono / m_resolution;
1068 
1069             increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) /
1070               m_config.pointNumber();
1071 
1072             // Because we'll need more points when the charge
1073             // increases... Formally, we have to check that charge is
1074             // not 0, but we should not be required to do it because
1075             // the charge can never be 0 (gui-limited values for the
1076             // charge spin box).
1077             if(m_charge)
1078               increment = increment / m_charge;
1079           }
1080       }
1081 
1082     if(increment)
1083       {
1084         m_config.setIncrement(increment);
1085 
1086         QString text;
1087         text.setNum(m_config.increment());
1088 
1089         m_ui.incrementLabel->setText(text);
1090 
1091         return true;
1092       }
1093     else
1094       m_ui.incrementLabel->setText("0");
1095 
1096     return false;
1097   }
1098 
1099 
1100   void
execute()1101   SpectrumCalculationDlg::execute()
1102   {
1103     // We might be called to perform two different things:
1104     //
1105     // 1. Calculate a single istotopic cluster pattern.
1106     //
1107     // 2. Calculate a spectrum for a whole set of oligomers.
1108 
1109     if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
1110       return executeCluster();
1111     else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM)
1112       return executeSpectrum();
1113   }
1114 
1115 
1116   void
executeCluster()1117   SpectrumCalculationDlg::executeCluster()
1118   {
1119     Application *application = static_cast<Application *>(qApp);
1120 
1121     // Clear the textEdit widget so that we can start out-putting text
1122     // into it, note that fetchValidateInputData() puts text in it.
1123     m_ui.resultPlainTextEdit->clear();
1124 
1125     // Clear the results string for the creation of the calculator
1126     // below to receive an empty string.
1127     m_resultsString.clear();
1128 
1129     // We are asked to launch the calculation. But we ought to make
1130     // sure that all the required data are set fine. The call below
1131     // will fill-in the m_config data.
1132 
1133     if(!fetchValidateInputData())
1134       {
1135         QMessageBox::warning(0,
1136                              tr("massXpert - Spectrum Calculator"),
1137                              tr("Please fix the input data first."),
1138                              QMessageBox::Ok);
1139         return;
1140       }
1141 
1142     // Before starting the calculations, swith to the log tab so that
1143     // the user can monitor what's going on.
1144 
1145     m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_LOG);
1146 
1147     // Send to both the log and result text edit widget the data we
1148     // are going to use for the calculation.
1149 
1150     QString *text = new QString();
1151 
1152     bool isUsingLocale = m_ui.localeCheckBox->checkState() == Qt::Checked ?
1153       true : false;
1154 
1155     if (isUsingLocale)
1156       text->append(tr("FWHM: %1 \t Max. peaks %2 "
1157                       "\t Min. probability: %3\n\n")
1158                    .arg(application->locale().toString(m_config.fwhm(),
1159                                                        'f', 4))
1160                    .arg(application->locale().toString(m_maximumPeaks))
1161                    .arg(application->locale().toString(m_minimumProbability,
1162                                                        'f', 15)));
1163     else
1164       text->append(tr("FWHM: %1 \t Max. peaks %2 "
1165                       "\t Min. probability: %3\n\n")
1166                    .arg(QString().setNum(m_config.fwhm(),
1167                                          'f', 4))
1168                    .arg(QString().setNum(m_maximumPeaks))
1169                    .arg(QString().setNum(m_minimumProbability,
1170                                          'f', 15)));
1171 
1172     m_ui.logPlainTextEdit->appendPlainText(*text);
1173     m_ui.resultPlainTextEdit->appendPlainText(*text);
1174 
1175     // Free the string, we will reuse it.
1176     delete text;
1177 
1178     // We will allocate a IsotopicPatternCalculator instance and let
1179     // it do the work.
1180 
1181     IsotopicPatternCalculator *calculator =
1182       new IsotopicPatternCalculator(m_formula, m_charge,
1183                                     m_maximumPeaks, m_minimumProbability,
1184                                     m_atomList, m_config);
1185 
1186     connect(calculator,
1187             SIGNAL(isotopicCalculationProgressValueChanged(int)),
1188             this,
1189             SLOT(spectrumCalculationProgressValueChanged(int)));
1190 
1191     connect(calculator,
1192             SIGNAL(isotopicCalculationMessageChanged(QString)),
1193             this,
1194             SLOT(spectrumCalculationMessageChanged(QString)));
1195 
1196     connect(this,
1197             SIGNAL(spectrumCalculationAborted()),
1198             calculator,
1199             SLOT(spectrumCalculationAborted()));
1200 
1201 
1202     // Perform the calculation...
1203     calculator->sumPeakShapes();
1204 
1205     // Get the list of peaks as "1251.14 0.025" pairs.
1206     text = calculator->peakCentroidListAsString();
1207 
1208     // Now prepend a header:
1209 
1210     text->prepend("\nList of peak centroids:\n");
1211 
1212     // Display the data to the user.
1213     m_ui.resultPlainTextEdit->appendPlainText(*text);
1214 
1215     // We want to get the calculation configuration.
1216 
1217     // Let's get the detailed calculation configuration
1218     // string, so that we can append it to the textEdit
1219     // widget.
1220     text->clear();
1221     text->append(m_config.config());
1222     m_ui.resultPlainTextEdit->appendPlainText(*text);
1223 
1224     // Finally delete the text.
1225     delete text;
1226 
1227     // Finally, if the user had asked for the data to be sent to a
1228     // file... or to a string... Get a reference to the list of
1229     // QPointF instances in the calculator and send them either to the
1230     // file or to the text edit widget.
1231 
1232     const QList<QPointF *> &spectrumPoints = calculator->pointList();
1233     QString dataString;
1234 
1235     if(!m_filePath.isEmpty())
1236       {
1237         // Send all the data (that is, the points located in
1238         // QList<QPointF *>m_patternPointList) to the output file.
1239 
1240         QFile file(m_filePath);
1241 
1242         if(!file.open(QFile::WriteOnly | QFile::Truncate))
1243           {
1244             QMessageBox::warning(this,
1245                                  tr("massXpert - Spectrum Calculator"),
1246                                  tr("Failed to export the isotopic pattern "
1247                                     "data to file."),
1248                                  QMessageBox::Ok);
1249             return;
1250           }
1251 
1252         QTextStream stream(&file);
1253 
1254         for(int iter = 0, size = spectrumPoints.size();
1255             iter < size ; ++iter)
1256           {
1257             QPointF *point = spectrumPoints.at(iter);
1258 
1259             dataString = QString().setNum(point->x(), 'f', 10);
1260             dataString += " ";
1261             dataString += QString().setNum(point->y(), 'f', 10);
1262             dataString += "\n";
1263             stream << dataString;
1264             // qDebug() << __FILE__ << __LINE__ << dataString;
1265           }
1266 
1267         stream.flush();
1268         file.close();
1269 
1270         // At this moment, tell the user in the log that the data have
1271         // been written to the given file.
1272 
1273         QString message;
1274 
1275         message += tr("\n\nData have been written to file %1\n\n")
1276           .arg(m_filePath);
1277 
1278         message += tr("\n\nAlso check the results tab\n\n");
1279 
1280         m_ui.logPlainTextEdit->appendPlainText(message);
1281 
1282         return;
1283       }
1284     else
1285       {
1286         for(int iter = 0, size = spectrumPoints.size();
1287             iter < size ; ++iter)
1288           {
1289             QPointF *point = spectrumPoints.at(iter);
1290 
1291             dataString += QString().setNum(point->x(), 'f', 10);
1292             dataString += " ";
1293             dataString += QString().setNum(point->y(), 'f', 10);
1294             dataString += "\n";
1295           }
1296 
1297         m_ui.resultPlainTextEdit->appendPlainText(dataString);
1298 
1299         // At this moment, tell the user in the log that the data have
1300         // been written to result tab.
1301 
1302         QString message;
1303 
1304         message += tr("\n\nData have been written to the results tab\n\n");
1305 
1306         m_ui.logPlainTextEdit->appendPlainText(message);
1307 
1308       }
1309 
1310     delete calculator;
1311   }
1312 
1313 
1314   void
executeSpectrum()1315   SpectrumCalculationDlg::executeSpectrum()
1316   {
1317     // Clear the textEdit widget so that we can start out-putting text
1318     // into it, note that fetchValidateInputData() puts text in it.
1319     m_ui.resultPlainTextEdit->clear();
1320     m_ui.logPlainTextEdit->clear();
1321 
1322     // Verify that there are oligomers in the list.
1323 
1324     if(!mp_oligomerList)
1325       return;
1326 
1327     if(mp_oligomerList->isEmpty())
1328       return;
1329 
1330     if(!fetchValidateInputData())
1331       {
1332         QMessageBox::warning(0,
1333                              tr("massXpert - Spectrum Calculator"),
1334                              tr("Please fix the input data first."),
1335                              QMessageBox::Ok);
1336         return;
1337       }
1338 
1339     // List of lists of QPointF, that is list of spectra.
1340     QList<QList<QPointF *> *> spectrumList;
1341 
1342     // Each time a new spectrum is calculated, store the corresponding
1343     // increment we'll need later to computed the summed final
1344     // spectrum.
1345     QHash<QList<QPointF *> *, double> incrementHash;
1346 
1347     // Before starting the calculations, swith to the log tab so that
1348     // the user can monitor what's going on.
1349 
1350     m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_LOG);
1351 
1352     if(m_withCluster)
1353       executeSpectrumWithCluster(&spectrumList, &incrementHash);
1354     else
1355       executeSpectrumWithoutCluster(&spectrumList, &incrementHash);
1356 
1357 
1358   // At this point, with one method or the other, we should have as
1359   // many allocated QList<QPointF *> * instances (that is, spectrum
1360   // instances) as there are oligomers. Make that sanity check.
1361 
1362   if(spectrumList.isEmpty())
1363     return;
1364 
1365   if(spectrumList.size() < mp_oligomerList->size())
1366     qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
1367   if(spectrumList.size() < incrementHash.size())
1368     qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
1369 
1370   // qDebug() << __FILE__ << __LINE__
1371   //          << "Done generating the unitary spectra for each oligomer.";
1372 
1373   // Before computing the sum of all spectra to yield the last
1374   // spectrum, we have to perform the ordering of the spectra so
1375   // that the m/z regions covered by each are in an ascending order.
1376 
1377   // We want to sort the spectra in ascending order. There are two
1378   // concepts:
1379 
1380   // 1. one spectrum is greater than the other if its first point is
1381   //  greater than the other's.
1382 
1383   // 2. one spectrum is greater than the other, even if their first
1384   // points are equal, if its last point is greater then the
1385   // other's. That means two spectra starting at the same point are
1386   // different if they end at different points and the longest
1387   // spectrum sorts after the shortest.
1388 
1389   // Only sort if there are at least two spectra !
1390 
1391   m_ui.logPlainTextEdit->
1392     appendPlainText(tr("Sorting all oligomer-related sub-spectra..."));
1393   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1394   qApp->processEvents();
1395 
1396   if(spectrumList.size() >= 2)
1397     {
1398       bool sorted = false;
1399 
1400       while(!sorted)
1401         {
1402           bool changed = false;
1403 
1404           for(int iter = 0; iter < spectrumList.size() - 1; ++iter)
1405             {
1406               QList<QPointF *> *thisSpectrum = spectrumList.at(iter);
1407               QList<QPointF *> *nextSpectrum = spectrumList.at(iter + 1);
1408 
1409               QPointF *thisFirstPoint = thisSpectrum->first();
1410               QPointF *thisLastPoint = thisSpectrum->last();
1411 
1412               QPointF *nextFirstPoint = nextSpectrum->first();
1413               QPointF *nextLastPoint = nextSpectrum->last();
1414 
1415               if(thisFirstPoint->x() > nextFirstPoint->x())
1416                 {
1417                   // Reverse the spectra in the list, as this spectrum
1418                   // is farther away in the mz axis than is the next
1419                   // spectrum.
1420 
1421                   int thisIdx = iter;
1422                   int nextIdx = iter + 1;
1423 
1424                   spectrumList.removeAt(thisIdx);
1425                   spectrumList.insert(nextIdx, thisSpectrum);
1426 
1427                   changed = true;
1428 
1429                   continue;
1430                 }
1431 
1432               if(thisFirstPoint->x() == nextFirstPoint->x() &&
1433                  thisLastPoint->x() > nextLastPoint->x())
1434                 {
1435                   // Reverse the spectra in the list, as this spectrum
1436                   // is longer than is the next spectrum.
1437 
1438                   int thisIdx = iter;
1439                   int nextIdx = iter + 1;
1440 
1441                   spectrumList.removeAt(thisIdx);
1442                   spectrumList.insert(nextIdx, thisSpectrum);
1443 
1444                   changed = true;
1445 
1446                   continue;
1447                 }
1448             }
1449           // End of
1450           // for(int iter = 0; iter < spectrumList.size() + 1; ++iter)
1451           // That is done making one iteration in the spectrum list.
1452 
1453           // If changed is true, then that means that we still have at
1454           // least one round to do to finalize the sorting.
1455 
1456           // If changed is false, then the spectrum list is sorted !
1457 
1458           sorted = !changed;
1459         }
1460       // End of
1461       // while(!sorted)
1462       //
1463       // That is, finished sorting of the spectrum list.
1464     }
1465   // End of
1466   // if(spectrumList.size() >= 2)
1467 
1468   m_ui.logPlainTextEdit->textCursor().insertText(" Done.\n");
1469   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1470   qApp->processEvents();
1471 
1472   // for(int iter = 0; iter < spectrumList.size(); ++iter)
1473   //   {
1474   //     QList<QPointF *> *spectrum = spectrumList.at(iter);
1475 
1476   //     QPointF *firstPoint = spectrum->first();
1477   //     QPointF *lastPoint = spectrum->last();
1478 
1479   //     qDebug() << __FILE__ << __LINE__
1480   //              << "List of sorted spectra:\n";
1481 
1482   //     qDebug()
1483   //       << "Spectrum index:" << iter
1484   //       << "spectrum: " << spectrum
1485   //       << "first point:" << firstPoint->x()
1486   //       << "last point:" << lastPoint->x();
1487   //   }
1488 
1489   m_ui.logPlainTextEdit->
1490     appendPlainText(tr("Computing sets of overlapping/non-overlapping "
1491                        "sub-spectra..."));
1492   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1493   qApp->processEvents();
1494 
1495   // Compute sets of overlapping spectra or of a single spectra
1496   // non-overlapping spectrum, without loosing the sorting made
1497   // before. One set of spectra is thus a list of lists of QPointF
1498   // instances.
1499   // To make things clear:
1500   //
1501   // A spectrum: QList<QPointF *>
1502   // A set of spectra: QList<QList<QPointF *> *>
1503   // A list of set of spectra: Qlist<QList<QList<QPointF *> *> *>
1504   QList<QList<QList<QPointF *> *> *>spectrumSetList;
1505 
1506   // Seed the iterations in the spectrum list at 0
1507 
1508   // During one iteration, all the members of the spectrum list are
1509   // tested to check if there is an overlap between these iterated
1510   // members and the seeded spectrum. Each time an overlap is found,
1511   // the new range border points are stored for the next test.
1512 
1513   // qDebug() << __FILE__ << __LINE__
1514   //          << "Before making spectral sets"
1515   //          << "spectrumList size:" << spectrumList.size();
1516 
1517   //  for(int iter = 0 ; iter < spectrumList.size() - 1; ++iter)
1518   while(!spectrumList.isEmpty())
1519     {
1520       QList<QPointF *> *thisSpectrum = spectrumList.first();
1521 
1522       // Allocate a new set of spectra that will hold all the spectra
1523       // that are overlapping with thisSpectrum.
1524 
1525       QList<QList<QPointF *> *> *newSpectrumSet =
1526         new QList<QList<QPointF *> *>;
1527 
1528       // Transer right away thisSpectrum to the new set of overlapping
1529       // spectra (if it remains alone because no spectrum overlaps
1530       // with it, then fine.
1531 
1532       spectrumList.removeOne(thisSpectrum);
1533       newSpectrumSet->append(thisSpectrum);
1534       spectrumSetList.append(newSpectrumSet);
1535 
1536       // If there are overlaps, we'll have to document them using new
1537       // 'borders' of the current spectrum set. We seed these border's
1538       // points with thisSpectrum's first and last points.
1539 
1540       // double newRangeFirstPointX = thisSpectrum->first()->x();
1541       double newRangeLastPointX = thisSpectrum->last()->x();
1542 
1543       // qDebug() << __FILE__ << __LINE__
1544       //          << "\nNew set seeded with thisSpectrum:" << thisSpectrum
1545       //          << "\n\t with last point x:" << thisSpectrum->last()->x()
1546       //          << "\n\t and newRangeLastPointX:" << newRangeLastPointX;
1547 
1548       // Now iterate in each remaining spectrum and check the overlap
1549       // with thisSpectrum. Remark that we can start at jter = 0
1550       // because we already removed the seeding spectrum for the list.
1551 
1552       for(int jter = 0 ; jter < spectrumList.size(); ++jter)
1553         {
1554           QList<QPointF *> *nextSpectrum = spectrumList.at(jter);
1555 
1556           double nextFirstPointX = nextSpectrum->first()->x();
1557           // double nextLastPointX = nextSpectrum->last()->x();
1558 
1559           // Check if there is an overlap of thisSpectrum with
1560           // nextSpectrum. Because we have sorted the spectra in a
1561           // previous step, thisSpectrum is necessarily <=
1562           // nextSpectrum. So, make the tests accordingly.
1563 
1564           if(nextFirstPointX < newRangeLastPointX)
1565             {
1566               // There is an overlap, as next spectrum has its first
1567               // point located inside the first spectrum.
1568 
1569               // That means that we have to move nextSpectrum to the
1570               // set of overlapping spectra.
1571 
1572               spectrumList.removeOne(nextSpectrum);
1573               newSpectrumSet->append(nextSpectrum);
1574 
1575               // Update the new right border of the range.
1576 
1577               newRangeLastPointX = nextSpectrum->last()->x();
1578 
1579               // We removed a spectrum from the list, thus decrement
1580               // jter so as to start a new loop at what was the next
1581               // spectrum in the list prior to the removal.
1582               --jter;
1583             }
1584           else
1585             {
1586               // There is no overlap between the last processed
1587               // spectrum, thus we break, which will "close" this
1588               // current set and start a new one.
1589 
1590               break;
1591             }
1592         }
1593       // End of
1594       // for(int jter = 0 ; jter < spectrumList.size(); ++jter)
1595     }
1596   // End of
1597   // for(int iter = 0 ; iter < spectrumList.size() + 1; ++iter)
1598 
1599   // If there was a last item in the spectrumList, it is necessarily a
1600   // non-overlapping item, thus allocate a last spectrumSet and
1601   // append the item to it.
1602 
1603   if(spectrumList.size())
1604     qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1605 
1606   m_ui.logPlainTextEdit->textCursor().insertText(" Done.\n\n");
1607   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1608   qApp->processEvents();
1609 
1610   // At this point we have a list of spectrum sets which each might
1611   // contain one or more spectra. When more than one spectra are
1612   // contained in a spectrum set, then that means that they overlap
1613   // with each other.
1614 
1615   m_ui.logPlainTextEdit->
1616     textCursor().insertText(tr("Creating final spectrum... "));
1617   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1618   qApp->processEvents();
1619 
1620   // Iterate in each spectrum set and see how to create the final
1621   // spectrum.
1622 
1623   QList<QPointF *> finalSpectrum;
1624 
1625   double pointCount = 0;
1626 
1627   for(int iter = 0; iter < spectrumSetList.size(); ++iter)
1628     {
1629       QList<QList<QPointF *> *> *set = spectrumSetList.at(iter);
1630 
1631       // Only iterate in the spectrum set if it contains overlapping
1632       // spectra, that is more than one single spectrum. Otherwise
1633       // just dump all the points of the non-overlapping spectrum into
1634       // the final spectrum.
1635 
1636       if(set->size() == 1)
1637         {
1638           // Only one non-overlapping spectrum in the set, dump all
1639           // its points into the final spectrum.
1640 
1641           while(!set->first()->isEmpty())
1642             {
1643               finalSpectrum.append(set->first()->takeFirst());
1644 
1645               ++pointCount;
1646               if(pointCount >= 1000)
1647                 {
1648                   m_ui.logPlainTextEdit->textCursor().insertText(". ");
1649                   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1650                   qApp->processEvents();
1651 
1652                   pointCount = 0;
1653 
1654                   qApp->processEvents();
1655                 }
1656             }
1657 
1658           continue;
1659         }
1660 
1661       // There are more than one spectrum in the set, that is these
1662       // spectra are overlapping. We have to ensure we sum all the
1663       // spectra one with the other. minMz is the first point of the
1664       // first spectrum of the set, while maxMz is the last point of
1665       // the last spectrum of the set.
1666 
1667       double minMz = set->first()->first()->x();
1668       double maxMz = set->last()->last()->x();
1669 
1670       double curMz = minMz;
1671 
1672       while(curMz <= maxMz)
1673         {
1674           // qDebug() << __FILE__ << __LINE__
1675           //          << "Handling mz value:" << curMz;
1676 
1677           // curMz now has a mz value for which we have to sum all the
1678           // intensities in each pointList.
1679 
1680           double summedIntensity = 0;
1681 
1682           // Iterate in each spectrum of the set of spectra and seek
1683           // the intensity at curMz. when found, add that intensity to
1684           // summedIntensity.
1685 
1686           double increment = 0;
1687 
1688           for(int jter = 0, jSize = set->size(); jter < jSize; ++jter)
1689             {
1690               QList<QPointF *> *curSpectrum = set->at(jter);
1691 
1692               increment = incrementHash.value(curSpectrum);
1693 
1694               // We are iterating in the list of points of a
1695               // spectrum. What is the intensity of the point at
1696               // x=curMz ? We provide a tolerance for x corresponding
1697               // to (increment / 2).
1698 
1699               for(int kter = 0,
1700                     kSize = curSpectrum->size(); kter < kSize; ++kter)
1701                 {
1702                   QPointF *point = curSpectrum->at(kter);
1703 
1704                   double mz = point->x();
1705 
1706                   if(mz <= (curMz + increment / 2) &&
1707                      mz >= (curMz - increment / 2))
1708                     {
1709                       summedIntensity += point->y();
1710 
1711                       break;
1712                     }
1713                   // Else, go on to next point. If not point is
1714                   // found at all, then, ok, we do not increment the
1715                   // summedIntensity variable.
1716                 }
1717               // At this point we have finished iterating in a given
1718               // spectrum (that is a point list).
1719             }
1720           // We have finished iterating in all the spectra of the set
1721           // for the current mz. Make a point (curMz,summedIntensity).
1722 
1723           QPointF *newPoint = new QPointF(curMz, summedIntensity);
1724 
1725           finalSpectrum.append(newPoint);
1726 
1727           ++pointCount;
1728           if(pointCount >= 1000)
1729             {
1730               m_ui.logPlainTextEdit->textCursor().insertText(". ");
1731               // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1732               qApp->processEvents();
1733 
1734               pointCount = 0;
1735 
1736               if(m_aborted)
1737                 break;
1738             }
1739 
1740           // qDebug() << __FILE__ << __LINE__
1741           //          << "Added new point:"
1742           //          << "x:" << newPoint->x() << "," << "y:" << newPoint->y()
1743           //          << "\n";
1744 
1745           curMz += increment;
1746         }
1747       // End of while(curMz <= maxMz). That is, we have finished
1748       // creating (x,y) pairs for the whole x axis, that is the mz
1749       // ratio axis.
1750     }
1751   // End of
1752   // for(int iter = 0; iter < spectrumSetList.size(); ++iter)
1753   //
1754   // We have finished iterating in the set of spectra, that is we have
1755   // finished going through all the spectra, that is, we have finished
1756   // doing the final spectrum.
1757 
1758    // qDebug() << __FILE__ << __LINE__
1759    //          << "Ended the creation of the final spectrum.";
1760 
1761   m_ui.logPlainTextEdit->
1762     textCursor().insertText("\n\nDone creating the spectrum.\n\n");
1763   // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1764   qApp->processEvents();
1765 
1766    // Finally, if the user had asked for the data to be sent to a
1767    // file... or to a string... Send the spectrum data either to the
1768    // file or to the text edit widget.
1769 
1770    QString dataString;
1771 
1772    if(!m_filePath.isEmpty())
1773      {
1774        // Send all the data (that is, the points located in
1775        // QList<QPointF *>m_patternPointList) to the output file.
1776 
1777        QFile file(m_filePath);
1778 
1779        if(!file.open(QFile::WriteOnly | QFile::Truncate))
1780          {
1781            QMessageBox::warning(this,
1782                                 tr("massXpert - Spectrum Calculator"),
1783                                 tr("Failed to export the isotopic pattern "
1784                                    "data to file."),
1785                                 QMessageBox::Ok);
1786            return;
1787          }
1788 
1789        QTextStream stream(&file);
1790 
1791        m_ui.logPlainTextEdit->
1792          textCursor().insertText("Writing data to file... ");
1793        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1794        qApp->processEvents();
1795 
1796        for(int iter = 0, size = finalSpectrum.size();
1797            iter < size ; ++iter)
1798          {
1799            QPointF *point = finalSpectrum.at(iter);
1800 
1801            dataString += QString().setNum(point->x(), 'f', 10);
1802            dataString += " ";
1803            dataString += QString().setNum(point->y(), 'f', 10);
1804            dataString += "\n";
1805 
1806            // Stream the data by packets of 4096 or less characters.
1807            if(dataString.size() > 4096)
1808              {
1809                stream << dataString;
1810                dataString.clear();
1811 
1812                m_ui.logPlainTextEdit->textCursor().insertText(". ");
1813                // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1814                qApp->processEvents();
1815 
1816                if(m_aborted)
1817                  break;
1818              }
1819            // qDebug() << __FILE__ << __LINE__ << dataString;
1820          }
1821 
1822        // One last time:
1823        stream << dataString;
1824        dataString.clear();
1825 
1826        // And finally:
1827        stream.flush();
1828        file.close();
1829 
1830        m_ui.logPlainTextEdit->
1831          textCursor().insertText("\n\nDone writing "
1832                                  "data to file.\n\n");
1833        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1834        qApp->processEvents();
1835 
1836        return;
1837      }
1838    else
1839      {
1840        m_ui.logPlainTextEdit->
1841          textCursor().insertText("Writing data to results tab... ");
1842        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1843        qApp->processEvents();
1844 
1845        for(int iter = 0, size = finalSpectrum.size();
1846            iter < size ; ++iter)
1847          {
1848            QPointF *point = finalSpectrum.at(iter);
1849 
1850            dataString += QString().setNum(point->x(), 'f', 10);
1851            dataString += " ";
1852            dataString += QString().setNum(point->y(), 'f', 10);
1853            dataString += "\n";
1854 
1855            // Append the data by packets of 4096 or less characters.
1856            if(dataString.size() > 4096)
1857              {
1858                m_ui.resultPlainTextEdit->appendPlainText(dataString);
1859                dataString.clear();
1860 
1861                m_ui.logPlainTextEdit->textCursor().insertText(". ");
1862                // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1863                qApp->processEvents();
1864 
1865                if(m_aborted)
1866                  break;
1867              }
1868 
1869          }
1870 
1871        // One last time:
1872        m_ui.resultPlainTextEdit->appendPlainText(dataString);
1873        dataString.clear();
1874 
1875        m_ui.logPlainTextEdit->
1876          textCursor().insertText("\n\nDone writing "
1877                                  "data to results tab.\n\n");
1878        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1879        qApp->processEvents();
1880      }
1881 
1882 
1883     // Finally, we have a bunch of data to free.
1884 
1885     QPointF *point = 0;
1886 
1887     // Free all the spectrum sets along with all the QPointF instances
1888     // contained in their spectra.
1889     while(!spectrumSetList.isEmpty())
1890       {
1891         QList<QList<QPointF *> *> *spectrumList = spectrumSetList.takeFirst();
1892 
1893         for(int iter = 0, size = spectrumList->size(); iter < size; ++iter)
1894           {
1895             QList<QPointF *> *curSpectrum = spectrumList->at(iter);
1896 
1897             foreach(point, *curSpectrum)
1898               delete point;
1899 
1900             curSpectrum->clear();
1901 
1902             delete curSpectrum;
1903           }
1904 
1905         delete spectrumList;
1906       }
1907 
1908     // qDebug() << __FILE__ << __LINE__
1909     //          << "Done freeing up material.";
1910 
1911     foreach(point, finalSpectrum)
1912       {
1913         // qDebug() << point->x() << " " << point->y();
1914 
1915         delete point;
1916       }
1917 
1918     finalSpectrum.clear();
1919   }
1920 
1921 
1922   void
executeSpectrumWithCluster(QList<QList<QPointF * > * > * spectrumList,QHash<QList<QPointF * > *,double> * incrementHash)1923   SpectrumCalculationDlg::executeSpectrumWithCluster
1924   (QList<QList<QPointF *> *> *spectrumList,
1925    QHash<QList<QPointF *> *, double> *incrementHash)
1926   {
1927     if(!spectrumList)
1928       qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
1929 
1930     if(!incrementHash)
1931       qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
1932 
1933     m_ui.logPlainTextEdit->
1934       textCursor().insertText(tr("Simulating a spectrum "
1935                                  "with calculation of an isotopic "
1936                                  "cluster for each oligomer.\n\n"));
1937 
1938     m_ui.logPlainTextEdit->
1939       textCursor().insertText(tr("There are %1 oligomers\n"
1940                                  "Calculating sub-spectrum "
1941                                  "for each\n\n")
1942                               .arg(mp_oligomerList->size()));
1943     // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1944     qApp->processEvents();
1945 
1946     for(int iter = 0,
1947           size = mp_oligomerList->size(); iter < size; ++iter)
1948       {
1949         Oligomer *oligomer = mp_oligomerList->at(iter);
1950 
1951         // 1. Does the user want a isotopic cluster calculation for
1952         // each oligomer in the list? If so we have to manage a list
1953         // of IsotopicPatternCalculator instances.
1954 
1955         // Since we have to perform an isotopic cluster
1956         // calculation, retrieve the elemental composition.
1957         StringProp *prop =
1958           static_cast<StringProp *>(oligomer->
1959                                     prop("ELEMENTAL_COMPOSITION"));
1960 
1961         if(!prop)
1962           {
1963             // qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
1964 
1965             QMessageBox::warning(this,
1966                                 tr("massXpert - Spectrum Calculator"),
1967                                 tr("Elemental composition not found for "
1968                                    "current oligomer.\n Uncheck the Isotopic "
1969                                    "cluster checkbox for this calculation."),
1970                                 QMessageBox::Ok);
1971 
1972             return;
1973           }
1974 
1975         QString *formulaString = static_cast<QString *>(prop->data());
1976 
1977         Formula formula(*formulaString);
1978 
1979         m_ui.logPlainTextEdit->
1980           textCursor().insertText(tr("Computing isotopic cluster"
1981                                      " for oligomer %1\n"
1982                                      "\tformula: %2.\n")
1983                                   .arg(iter + 1)
1984                                   .arg(*formulaString));
1985 
1986         // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
1987         qApp->processEvents();
1988 
1989         // We want to validate the formula and in the mean time
1990         // construct the list of all the AtomCount objects(first
1991         // param true), and since the formula is never reused we
1992         // do not need to reset (second param false). m_atomList
1993         // is a reference to the atom list of the polymer
1994         // chemistry definition currently used in the caller
1995         // calculator window.
1996 
1997         m_ui.logPlainTextEdit->
1998           textCursor().insertText(tr(" Validating formula... "));
1999         qApp->processEvents();
2000 
2001         if(!formula.validate(m_atomList, true, false))
2002           {
2003             m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error"));
2004 
2005             m_ui.logPlainTextEdit->textCursor().insertText(tr(" Failure.\n\n"));
2006             // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2007             qApp->processEvents();
2008 
2009             return;
2010           }
2011 
2012         m_ui.logPlainTextEdit->textCursor().insertText(tr("Success.\n"));
2013         // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2014         qApp->processEvents();
2015 
2016         double mono = oligomer->mono();
2017         double charge = oligomer->charge();
2018 
2019         // Because we have the mono m/z, we can compute the fwhm:
2020         double fwhm = (mono / m_resolution);
2021         m_config.setFwhm(fwhm);
2022 
2023         double increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) /
2024           m_config.pointNumber();
2025 
2026         // But we have to take into account the charge:
2027         if(charge)
2028           increment = increment / charge;
2029 
2030         m_config.setIncrement(increment);
2031 
2032         m_ui.logPlainTextEdit->
2033           textCursor().insertText(tr("\tmono m/z: %1\n"
2034                                      "\tcharge: %2\n"
2035                                      "\tfwhm: %3\n"
2036                                      "\tincrement: %4\n\n")
2037                                   .arg(mono)
2038                                   .arg(charge)
2039                                   .arg(fwhm)
2040                                   .arg(increment));
2041         // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2042         qApp->processEvents();
2043 
2044         // qDebug() << __FILE__ << __LINE__
2045         //          << "index:" << iter << "\n"
2046         //          << "formula: " << formula.formula() << "\n"
2047         //          << "charge:" << charge << "\n"
2048         //          << "mono m/z:" << mono << "\n"
2049         //          << "avg m/z:" << oligomer->avg() << "\n"
2050         //          << "resolution:" << m_resolution << "\n"
2051         //          << "fwhm:" << fwhm << "\n"
2052         //          << "Whole config:" << m_config.config();
2053 
2054         IsotopicPatternCalculator *calculator =
2055           new IsotopicPatternCalculator(formula, charge,
2056                                         m_maximumPeaks,
2057                                         m_minimumProbability,
2058                                         m_atomList, m_config);
2059 
2060         // Perform the calculation...
2061         const QList<QPointF *>  &pointList = calculator->sumPeakShapes();
2062 
2063         if(pointList.isEmpty())
2064           qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
2065 
2066         QList<QPointF *> *newPointList = new QList<QPointF *>;
2067 
2068         calculator->transferPoints(newPointList);
2069 
2070         spectrumList->append(newPointList);
2071 
2072         m_ui.logPlainTextEdit->
2073           textCursor().insertText(tr("\t\tDone computing the cluster\n\n"));
2074         // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2075         qApp->processEvents();
2076 
2077         // Store along with the point list, the increment that was
2078         // used for that calculation. We'll need it later when
2079         // computing the whole spectrum.
2080 
2081         incrementHash->insert(newPointList, m_config.increment());
2082 
2083         // At this point we have the points, we can delete the
2084         // calculator.
2085         delete calculator;
2086 
2087         if(m_aborted)
2088           break;
2089       }
2090     // End of
2091     // for(int iter = 0,
2092     //         size = mp_oligomerList->size(); iter < size; ++iter)
2093 
2094     m_ui.logPlainTextEdit->
2095       textCursor().insertText(tr("Done computing *all* the clusters\n\n"));
2096     // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2097     qApp->processEvents();
2098   }
2099 
2100 
2101   void
executeSpectrumWithoutCluster(QList<QList<QPointF * > * > * spectrumList,QHash<QList<QPointF * > *,double> * incrementHash)2102   SpectrumCalculationDlg::executeSpectrumWithoutCluster
2103     (QList<QList<QPointF *> *> *spectrumList,
2104      QHash<QList<QPointF *> *, double> *incrementHash)
2105   {
2106     if(!spectrumList)
2107       qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
2108 
2109     if(!incrementHash)
2110       qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
2111 
2112     m_ui.logPlainTextEdit->
2113       textCursor().insertText(tr("Simulating a spectrum "
2114                                  "without calculation of isotopic "
2115                                  "clusters for each "
2116                                  "oligomer.\n\n"));
2117 
2118     m_ui.logPlainTextEdit->appendPlainText(tr("There are %1 oligomers\n"
2119                                               "Calculating sub-spectrum "
2120                                               "for each\n\n")
2121                                            .arg(mp_oligomerList->size()));
2122     // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2123     qApp->processEvents();
2124 
2125     for(int iter = 0,
2126           size = mp_oligomerList->size(); iter < size; ++iter)
2127       {
2128         Oligomer *oligomer = mp_oligomerList->at(iter);
2129 
2130         // Not asking for an isotopic cluster to be computed for
2131         // each oligomer. The user want the mono or avg mass of
2132         // the oligomer to be considered the centroid of a peak
2133         //
2134         double mz = 0;
2135 
2136         if(m_withMonoMass)
2137           mz = oligomer->mono();
2138         else
2139           mz = oligomer->avg();
2140 
2141         double charge = oligomer->charge();
2142 
2143         PeakCentroid centroid(mz, 100, 1);
2144 
2145         // Because we have the m/z, we can compute the fwhm:
2146         double fwhm = (mz / m_resolution);
2147         m_config.setFwhm(fwhm);
2148 
2149         double increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) /
2150           m_config.pointNumber();
2151 
2152         // But we have to take into account the charge:
2153         if(charge)
2154           increment = increment / charge;
2155 
2156         m_config.setIncrement(increment);
2157 
2158         m_ui.logPlainTextEdit->
2159           textCursor().insertText(tr("Computing peak shape for oligomer %1\n")
2160                                   .arg(iter + 1));
2161 
2162         if(m_withMonoMass)
2163           m_ui.logPlainTextEdit->
2164             textCursor().insertText(tr("\tmono m/z: %1\n")
2165                                     .arg(mz));
2166         else
2167           m_ui.logPlainTextEdit->
2168             textCursor().insertText(tr("\tavg m/z: %1\n")
2169                             .arg(mz));
2170 
2171         m_ui.logPlainTextEdit->
2172           textCursor().insertText(tr("\tcharge: %2\n"
2173                                      "\tfwhm: %3\n"
2174                                      "\tincrement: %4\n\n")
2175                                   .arg(charge)
2176                                   .arg(fwhm)
2177                                   .arg(increment));
2178         // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2179         qApp->processEvents();
2180 
2181         // qDebug() << __FILE__ << __LINE__
2182         //          << "index:" << iter << "\n"
2183         //          << "charge:" << charge << "\n"
2184         //          << "m/z:" << mz << "\n"
2185         //          << "avg m/z:" << oligomer->avg() << "\n"
2186         //          << "resolution:" << m_resolution << "\n"
2187         //          << "fwhm:" << fwhm << "\n"
2188         //          << "Whole config:" << m_config.config();
2189 
2190         PeakShape *shape = new PeakShape(centroid, m_config);
2191 
2192         shape->calculatePeakShape();
2193 
2194         const QList<QPointF *>  &pointList = shape->pointList();
2195 
2196         if(pointList.isEmpty())
2197           qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
2198 
2199         QList<QPointF *> *newPointList = new QList<QPointF *>;
2200 
2201         shape->transferPoints(newPointList);
2202 
2203         spectrumList->append(newPointList);
2204 
2205         m_ui.logPlainTextEdit->
2206           textCursor().insertText(tr("\t\tDone computing the peak shape\n\n"));
2207         // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2208         qApp->processEvents();
2209 
2210         // Store along with the point list, the increment that was
2211         // used for that calculation. We'll need it later when
2212         // computing the whole spectrum.
2213 
2214         incrementHash->insert(newPointList, m_config.increment());
2215 
2216         // At this point we have the points, we can delete the
2217         // peack shape.
2218         delete shape;
2219 
2220         if(m_aborted)
2221           break;
2222       }
2223     // End of
2224     // for(int iter = 0,
2225     //         size = mp_oligomerList->size(); iter < size; ++iter)
2226 
2227     m_ui.logPlainTextEdit->
2228       textCursor().insertText(tr("Done computing *all* the peak shapes\n\n"));
2229     // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
2230     qApp->processEvents();
2231   }
2232 
2233 
2234   void
spectrumCalculationProgressValueChanged(int value)2235   SpectrumCalculationDlg::spectrumCalculationProgressValueChanged
2236   (int value)
2237   {
2238     m_ui.progressBar->setValue(value);
2239     // qDebug() << __FILE__ << __LINE__
2240     //          << "spectrumCalculationProgressValueChanged:" << value;
2241     qApp->processEvents();
2242   }
2243 
2244 
2245   void
spectrumCalculationMessageChanged(QString text)2246   SpectrumCalculationDlg::spectrumCalculationMessageChanged
2247   (QString text)
2248   {
2249     m_ui.logPlainTextEdit->appendPlainText(text);
2250     // qDebug() << __FILE__ << __LINE__
2251     //          << "spectrumCalculationMessageChanged:" << text;
2252     qApp->processEvents();
2253   }
2254 
2255 
2256   void
abort()2257   SpectrumCalculationDlg::abort()
2258   {
2259     m_aborted = true;
2260     emit(spectrumCalculationAborted());
2261   }
2262 
2263 
2264   void
outputFile()2265   SpectrumCalculationDlg::outputFile()
2266   {
2267     QString name;
2268 
2269     m_filePath =
2270       QFileDialog::getSaveFileName(this, tr("Export Raw Text File"),
2271 				    QDir::homePath(),
2272 				    tr("Any file type(*)"));
2273   }
2274 
2275   void
debugPutStdErrBitset(QString file,int line,int value,const QString & text)2276   SpectrumCalculationDlg::debugPutStdErrBitset(QString file, int line,
2277                                                       int value,
2278                                                       const QString &text)
2279   {
2280     qDebug() << file << line << "\n"
2281              << GlobBinaryRepresentation(MXP_VALIDATION_ERRORS_NONE)
2282              << ": MXP_VALIDATION_ERRORS_NONE" << "\n"
2283              << GlobBinaryRepresentation(MXP_VALIDATION_FORMULA_ERRORS)
2284              << ": MXP_VALIDATION_FORMULA_ERRORS" << "\n"
2285              << GlobBinaryRepresentation(MXP_VALIDATION_RESOLUTION_ERRORS)
2286              << ": MXP_VALIDATION_RESOLUTION_ERRORS" << "\n"
2287              << GlobBinaryRepresentation(MXP_VALIDATION_FWHM_ERRORS)
2288              << ": MXP_VALIDATION_FWHM_ERRORS" << "\n"
2289              << "\n"
2290              << GlobBinaryRepresentation(value)
2291              << ":" << text << ":" << value
2292              << "\n";
2293   }
2294 
2295 } // namespace massXpert
2296 
2297 
2298 
2299 #if 0
2300   /***************************************************************************
2301    *  file                 :  ipc.c                                          *
2302    *  copyright            :(C) 2001-2005 by Dirk Nolting                   *
2303    *  email                : nolting@uni-duesseldorf.de                      *
2304    ***************************************************************************/
2305 
2306   /***************************************************************************
2307    *                                                                         *
2308    *   This program is free software; you can redistribute it and/or modify  *
2309    *   it under the terms of the GNU General Public License as published by  *
2310    *   the Free Software Foundation; either version~2 of the License, or     *
2311    *  (at your option) any later version.                                   *
2312    *                                                                         *
2313    ***************************************************************************/
2314 
2315 #include "global.h"
2316 #include "ipc.h"
2317 #include "pars.h"
2318 #include "element.h"
2319 #include "gp_out.h"
2320 #include <time.h>
2321 #include <signal.h>
2322 #include <math.h>
2323 
2324 //#define SUMMARIZE_LEVEL 0.00000001
2325 //#define SUMMARIZE_LEVEL 0.0001
2326 //#define SUMMARIZE_LEVEL 0.001
2327 #define SUMMARIZE_LEVEL 0.01
2328 
2329   compound *verbindung=NULL;
2330   isotope *m_peakList;
2331   int fast_calc=0;
2332 
2333   void free_list(isotope *target)
2334   {
2335     while(target->next)
2336       {
2337 	target=target->next;
2338 	free(target->previous);
2339       }
2340     free(target);
2341   }
2342 
2343   void cut_peaks(isotope *spectrum)
2344   {
2345     int dummy=1;
2346 
2347     while((spectrum->next) &&(dummy<fast_calc) )
2348       {
2349 	++dummy;
2350 	spectrum=spectrum->next;
2351       }
2352 
2353     if(spectrum->next)
2354       {
2355 	free_list(spectrum->next);
2356 	spectrum->next=NULL;
2357       }
2358   }
2359 
2360   void summarize_peaks()
2361   {  isotope *dummy,*d2;
2362 
2363     for(dummy=m_peakList;dummy;dummy=dummy->next)
2364       /* Differenz wegen Rundungsfehlern */
2365       while( dummy->next &&(dummy->next->mass - dummy->mass < SUMMARIZE_LEVEL) )
2366 	{
2367 	  d2=dummy->next;
2368 	  dummy->next=d2->next;
2369 	  if(dummy->next)
2370 	    dummy->next->previous=dummy;
2371 	  dummy->p+=d2->p;
2372 	  free(d2);
2373 	}
2374   }
2375 
2376   isotope *add_peak(isotope *base,isotope *peak)
2377   {
2378     static isotope *IsotopeIterator;
2379 
2380     if(!(base->mass))
2381       {
2382 	peak->next=NULL;
2383 	peak->previous=NULL;
2384 	IsotopeIterator = peak;
2385 	return peak;
2386       }
2387 
2388     if( peak->mass >= IsotopeIterator->mass )
2389       while((IsotopeIterator->next) &&(IsotopeIterator->mass < peak->mass))
2390 	IsotopeIterator=IsotopeIterator->next;
2391     else
2392       {
2393 	while((IsotopeIterator->previous) &&(IsotopeIterator->mass > peak->mass) )
2394 	  IsotopeIterator=IsotopeIterator->previous;
2395 	IsotopeIterator=IsotopeIterator->next;
2396       }
2397 
2398     if((IsotopeIterator->mass) >=(peak->mass) )
2399       {
2400 	peak->next=IsotopeIterator;
2401 	peak->previous=IsotopeIterator->previous;
2402 	peak->previous->next=peak;
2403 	IsotopeIterator->previous=peak;
2404 	return base;
2405       }
2406     else
2407       {
2408 	IsotopeIterator->next=peak;
2409 	peak->next=NULL;
2410 	peak->previous=IsotopeIterator;
2411 	return base;
2412       }
2413     return 0;
2414   }
2415 
2416   int calculate_peaks(){
2417     compound *c;
2418     isotope *newPeakList,*p,*i,*np1;
2419     int amount;
2420 
2421     if(!(m_peakList=malloc(sizeof(isotope))))
2422       return 0;
2423     m_peakList->mass=0;
2424     m_peakList->p=1;
2425     m_peakList->previous=NULL;
2426     m_peakList->next=NULL;
2427 
2428     for (c = verbindung; c; c = c->next)
2429       {
2430 	for(amount = 0; amount < c->amount; ++amount)
2431 	  {
2432 	    if (!(newPeakList=malloc(sizeof(isotope))))
2433 	      return 0;
2434 
2435 	    newPeakList->mass = 0;
2436 
2437 	    for (p = m_peakList; p; p = p->next)
2438 	      {
2439 		for(i = c->isotopes; i; i = i->next)
2440 		  {
2441 		    //printf("working on isotope of mass %f\n", i->mass);
2442 
2443 		    if (!(np1 = malloc(sizeof(isotope))))
2444 		      return 0;
2445 
2446 		    np1->mass=p->mass + i->mass;
2447 		    np1->p=p->p * i->p;
2448 
2449 		    if(!(newPeakList = add_peak(newPeakList,np1)))
2450 		      return 0;
2451 		  }
2452 	      }
2453 
2454 	    free_list(m_peakList);
2455 	    m_peakList = newPeakList;
2456 	    summarize_peaks();
2457 
2458 	    if (fast_calc)
2459 	      cut_peaks(m_peakList);
2460 	  }
2461       }
2462 
2463     return 1;
2464   }
2465 
2466 
2467   void print_result(int digits,int charge){
2468     isotope *d;
2469     double maxp=0,relint=0,sump=0;
2470     int permutationen=0;
2471 
2472     printf("\n");
2473 
2474     for(d=m_peakList;d;d=d->next)
2475       {
2476 	++permutationen;
2477 	sump+=d->p;
2478 	d->mass=d->mass / charge;
2479 	d->mass=(rint( d->mass * pow(10,digits) ) / pow(10,digits) );
2480       }
2481 
2482     summarize_peaks();
2483     for(d=m_peakList;d;d=d->next)
2484       if(d->p > maxp)
2485 	maxp=d->p;
2486 
2487     for(d=m_peakList;d;d=d->next)
2488       {
2489 	if(( relint=(d->p/maxp)*100) > MIN_INT )
2490 	  printf("M= %f, p= %e, rel. Int.= %f%%\n",
2491 		 d->mass,d->p,relint);
2492       }
2493     if(!(fast_calc))
2494       printf("\nNumber of  permutations: %i\n",permutationen);
2495     else
2496       {
2497 	sump=(rint(sump*10000)/100);
2498 	printf("\nCovered Intensity: %2.2f%% \n",sump);
2499       }
2500   }
2501 
2502   int main(int argc,char **argv){
2503     long seconds;
2504     int d=1,zeig_summenformel=0,calc_peaks=1,gnuplot=0,use_digits=USE_DIGITS,charge=1;
2505     char *gnuplotfile=NULL;
2506 
2507     if(!argv[d])
2508       {
2509 	usage();
2510 	return 1;
2511       }
2512 
2513 #ifdef HAVE_SIGNAL
2514     signal(SIGHUP,SIG_IGN);
2515 #endif
2516 
2517     if(!init_elements()){
2518       printf("Error in init_elements\n");
2519       return 1;
2520     }
2521     while(argv[d])
2522       {
2523 	if(!strcmp(argv[d],"-f"))
2524 	  {
2525 	    ++d;
2526 	    if(!argv[d])
2527 	      {
2528 		printf("Missing argument for -f\n");
2529 		return 1;
2530 	      }
2531 	    fast_calc=strtol(argv[d],NULL,10);
2532 	  }
2533 
2534 	else if(!strcmp(argv[d],"-z"))
2535 	  {
2536 	    ++d;
2537 	    if(!argv[d])
2538 	      {
2539 		printf("Missing argument for -z\n");
2540 		return 1;
2541 	      }
2542 	    charge=strtol(argv[d],NULL,10);
2543 	  }
2544 
2545 	else if(!strcmp(argv[d],"-d"))
2546 	  {
2547 	    ++d;
2548 	    if(!argv[d])
2549 	      {
2550 		printf("Missing argument for -d\n");
2551 		return 1;
2552 	      }
2553 	    use_digits=strtol(argv[d],NULL,10);
2554 	  }
2555 
2556 	else if(!strcmp(argv[d],"-c")){
2557 	  ++d;
2558 	  if(!argv[d])
2559 	    {
2560 	      printf("Missing argument for -c\n");
2561 	      return 1;
2562 	    }
2563 	  if(!pars_chem_form(argv[d])){
2564 	    printf("Parser error.\n");
2565 	    return 1;
2566 	  }
2567 	}
2568 
2569 	else if(!strcmp(argv[d],"-g")){
2570 	  ++d;
2571 	  if(!argv[d]){
2572 	    printf("Missing argument for -g\n");
2573 	    return 1;
2574 	  }
2575 	  gnuplot=1;
2576 	  if(!(gnuplotfile=strdup(argv[d]))){
2577 	    printf("Not enough memory\n");
2578 	    return 1;
2579 	  }
2580 	}
2581 
2582 
2583 	else if(!strcmp(argv[d],"-p")){
2584 	  ++d;
2585 	  if(!argv[d]){
2586 	    printf("Missing argument for -p\n");
2587 	    return 1;
2588 	  }
2589 	  if(!(pars_peptid(argv[d]))){
2590 	    printf("Error in peptid parser.\n");
2591 	    return 1;
2592 	  }
2593 	}
2594 
2595 	else if(!strcmp(argv[d],"-a")){
2596 	  ++d;
2597 	  if(!argv[d]){
2598 	    printf("Missing argument for -a\n");
2599 	    return 1;
2600 	  }
2601 	  if(!pars_amino_acid(argv[d])){
2602 	    printf("Error in pars_amino_acid.\n");
2603 	    return 1;
2604 	  }
2605 	}
2606 
2607 	else if(!strcmp(argv[d],"-s"))
2608 	  zeig_summenformel=1;
2609 
2610 	else if(!strcmp(argv[d],"-h"))
2611 	  {
2612 	    usage();
2613 	    return 1;
2614 	  }
2615 
2616 	else if(!strcmp(argv[d],"-x"))
2617 	  calc_peaks=0;
2618 
2619 	else
2620 	  {
2621 	    printf("Unknown flag: %s\n",argv[d]);
2622 	    usage();
2623 	    return 1;
2624 	  }
2625 	++d;
2626       }
2627 
2628     if(zeig_summenformel)
2629       {
2630 	if(!print_sum())
2631 	  {
2632 	    printf("error while showing chemical formula.\n");
2633 	    return 1;
2634 	  }
2635       }
2636 #ifdef HAVE_TIME
2637     seconds=time(0);
2638 #endif
2639 
2640     if(calc_peaks)
2641       if(!calculate_peaks()){
2642 	printf("Error in calculate_peaks\n");
2643 	return 1;
2644       }
2645 
2646     print_result(use_digits,charge);
2647 
2648 #ifdef HAVE_TIME
2649     seconds=time(0)-seconds;
2650     printf("Computing time: %li seconds.\n",seconds);
2651 #endif
2652 
2653     if(gnuplot)
2654       if(!(make_gnuplot_output(gnuplotfile))){
2655 	printf("Error while generating gnuplot file\n");
2656 	return 1;
2657       }
2658 
2659     return 0;
2660   }
2661 
2662 
2663   void usage()
2664   {
2665     printf("\nThis is IPC v %s\nCopyright Dirk Nolting 2001-2005\n\n",VERSION);
2666     printf("\nSynopsis:\n ipc -d <int> -z <int> -f <int> <-a <amino acid> -c <chemical formula> -p <File> -g <name> -s -x -h\n\n");
2667 
2668     printf("-c <chemical formula> calculates the isotopic pattern of the \n");
2669     printf("   given chemical formula. Additive with -p\n");
2670 
2671     printf("-p <File> reads peptide sequenz(one letter notation) from the \n");
2672     printf("   given file and calculates the isotopic pattern. Additive with -c\n");
2673 
2674     printf("-a <amino acids> calculate isotopic pattern from given amino acids\n");
2675     printf("   in one letter notation\n");
2676 
2677     printf("-s gives the chemical formula. Usefull for -a or -p\n");
2678     printf("-g creates gnuplot output and stores it in the file <name> and <name>.gnu\n");
2679 
2680     printf("-x no calculation of the isotopic pattern. Usefull for -s\n");
2681     printf("-f fast calc, calculates only first <int> peaks\n");
2682     printf("-d <int> digits significant\n");
2683     printf("-z assume <int> charges on ion \n");
2684     printf("-h show this text\n\n");
2685   }
2686 #endif
2687