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