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<QDebug> 40 #include<QMouseEvent> 41 #include<QMessageBox> 42 43 /////////////////////// Local includes 44 #include "globals.hpp" 45 #include "cleaveOligomerTableView.hpp" 46 #include "cleaveOligomerTableViewSortProxyModel.hpp" 47 #include "oligomer.hpp" 48 #include "cleavageDlg.hpp" 49 #include "application.hpp" 50 #include "cleaveOligomerTableViewMimeData.hpp" 51 52 53 namespace massXpert 54 { 55 CleaveOligomerTableView(QWidget * parent)56 CleaveOligomerTableView::CleaveOligomerTableView(QWidget *parent) 57 : QTableView(parent) 58 { 59 60 setAlternatingRowColors(true); 61 62 setSortingEnabled(true); 63 setDragEnabled(true); 64 65 connect(this, 66 SIGNAL(activated(const QModelIndex &)), 67 this, 68 SLOT(itemActivated(const QModelIndex &))); 69 70 71 QHeaderView *headerView = horizontalHeader(); 72 headerView->setSectionsClickable(true); 73 headerView->setSectionsMovable(true); 74 75 ////// Create the actions for the contextual menu. 76 77 // Copy Mono 78 copyMonoAct = new QAction(tr("Copy Mono To Clipboard"), this); 79 copyMonoAct->setStatusTip(tr("Copies the monoisotopic mass list " 80 "to the clipboard")); 81 connect(copyMonoAct, SIGNAL(triggered()), this, SLOT(copyMono())); 82 83 // Copy Avg 84 copyAvgAct = new QAction(tr("Copy Avg To Clipboard"), this); 85 copyMonoAct->setStatusTip(tr("Copies the average mass list " 86 "to the clipboard")); 87 connect(copyAvgAct, SIGNAL(triggered()), this, SLOT(copyAvg())); 88 89 // And now create the contextual menu and add the actions to it. 90 contextMenu = new QMenu(tr("Copy Mass List"), this); 91 contextMenu->addAction(copyMonoAct); 92 contextMenu->addAction(copyAvgAct); 93 } 94 95 ~CleaveOligomerTableView()96 CleaveOligomerTableView::~CleaveOligomerTableView() 97 { 98 99 } 100 101 102 void setOligomerList(OligomerList * oligomerList)103 CleaveOligomerTableView::setOligomerList(OligomerList *oligomerList) 104 { 105 mp_oligomerList = oligomerList; 106 } 107 108 109 const OligomerList * oligomerList()110 CleaveOligomerTableView::oligomerList() 111 { 112 return mp_oligomerList; 113 } 114 115 116 CleavageDlg * parentDlg()117 CleaveOligomerTableView::parentDlg() 118 { 119 return mp_parentDlg; 120 } 121 122 void setParentDlg(CleavageDlg * dlg)123 CleaveOligomerTableView::setParentDlg(CleavageDlg *dlg) 124 { 125 Q_ASSERT(dlg); 126 mp_parentDlg = dlg; 127 } 128 129 130 int selectedOligomers(OligomerList * oligomerList,int index) const131 CleaveOligomerTableView::selectedOligomers(OligomerList *oligomerList, 132 int index) const 133 { 134 if(!oligomerList) 135 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 136 137 int count = 0; 138 139 int localIndex = 0; 140 141 // How many oligomers are there in the list passed as argument? 142 int oligomerCount = oligomerList->size(); 143 144 if(index > oligomerCount) 145 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 146 147 // If index is -1 , then we are asked to append the oligomers to 148 // the list. 149 if(index == -1) 150 localIndex = oligomerList->size(); 151 152 // For each selected oligomer, duplicate it and append to the list 153 // passed as argument. 154 155 // We first have to get the selection model for the proxy model. 156 157 QItemSelectionModel *selModel = selectionModel(); 158 159 // Now get the selection ranges. 160 161 QItemSelection proxyItemSelection = selModel->selection(); 162 163 QSortFilterProxyModel *sortModel = 164 static_cast<QSortFilterProxyModel *>(model()); 165 166 QItemSelection sourceItemSelection = 167 sortModel->mapSelectionToSource(proxyItemSelection); 168 169 QModelIndexList modelIndexList = sourceItemSelection.indexes(); 170 171 int modelIndexListSize = modelIndexList.size(); 172 173 // Attention, if we select one single row, our modelIndexList will 174 // be of size 7, because in one single row there are seven cells: 175 // each cell for each column, and there are 7 columns. Thus, when 176 // we iterate in the modelIndexList, we'll have to take care of 177 // this and make sure we are not putting each selected row's 178 // oligomer sevent times. For this, we make sure we are not 179 // handling the same row twice or more, by storing the processed 180 // rows in a list of integers and by checking for existence of 181 // that row each time a new index is processed. 182 183 QList<int> processedRowList; 184 185 for (int iter = 0; iter < modelIndexListSize; ++iter) 186 { 187 QModelIndex oligomerIndex = modelIndexList.at(iter); 188 189 Q_ASSERT(oligomerIndex.isValid()); 190 191 // Get to know what's the row of the index, so that we can get 192 // to the oligomer. 193 194 int row = oligomerIndex.row(); 195 196 if(processedRowList.contains(row)) 197 continue; 198 else 199 processedRowList.append(row); 200 201 CleaveOligomer *oligomer = 202 static_cast<CleaveOligomer *>(mp_oligomerList->at(row)); 203 204 CleaveOligomer *newOligomer = new CleaveOligomer(*oligomer); 205 206 // Create a NoDeletePointerProp, which might be used later by 207 // the user of the list of oligomers to highlight regions in 208 // the sequence editor. 209 210 NoDeletePointerProp *prop = 211 new NoDeletePointerProp("SEQUENCE_EDITOR_WND", 212 static_cast<void *> 213 (mp_parentDlg->editorWnd())); 214 215 newOligomer->appendProp(prop); 216 217 oligomerList->insert(localIndex, newOligomer); 218 219 ++localIndex; 220 ++count; 221 } 222 223 return count; 224 } 225 226 227 QString * selectedOligomersAsPlainText(QString delimiter,bool withSequence,bool forXpertMiner,MassType massType) const228 CleaveOligomerTableView::selectedOligomersAsPlainText(QString delimiter, 229 bool withSequence, 230 bool forXpertMiner, 231 MassType massType) const 232 { 233 Application *application = static_cast<Application *>(qApp); 234 QLocale locale = application->locale(); 235 236 // Let's get all the currently selected oligomers in one list. 237 OligomerList oligomerList; 238 239 // Append the selected oligomers to the empty list. 240 int appendedOligomerCount = selectedOligomers(&oligomerList, -1); 241 242 // Sanity check 243 if(appendedOligomerCount != oligomerList.size()) 244 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 245 246 // If delimiter is empty, then set to "$". 247 if (delimiter.isEmpty()) 248 delimiter = "$"; 249 250 // Allocate a string in which we describe all the selected items. 251 252 // For export to XpertMiner, we only want the masses asked for: 253 // MXT_MASS_MONO or MXT_MASS_AVG. Also, we want the format to be : 254 255 // mass <delim> charge <delim> name <delim> coords 256 257 QString *text = new QString(); 258 259 while(!oligomerList.isEmpty()) 260 { 261 CleaveOligomer *oligomer = 262 static_cast<CleaveOligomer *>(oligomerList.takeFirst()); 263 264 if(!forXpertMiner) 265 *text += QString("\n%1%2") 266 .arg(oligomer->description()) 267 .arg(delimiter); 268 269 if(forXpertMiner && massType == MXT_MASS_AVG) 270 { 271 } 272 else 273 { 274 *text += QString("%1%2") 275 .arg(oligomer->mono(locale, MXP_OLIGOMER_DEC_PLACES)) 276 .arg(delimiter); 277 } 278 279 if(forXpertMiner && massType == MXT_MASS_MONO) 280 { 281 } 282 else 283 { 284 *text += QString("%1%2") 285 .arg(oligomer->avg(locale, MXP_OLIGOMER_DEC_PLACES)) 286 .arg(delimiter); 287 } 288 289 *text += QString("%1%2") 290 .arg(oligomer->charge()) 291 .arg(delimiter); 292 293 *text += QString("%1%2") 294 .arg(oligomer->name()) 295 .arg(delimiter); 296 297 *text += QString("%1%2") 298 .arg(static_cast<CoordinateList *>(oligomer)->positionsAsText()) 299 .arg(delimiter); 300 301 if(!forXpertMiner) 302 *text += QString("%1%2") 303 .arg(oligomer->isModified()) 304 .arg(delimiter); 305 306 // We cannot export the sequence if data are for XpertMiner 307 if(!forXpertMiner && withSequence) 308 { 309 QString *sequence = oligomer->monomerText(); 310 311 *text += QString("\n%1") 312 .arg(*sequence); 313 314 delete sequence; 315 } 316 317 // Terminate the stanza 318 *text += QString("\n"); 319 320 // We can now delete the allocated oligomer, since we do not 321 // need it anymore. 322 delete oligomer; 323 } 324 325 // Terminate the string with a new line. 326 *text += QString("\n"); 327 328 return text; 329 } 330 331 332 333 void mousePressEvent(QMouseEvent * mouseEvent)334 CleaveOligomerTableView::mousePressEvent(QMouseEvent *mouseEvent) 335 { 336 if (mouseEvent->buttons() & Qt::LeftButton) 337 { 338 m_dragStartPos = mouseEvent->pos(); 339 } 340 else if (mouseEvent->buttons() & Qt::RightButton) 341 { 342 contextMenu->popup(mouseEvent->globalPos()); 343 return; 344 } 345 346 QTableView::mousePressEvent(mouseEvent); 347 } 348 349 350 void mouseMoveEvent(QMouseEvent * mouseEvent)351 CleaveOligomerTableView::mouseMoveEvent(QMouseEvent *mouseEvent) 352 { 353 if (mouseEvent->buttons() & Qt::LeftButton) 354 { 355 int distance = 356 (mouseEvent->pos() - m_dragStartPos).manhattanLength(); 357 358 if(distance >= QApplication::startDragDistance()) 359 { 360 startDrag(); 361 return; 362 } 363 } 364 365 QTableView::mousePressEvent(mouseEvent); 366 } 367 368 369 void startDrag()370 CleaveOligomerTableView::startDrag() 371 { 372 CleaveOligomerTableViewMimeData *mimeData = 373 new CleaveOligomerTableViewMimeData(this, 374 mp_parentDlg->editorWnd(), 375 mp_parentDlg); 376 377 QDrag *drag = new QDrag(this); 378 drag->setMimeData(mimeData); 379 // drag->setPixmap(QPixmap(":/images/greenled.png")); 380 drag->start(Qt::CopyAction); 381 } 382 383 384 void currentChanged(const QModelIndex & current,const QModelIndex & previous)385 CleaveOligomerTableView::currentChanged(const QModelIndex ¤t, 386 const QModelIndex &previous) 387 { 388 if (!current.isValid()) 389 return; 390 391 CleaveOligomerTableViewSortProxyModel *sortModel = 392 static_cast<CleaveOligomerTableViewSortProxyModel *>(model()); 393 394 QModelIndex sourceIndex = sortModel->mapToSource(current); 395 396 int row = sourceIndex.row(); 397 398 // Get to the list of oligomers that is referenced in this 399 // tableView (that list actually belongs to the CleavageDlg 400 // instance. 401 402 CleaveOligomer *oligomer = 403 static_cast<CleaveOligomer *>(mp_oligomerList->at(row)); 404 405 // If the oligomers obtained with the cleavage are old and the 406 // sequence has been changed since the cleavage, then the 407 // oligomers might point to a sequence element that is no more. We 408 // want to avoid such kind of errors. 409 410 if (oligomer->startIndex() >= oligomer->polymer()->size() || 411 oligomer->endIndex() >= oligomer->polymer()->size()) 412 { 413 QMessageBox::warning(this, 414 tr("massXpert - Cleavage"), 415 tr("%1@%2\n" 416 "The monomer indices do not correspond " 417 "to a valid polymer sequence range.\n" 418 "Avoid modifying the sequence while " 419 "working with cleavages.") 420 .arg(__FILE__) 421 .arg(__LINE__), 422 QMessageBox::Ok); 423 424 return; 425 } 426 427 QString *text = oligomer->monomerText(); 428 429 // We are getting text for an oligomer; it cannot be empty, 430 // because that would mean the oligomer has no monomers. In that 431 // case it is not conceivable that the oligomer be in the cleavage 432 // product list. 433 434 Q_ASSERT(!text->isEmpty()); 435 436 *text += QString(" -- %1") 437 .arg(oligomer->description()); 438 439 // Get the formula of the oligomer and display it all along. 440 441 QString formula = oligomer->elementalComposition(); 442 443 *text += QString("(formula: %1)") 444 .arg(formula); 445 446 mp_parentDlg->updateOligomerSequence(text); 447 448 delete text; 449 450 // Get the mass calculation engine's options out of the oligomer, 451 // so that we can display them correctly. 452 453 CalcOptions calcOptions = oligomer->calcOptions(); 454 455 mp_parentDlg->updateCleavageDetails(calcOptions); 456 457 QTableView::currentChanged(current, previous); 458 } 459 460 461 void itemActivated(const QModelIndex & index)462 CleaveOligomerTableView::itemActivated(const QModelIndex &index) 463 { 464 if (!index.isValid()) 465 return; 466 467 CleaveOligomerTableViewSortProxyModel *sortModel = 468 static_cast<CleaveOligomerTableViewSortProxyModel *>(model()); 469 470 QModelIndex sourceIndex = sortModel->mapToSource(index); 471 472 int row = sourceIndex.row(); 473 474 // Get to the list of oligomers that is referenced in this 475 // tableView (that list actually belongs to the CleavageDlg 476 // instance. 477 478 CleaveOligomer *oligomer = 479 static_cast<CleaveOligomer *>(mp_oligomerList->at(row)); 480 481 SequenceEditorWnd *editorWnd = mp_parentDlg->editorWnd(); 482 483 CoordinateList *coordinateList = 484 static_cast<CoordinateList *>(oligomer); 485 486 // Remove the previous selection, so that we can start fresh. 487 editorWnd->mpa_editorGraphicsView->resetSelection(); 488 489 for (int iter = 0; iter < coordinateList->size(); ++iter) 490 { 491 Coordinates *coordinates = coordinateList->at(iter); 492 493 int start = coordinates->start(); 494 int end = coordinates->end(); 495 496 if(start >= oligomer->polymer()->size() || 497 end >= oligomer->polymer()->size()) 498 { 499 QMessageBox::warning(this, 500 tr("massXpert - Cleavage"), 501 tr("%1@%2\n" 502 "The monomer indices do not correspond " 503 "to a valid polymer sequence range.\n" 504 "Avoid modifying the sequence while " 505 "working with cleavages.") 506 .arg(__FILE__) 507 .arg(__LINE__), 508 QMessageBox::Ok); 509 510 return; 511 } 512 513 editorWnd->mpa_editorGraphicsView->setSelection(*coordinates, 514 true, false); 515 } 516 517 editorWnd->updateSelectedSequenceMasses(); 518 } 519 520 521 ///////// Contextual menu for copying to clipboard of mono/avg 522 ///////// masses. 523 void copyMono()524 CleaveOligomerTableView::copyMono() 525 { 526 return copyMassList(MXT_MASS_MONO); 527 } 528 529 530 void copyAvg()531 CleaveOligomerTableView::copyAvg() 532 { 533 return copyMassList(MXT_MASS_AVG); 534 } 535 536 537 void copyMassList(int monoOrAvg)538 CleaveOligomerTableView::copyMassList(int monoOrAvg) 539 { 540 Application *application = static_cast<Application *>(qApp); 541 QLocale locale = application->locale(); 542 543 QString massList; 544 545 // We want to prepare a textual list of masses (either MONO or 546 // AVG) of all the oligomers in the tableview, exactly as they are 547 // currently displayed (that is, according to the proxy's model). 548 549 QSortFilterProxyModel *sortModel = 550 static_cast<QSortFilterProxyModel *>(model()); 551 552 // Get number of rows under the model. 553 int rowCount = sortModel->rowCount(); 554 555 for(int iter = 0; iter < rowCount; ++iter) 556 { 557 // qDebug() << __FILE__ << __LINE__ 558 // << "proxyIter:" << iter; 559 560 QModelIndex proxyIndex = sortModel->index(iter, 0); 561 QModelIndex sourceIndex = sortModel->mapToSource(proxyIndex); 562 563 int sourceRow = sourceIndex.row(); 564 565 // qDebug() << __FILE__ << __LINE__ 566 // << "sourceRow:" << sourceRow; 567 568 CleaveOligomer *oligomer = 569 static_cast<CleaveOligomer *>(mp_oligomerList->at(sourceRow)); 570 571 if(monoOrAvg == MXT_MASS_MONO) 572 massList += oligomer->mono(locale, MXP_OLIGOMER_DEC_PLACES); 573 else if (monoOrAvg == MXT_MASS_AVG) 574 massList += oligomer->avg(locale, MXP_OLIGOMER_DEC_PLACES); 575 else 576 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); 577 578 // End the mass item with a new line. 579 massList += "\n"; 580 } 581 582 if (massList.isEmpty()) 583 return; 584 585 QClipboard *clipboard = QApplication::clipboard(); 586 587 clipboard->setText(massList, QClipboard::Clipboard); 588 } 589 590 } // namespace massXpert 591