1 /* massXpert - the true massist's program.
2    --------------------------------------
3    Copyright(C) 2006,2007 Filippo Rusconi
4 
5    http://www.massxpert.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<QMessageBox>
40 #include<QFileDialog>
41 
42 /////////////////////// Local includes
43 #include "compositionsDlg.hpp"
44 #include "application.hpp"
45 #include "coordinates.hpp"
46 
47 namespace massXpert
48 {
49 
CompositionsDlg(QWidget * parent,Polymer * polymer,CalcOptions * calcOptions,IonizeRule * ionizeRule)50   CompositionsDlg::CompositionsDlg(QWidget *parent,
51 				    Polymer *polymer,
52 				    CalcOptions *calcOptions,
53 				    IonizeRule *ionizeRule)
54     : QDialog(parent),
55       mp_polymer(polymer),
56       mp_calcOptions(calcOptions),
57       mp_ionizeRule(ionizeRule)
58   {
59     Q_ASSERT(parent);
60     Q_ASSERT(mp_polymer && mp_calcOptions && mp_ionizeRule);
61 
62     m_ui.setupUi(this);
63 
64     mp_editorWnd = static_cast<SequenceEditorWnd *>(parent);
65 
66     updateSelectionData();
67 
68     setupTreeView();
69 
70     // The results-exporting menus. ////////////////////////////////
71 
72     QStringList comboBoxItemList;
73 
74     comboBoxItemList
75       << tr("To Clipboard")
76       << tr("To File")
77       << tr("Select File");
78 
79     m_ui.exportResultsComboBox->addItems(comboBoxItemList);
80 
81     connect(m_ui.exportResultsComboBox,
82 	     SIGNAL(activated(int)),
83 	     this,
84 	     SLOT(exportResults(int)));
85 
86     mpa_resultsString = new QString();
87 
88     //////////////////////////////////// The results-exporting menus.
89 
90 
91     QSettings settings
92      (static_cast<Application *>(qApp)->configSettingsFilePath(),
93        QSettings::IniFormat);
94 
95     settings.beginGroup("compositions_dlg");
96 
97     restoreGeometry(settings.value("geometry").toByteArray());
98 
99     m_ui.splitter->restoreState(settings.value("splitter").toByteArray());
100 
101     settings.endGroup();
102 
103 
104     connect(m_ui.updateSelectionDataPushButton,
105 	     SIGNAL(clicked()),
106 	     this,
107 	     SLOT(updateSelectionData()));
108 
109     connect(m_ui.monomericPushButton,
110 	     SIGNAL(clicked()),
111 	     this,
112 	     SLOT(monomericComposition()));
113 
114     connect(m_ui.elementalPushButton,
115 	     SIGNAL(clicked()),
116 	     this,
117 	     SLOT(elementalComposition()));
118   }
119 
120 
~CompositionsDlg()121   CompositionsDlg::~CompositionsDlg()
122   {
123     delete mpa_compositionTreeViewModel;
124 
125     delete mpa_compositionProxyModel;
126 
127     delete mpa_resultsString;
128 
129     freeMonomerList();
130   }
131 
132 
133   void
closeEvent(QCloseEvent * event)134   CompositionsDlg::closeEvent(QCloseEvent *event)
135   {
136     if (event)
137       printf("%s", "");
138 
139     QSettings settings
140      (static_cast<Application *>(qApp)->configSettingsFilePath(),
141        QSettings::IniFormat);
142 
143     settings.beginGroup("compositions_dlg");
144 
145     settings.setValue("geometry", saveGeometry());
146 
147     settings.setValue("splitter", m_ui.splitter->saveState());
148 
149     settings.endGroup();
150   }
151 
152 
153   SequenceEditorWnd *
editorWnd()154   CompositionsDlg::editorWnd()
155   {
156     return mp_editorWnd;
157   }
158 
159 
160   void
updateSelectionData()161   CompositionsDlg::updateSelectionData()
162   {
163     // The selection might exist as a list of region selections.
164 
165     if (mp_editorWnd->mpa_editorGraphicsView->
166 	selectionIndices(&m_coordinateList))
167       {
168 	m_ui.selectionCoordinatesLineEdit->
169 	  setText(m_coordinateList.positionsAsText());
170 
171 	m_ui.selectedSequenceRadioButton->setChecked(true);
172       }
173     else
174       {
175 	m_ui.selectionCoordinatesLineEdit->setText("");
176 
177 	m_ui.wholeSequenceRadioButton->setChecked(true);
178       }
179 
180     m_ui.monomericPushButton->setFocus();
181   }
182 
183 
184   bool
fetchValidateInputData()185   CompositionsDlg::fetchValidateInputData()
186   {
187     if (!m_ui.selectedSequenceRadioButton->isChecked())
188       {
189 	m_coordinateList.setCoordinates(Coordinates(0,
190 						      mp_polymer->size() - 1));
191       }
192 
193     // Make sure the sequence still has a number of residues
194     // compatible with the current m_coordinateList. this because the
195     // sequence might be edited between opening this dialog window and
196     // actually using it.
197 
198     int polymerSize = mp_polymer->size();
199 
200     for (int iter = 0; iter < m_coordinateList.size(); ++iter)
201       {
202 	// New coordinates instance we are iterating into.
203 	Coordinates *coordinates = m_coordinateList.at(iter);
204 
205 	if(coordinates->start() >= polymerSize ||
206 	    coordinates->end() >= polymerSize)
207 	  {
208 	    QMessageBox::warning(0,
209 				  tr("massXpert - Compositions"),
210 				  tr("Selection data are no more valid.\n"
211 				      "Please update these data."),
212 				  QMessageBox::Ok);
213 	    return false;
214 	  }
215       }
216 
217     // We also want to know if the cross-links should be taken into account.
218 
219     if (mp_calcOptions->monomerEntities() & MXT_MONOMER_CHEMENT_CROSS_LINK)
220       {
221 	// If the whole sequence is dealt with, then by definition all
222 	// the cross-links are encompassed by the sequence.
223 
224 	if(!m_ui.selectedSequenceRadioButton->isChecked())
225 	  {
226 	    m_ui.incompleteCrossLinkWarningLabel->setText
227 	     (tr("Incomplete cross-links: %1").
228 	       arg(0));
229 	  }
230 	else
231 	  {
232 	    // We have to count the incomplete cross-links.
233 
234 	    const CrossLinkList &crossLinkList = mp_polymer->crossLinkList();
235 
236 	    int crossLinkPartial = 0;
237 
238 	    for (int iter = 0; iter < crossLinkList.size(); ++iter)
239 	      {
240 		CrossLink *crossLink = crossLinkList.at(iter);
241 
242 		int ret =
243 		  crossLink->encompassedBy(m_coordinateList);
244 
245 		if(ret == MXP_CROSS_LINK_ENCOMPASSED_FULL)
246 		  {
247 		    // 		qDebug() << __FILE__ << __LINE__
248 		    // 			  << "CrossLink at iter:" << iter
249 		    // 			  << "is fully encompassed";
250 		  }
251 		else if (ret == MXP_CROSS_LINK_ENCOMPASSED_PARTIAL)
252 		  {
253 		    // 		qDebug() << __FILE__ << __LINE__
254 		    // 			  << "CrossLink at iter:" << iter
255 		    // 			  << "is partially encompassed";
256 
257 		    ++crossLinkPartial;
258 		  }
259 		else
260 		  {
261 		    // 		qDebug() << __FILE__ << __LINE__
262 		    // 			  << "CrossLink at iter:" << iter
263 		    // 			  << "is not encompassed at all";
264 		  }
265 	      }
266 
267 	    m_ui.incompleteCrossLinkWarningLabel->setText
268 	     (tr("Incomplete cross-links: %1").
269 	       arg(crossLinkPartial));
270 	  }
271       }
272     else
273       {
274 	m_ui.incompleteCrossLinkWarningLabel->
275 	  setText(tr("Not accounting for cross-links"));
276       }
277 
278     return true;
279   }
280 
281 
282   void
monomericComposition()283   CompositionsDlg::monomericComposition()
284   {
285     if (!fetchValidateInputData())
286       {
287 	QMessageBox::warning(0,
288 			      tr("massXpert - Compositions"),
289 			      tr("Failed validating input data."),
290 			      QMessageBox::Ok);
291 	return;
292       }
293 
294     Monomer *fakeMonomer = 0;
295 
296     // Empty the treeview. This will also remove all the monomer items
297     // in the m_monomerList.
298     mpa_compositionTreeViewModel->removeAll();
299 
300     setCursor(Qt::WaitCursor);
301 
302     // Iterate in the sequence and count all the occurrences of the
303     // different monomer that comprise the sequence.
304 
305     if (mpa_compositionTreeViewModel)
306       {
307 	delete mpa_compositionTreeViewModel;
308 	mpa_compositionTreeViewModel = 0;
309       }
310 
311     if (mpa_compositionProxyModel)
312       {
313 	delete mpa_compositionProxyModel;
314 	mpa_compositionProxyModel = 0;
315       }
316 
317     for (int iter = 0; iter < m_coordinateList.size(); ++iter)
318       {
319 	// New coordinates instance we are iterating into.
320 	Coordinates *coordinates = m_coordinateList.at(iter);
321 
322 	for(int jter = coordinates->start() ;
323 	     jter < coordinates->end() + 1; ++jter)
324 	  {
325 	    bool processed = false;
326 
327 	    const Monomer *iterMonomer = mp_polymer->at(jter);
328 	    Q_ASSERT(iterMonomer);
329 
330 	    // Check if monomer by same name is not already in our list of
331 	    // fake monomers.
332 
333 	    for (int kter = 0; kter < m_monomerList.size(); ++kter)
334 	      {
335 		fakeMonomer = m_monomerList.at(kter);
336 		Q_ASSERT(fakeMonomer);
337 
338 		if(iterMonomer->name() == fakeMonomer->name())
339 		  {
340 		    Prop *prop = fakeMonomer->prop("MONOMER_COUNT");
341 
342 		    // The fake monomer MUST have a count prop !
343 		    Q_ASSERT(prop);
344 
345 		    int *count =
346 		      new int(*static_cast<const int *>(prop->data()));
347 
348 		    ++(*count);
349 		    prop->setData(count);
350 
351 		    if (iterMonomer->isModified())
352 		      {
353 			// The monomer in the sequence is modified. Let the
354 			// fake monomer know it.
355 			Prop *prop = fakeMonomer->prop("MODIF_COUNT");
356 
357 			// The fake monomer might not have a modif prop.
358 			if(prop)
359 			  {
360 			    int *count =
361 			      new int(*static_cast<const int *>(prop->data()));
362 			    ++(*count);
363 			    prop->setData(count);
364 			  }
365 			else
366 			  {
367 			    IntProp *prop = new IntProp("MODIF_COUNT", 1);
368 			    fakeMonomer->appendProp(prop);
369 			  }
370 		      }
371 
372 		    processed = true;
373 
374 		    break;
375 		  }
376 	      }
377 	    // End of
378 	    // for (int kter = 0; kter < m_monomerList.size(); ++kter)
379 
380 	    // Did we find a fake monomer, thus processing the currently
381 	    // iterated sequence monomer or not ? If not we still have to do
382 	    // all the work.
383 
384 	    if (!processed)
385 	      {
386 		fakeMonomer = iterMonomer->clone();
387 
388 		IntProp *prop = new IntProp("MONOMER_COUNT", 1);
389 		fakeMonomer->appendProp(prop);
390 
391 		if(iterMonomer->isModified())
392 		  {
393 		    // The monomer in the sequence is modified. Let the
394 		    // fake monomer know it.
395 
396 		    IntProp *prop = new IntProp("MODIF_COUNT", 1);
397 		    fakeMonomer->appendProp(prop);
398 		  }
399 
400 		m_monomerList.append(fakeMonomer);
401 	      }
402 	  }
403 	// End of
404 	// for (int iter = startIndex ; iter < endIndex + 1; ++iter)
405 
406 	setupTreeView();
407 
408 	prepareResultsTxtString(MXP_TARGET_MONOMERIC);
409 
410 	setCursor(Qt::ArrowCursor);
411       }
412   }
413 
414 
415   void
elementalComposition()416   CompositionsDlg::elementalComposition()
417   {
418     // For each monomer in the sequence, get the formula and account it.
419 
420     if (!fetchValidateInputData())
421       {
422 	QMessageBox::warning(0,
423 			      tr("massXpert - Compositions"),
424 			      tr("Failed validating input data."),
425 			      QMessageBox::Ok);
426 	return;
427       }
428 
429     QString composition = mp_polymer->elementalComposition(*mp_ionizeRule,
430                                                            m_coordinateList,
431                                                            *mp_calcOptions);
432         m_ui.elementalCompositionLineEdit->
433       setText(composition);
434 
435     prepareResultsTxtString(MXP_TARGET_ELEMENTAL);
436 
437     setCursor(Qt::ArrowCursor);
438 
439   }
440 
441 
442   void
setupTreeView()443   CompositionsDlg::setupTreeView()
444   {
445     // Model stuff all thought for sorting.
446     mpa_compositionTreeViewModel =
447       new CompositionTreeViewModel(&m_monomerList, this);
448 
449     mpa_compositionProxyModel = new CompositionTreeViewSortProxyModel(this);
450     mpa_compositionProxyModel->setSourceModel(mpa_compositionTreeViewModel);
451 
452     m_ui.compositionTreeView->setModel(mpa_compositionProxyModel);
453     m_ui.compositionTreeView->setParentDlg(this);
454     mpa_compositionTreeViewModel->setTreeView(m_ui.compositionTreeView);
455   }
456 
457 
458   void
freeMonomerList()459   CompositionsDlg::freeMonomerList()
460   {
461     while(!m_monomerList.isEmpty())
462       delete m_monomerList.takeFirst();
463   }
464 
465 
466   // The results-exporting functions. ////////////////////////////////
467   // The results-exporting functions. ////////////////////////////////
468   // The results-exporting functions. ////////////////////////////////
469   void
exportResults(int index)470   CompositionsDlg::exportResults(int index)
471   {
472     // Remember that we had set up the combobox with the following strings:
473     // << tr("To &Clipboard")
474     // << tr("To &File")
475     // << tr("&Select File");
476 
477     if (index == 0)
478       {
479 	exportResultsClipboard();
480       }
481     else if (index == 1)
482       {
483 	exportResultsFile();
484       }
485     else if (index == 2)
486       {
487 	selectResultsFile();
488       }
489     else
490       Q_ASSERT(0);
491 
492   }
493 
494 
495   void
prepareResultsTxtString(int target)496   CompositionsDlg::prepareResultsTxtString(int target)
497   {
498     mpa_resultsString->clear();
499 
500     *mpa_resultsString += QObject::tr("# \n"
501                                       "# -------------\n"
502                                       "# Compositions: \n"
503                                       "# -------------\n");
504 
505     if (target == MXP_TARGET_ELEMENTAL)
506       {
507 	*mpa_resultsString += QObject::tr("\nIonization rule:\n");
508 
509 	*mpa_resultsString += QObject::tr("Formula: %1 - ")
510 	  .arg(mp_ionizeRule->formula());
511 
512 	*mpa_resultsString += QObject::tr("Charge: %1 - ")
513 	  .arg(mp_ionizeRule->charge());
514 
515 	*mpa_resultsString += QObject::tr("Level: %1\n")
516 	  .arg(mp_ionizeRule->level());
517 
518 
519 	*mpa_resultsString += QObject::tr("\nCalculation options:\n");
520 
521 
522 	bool withEntities =(mp_calcOptions->monomerEntities() &
523 			 MXT_MONOMER_CHEMENT_MODIF);
524 
525 	// We want a delimited sequence with indication of the
526 	// different sequence regions for which the composition was
527 	// determined, thus the true below.
528 
529 	QString *sequence =
530 	  mp_polymer->monomerText(m_coordinateList, withEntities, true);
531 
532 	*mpa_resultsString += *sequence;
533 
534 	delete sequence;
535 
536 	if(withEntities)
537 	  *mpa_resultsString += QObject::tr("Account monomer modifs: yes\n");
538 	else
539 	  *mpa_resultsString += QObject::tr("Account monomer modifs: no\n");
540 
541 	// Left end and right end modifs
542 	withEntities =(mp_calcOptions->polymerEntities() &
543 			MXT_POLYMER_CHEMENT_LEFT_END_MODIF ||
544 			mp_calcOptions->polymerEntities() &
545 			MXT_POLYMER_CHEMENT_RIGHT_END_MODIF);
546 
547 	if(!withEntities)
548 	  {
549 	    *mpa_resultsString += QObject::tr("Account ends' modifs: no\n");
550 	  }
551 	else
552 	  {
553 	    *mpa_resultsString += QObject::tr("Account ends' modifs: yes - ");
554 
555 	    // Left end modif
556 	    withEntities =(mp_calcOptions->polymerEntities() &
557 			    MXT_POLYMER_CHEMENT_LEFT_END_MODIF);
558 	    if (withEntities)
559 	      {
560 		*mpa_resultsString += QObject::tr("Left end modif: %1 - ")
561 		  .arg(mp_polymer->leftEndModif().name());
562 	      }
563 
564 	    // Right end modif
565 	    withEntities =(mp_calcOptions->polymerEntities() &
566 			    MXT_POLYMER_CHEMENT_RIGHT_END_MODIF);
567 	    if (withEntities)
568 	      {
569 		*mpa_resultsString += QObject::tr("Right end modif: %1")
570 		  .arg(mp_polymer->leftEndModif().name());
571 	      }
572 	  }
573 
574 	*mpa_resultsString += QObject::tr("\n\nElemental composition: %1")
575 	  .arg(m_ui.elementalCompositionLineEdit->text());
576       }
577     else if (target == MXP_TARGET_MONOMERIC)
578       {
579 	CompositionTreeViewModel *model =
580 	  static_cast<CompositionTreeViewModel *>
581 	 (m_ui.compositionTreeView->model());
582 	Q_ASSERT(model);
583 
584 	int rowCount = model->rowCount();
585 	//   qDebug() << __FILE__ << __LINE__ << "rowCount" << rowCount;
586 	if(!rowCount)
587 	  return;
588 
589 	QString composString;
590 
591 	for(int iter = 0; iter < rowCount; ++iter)
592 	  {
593 	    QModelIndex currentIndex = model->index(iter,
594 						     COMPOSITION_NAME_COLUMN,
595 						     QModelIndex());
596 	    Q_ASSERT(currentIndex.isValid());
597 
598 	    composString += QObject::tr("%1 - ").
599 	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
600 
601 
602 	    currentIndex = model->index(iter,
603 					 COMPOSITION_CODE_COLUMN,
604 					 QModelIndex());
605 	    Q_ASSERT(currentIndex.isValid());
606 
607 	    composString += QObject::tr("%1 - ").
608 	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
609 
610 
611 	    currentIndex = model->index(iter,
612 					 COMPOSITION_MODIF_COLUMN,
613 					 QModelIndex());
614 	    Q_ASSERT(currentIndex.isValid());
615 
616 	    composString += QObject::tr("Modified ?: %1 - ").
617 	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
618 
619 
620 	    currentIndex = model->index(iter,
621 					 COMPOSITION_COUNT_COLUMN,
622 					 QModelIndex());
623 	    Q_ASSERT(currentIndex.isValid());
624 
625 	    composString += QObject::tr("Count: %1.\n").
626 	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
627 	  }
628 	*mpa_resultsString += composString;
629       }
630     else
631       Q_ASSERT(0);
632   }
633 
634 
635   bool
exportResultsClipboard()636   CompositionsDlg::exportResultsClipboard()
637   {
638     QClipboard *clipboard = QApplication::clipboard();
639 
640     clipboard->setText(*mpa_resultsString, QClipboard::Clipboard);
641 
642     return true;
643   }
644 
645 
646   bool
exportResultsFile()647   CompositionsDlg::exportResultsFile()
648   {
649     if (m_resultsFilePath.isEmpty())
650       {
651 	if(!selectResultsFile())
652 	  return false;
653       }
654 
655     QFile file(m_resultsFilePath);
656 
657     if (!file.open(QIODevice::WriteOnly | QIODevice::Append))
658       {
659 	QMessageBox::information(0,
660 				  tr("massXpert - Export Data"),
661 				  tr("Failed to open file in append mode."),
662 				  QMessageBox::Ok);
663 	return false;
664       }
665 
666     QTextStream stream(&file);
667     stream.setCodec("UTF-8");
668 
669     stream << *mpa_resultsString;
670 
671     file.close();
672 
673     return true;
674   }
675 
676 
677   bool
selectResultsFile()678   CompositionsDlg::selectResultsFile()
679   {
680     m_resultsFilePath =
681       QFileDialog::getSaveFileName(this, tr("Select file to export data to"),
682 				    QDir::homePath(),
683 				    tr("Data files(*.dat *.DAT)"));
684 
685     if (m_resultsFilePath.isEmpty())
686       return false;
687 
688     return true;
689   }
690   //////////////////////////////////// The results-exporting functions.
691   //////////////////////////////////// The results-exporting functions.
692   //////////////////////////////////// The results-exporting functions.
693 
694 } // namespace massXpert
695