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 &current,
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