1 /* Copyright (c) 2015  Gerald Knizia
2  *
3  * This file is part of the IboView program (see: http://www.iboview.org)
4  *
5  * IboView is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * IboView is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
16  *
17  * Please see IboView documentation in README.txt for:
18  * -- A list of included external software and their licenses. The included
19  *    external software's copyright is not touched by this agreement.
20  * -- Notes on re-distribution and contributions to/further development of
21  *    the IboView software
22  */
23 
24 #include <algorithm>
25 #include <QTextStream>
26 #include <QItemSelectionModel>
27 #include <QProgressDialog>
28 
29 #include "Iv.h"
30 #include "IvEditFramesForm.h"
31 #include "ui_EditFramesForm.h"
32 #include "IvDocument.h"
33 
34 
35 
FFrameListModel(FDocument * pDocument_,QObject * Parent_)36 FFrameListModel::FFrameListModel(FDocument *pDocument_, QObject *Parent_)
37    : QAbstractTableModel(Parent_), m_pDocument(pDocument_)
38 {
39    ConnectForwardSignals();
40 }
41 
~FFrameListModel()42 FFrameListModel::~FFrameListModel()
43 {
44 }
45 
46 
rowCount(const QModelIndex & parent) const47 int FFrameListModel::rowCount(const QModelIndex &parent) const
48 {
49    return m_pDocument->GetNumFrames();
50    IR_SUPPRESS_UNUSED_WARNING(parent);
51 }
52 
columnCount(const QModelIndex & parent) const53 int FFrameListModel::columnCount(const QModelIndex &parent) const
54 {
55    return 2; // ID, measure, name
56    IR_SUPPRESS_UNUSED_WARNING(parent);
57 }
58 
headerData(int section,Qt::Orientation orientation,int role) const59 QVariant FFrameListModel::headerData(int section, Qt::Orientation orientation, int role) const
60 {
61    if (role == Qt::DisplayRole && orientation == Qt::Vertical) {
62       FFrame
63          *pFrame = m_pDocument->GetFrame(section, false);
64       if (pFrame == 0)
65          return QVariant("#UNK");
66       return IvFmt("%1", section); // frame number.
67    }
68    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
69       if (section == 0)
70          return QVariant("s (Arc)");
71       if (section == 1)
72          return QVariant("Frame");
73       return QVariant("#UNK");
74    }
75    if ( role == Qt::FontRole ) {
76          QFont CaptionFont;
77          CaptionFont.setBold(true);
78          return CaptionFont;
79    }
80    return QVariant();
81 }
82 
data(const QModelIndex & index,int role) const83 QVariant FFrameListModel::data(const QModelIndex &index, int role) const
84 {
85    if (role == Qt::DisplayRole) {
86 //       FMeasure const
87 //          *pMeasure = GetMeasure(index);
88 //       FFrame
89 //          *pFrame = m_pDocument->GetFrame(index.row(),false);
90 //       if (pFrame == 0 || pMeasure == 0)
91 //          return QVariant("#UNK");
92 //       return QVariant(pMeasure->MeasureFrame(pFrame,FMeasure::FORMAT_ValueOnly));
93       FFrame
94          *pFrame = m_pDocument->GetFrame(index.row(), false);
95       if (index.column() == 1 && pFrame != 0)
96          return QVariant(pFrame->GetFullInputFileName());
97    }
98    if (role == Qt::TextAlignmentRole) {
99       if (index.column() == 0)
100          return QVariant(Qt::AlignRight);
101       else
102          return QVariant(Qt::AlignLeft);
103    }
104    return QVariant();
105 }
106 
removeRows(int row,int count,const QModelIndex & parent)107 bool FFrameListModel::removeRows(int row, int count, const QModelIndex &parent)
108 {
109    if (count < 0 || row + count >= rowCount()) {
110       IV_NOTIFY(NOTIFY_Warning, "Attempted to remove non-existent frame.");
111       return false;
112    }
113 //    beginRemoveRows(parent, row, row + count - 1);
114 //    FMeasureList::iterator
115 //       itFirst = m_List.begin() + row,
116 //       itLast = itFirst + count;
117 //    m_List.erase(itFirst, itLast);
118 //    endRemoveRows();
119 //    return true;
120    return false;
121    IR_SUPPRESS_UNUSED_WARNING(parent);
122 }
123 
makeRowIndex(int iRow)124 QModelIndex FFrameListModel::makeRowIndex(int iRow)
125 {
126    return createIndex(iRow, 0, (void*)0);
127 }
128 
129 
ConnectForwardSignals()130 void FFrameListModel::ConnectForwardSignals()
131 {
132    // forward those things... to deal with newly inserted frames etc.
133    connect(m_pDocument, SIGNAL(layoutAboutToBeChanged()), this, SLOT(parentLayoutAboutToBeChanged()));
134    connect(m_pDocument, SIGNAL(layoutChanged()), this, SLOT(parentLayoutChanged()));
135 }
136 
parentLayoutAboutToBeChanged()137 void FFrameListModel::parentLayoutAboutToBeChanged()
138 {
139    emit layoutAboutToBeChanged();
140 }
141 
parentLayoutChanged()142 void FFrameListModel::parentLayoutChanged()
143 {
144    emit layoutChanged();
145 }
146 
147 
148 
FEditFramesForm(FDocument * document,QWidget * parent)149 FEditFramesForm::FEditFramesForm(FDocument *document, QWidget *parent)
150    : QDialog(parent),
151      ui(new Ui::EditFramesForm),
152      m_pDocument(document),
153      m_pFrameList(new FFrameListModel(document, this)),
154      m_OrbitalRelinkNeeded(false)
155 {
156    ui->setupUi(this);
157 //    ui->label_WorkSpace->setVisible(false);
158 //    ui->spinBox_WorkSpacePerThread->setVisible(false);
159 //    ui->spinBox_WorkSpacePerThread->setEnabled(false);
160 
161 //    ui->tabWidget->layout()->setContentsMargins(0, 0, 0, 0); // left, top, right, bottom
162 //    connect(ui->checkBox_RunIbba, SIGNAL(toggled(bool)), this, SLOT(ToggleIbbaPage(bool)));
163 
164    ui->tableView_FrameList->setModel(m_pFrameList);
165 
166    UpdateExcludedAtoms();
167 
168    connect(ui->toolButton_MoveUp, SIGNAL(clicked()), this, SLOT(moveFramesUp()));
169    connect(ui->toolButton_MoveDown, SIGNAL(clicked()), this, SLOT(moveFramesDown()));
170    connect(ui->toolButton_MoveToTop, SIGNAL(clicked()), this, SLOT(moveFramesToTop()));
171    connect(ui->toolButton_MoveToBottom, SIGNAL(clicked()), this, SLOT(moveFramesToBottom()));
172    connect(ui->toolButton_ReverseFrames, SIGNAL(clicked()), this, SLOT(reverseFrameOrder()));
173    connect(ui->toolButton_DeleteSelected, SIGNAL(clicked()), this, SLOT(deleteFrames()));
174    connect(ui->toolButton_AlignFrames, SIGNAL(clicked()), this, SLOT(alignFrames()));
175 
176    ui->buttonBox->setFocus();
177 //    setAttribute(Qt::WA_DeleteOnClose); // should I do this?
178 }
179 
~FEditFramesForm()180 FEditFramesForm::~FEditFramesForm()
181 {
182    delete ui;
183 }
184 
185 
UpdateExcludedAtoms()186 void FEditFramesForm::UpdateExcludedAtoms()
187 {
188    // todo: go though atom flags of document and check which ones have the flag set.
189    int
190       nAt = m_pDocument->nAtomsWithFlags();
191    QString
192       s;
193    QTextStream
194       str(&s);
195    str << "Atoms Excluded: ";
196    int nAtomsExcluded = 0;
197    for (int iAt = 0; iAt < nAt; ++ iAt) {
198       if (m_pDocument->IsAtomExcludedFromAlignment(iAt)) {
199          if (nAtomsExcluded != 0)
200             str << " | ";
201          str << m_pDocument->AtomLabel(iAt);
202          nAtomsExcluded += 1;
203       }
204    }
205 //    if (nAtomsExcluded <= 0) {
206 //       ui->label_ExcludedAtoms->setText("(no atoms excluded.)");
207 //    } else {
208 //       ui->label_ExcludedAtoms->setText(s);
209 //    }
210    if (nAtomsExcluded <= 0)
211       str << "None";
212    ui->label_ExcludedAtoms->setText(s);
213 }
214 
215 
InvertOrder(FFrameIndexList & iAll_,FFrameIndexList & iSelected_)216 static void InvertOrder(FFrameIndexList &iAll_, FFrameIndexList &iSelected_)
217 {
218    FFrameIndexList
219       iAll,
220       iSelected;
221    iAll.reserve(iAll_.size());
222    size_t
223       n = iAll_.size(),
224       s = iSelected_.size();
225    for (size_t i = n-1; i < n; -- i)
226       iAll.push_back(iAll_[i]);
227    iSelected.reserve(iSelected_.size());
228    for (size_t i = s-1; i < s; -- i)
229       iSelected.push_back(n - iSelected_[i] - 1);
230    iAll_.swap(iAll);
231    iSelected_.swap(iSelected);
232 }
233 
MoveSelectedUp(FFrameIndexList & iAll,FFrameIndexList const & iSelected)234 static void MoveSelectedUp(FFrameIndexList &iAll, FFrameIndexList const &iSelected)
235 {
236    if (iAll.empty() || iSelected.empty())
237       return; // no frames there or nothing to move.
238 //    assert(iAll[0] == 0 || iAll.empty());
239    assert(iAll[0] == 0 || iAll[0] == int(iAll.size()-1));
240 
241    // note: this logic is for handling cases like that:
242    //   #Frame   Selected?
243    //     0        yes
244    //     1        yes
245    //     2        no
246    //     3        no
247    //     4        yes
248    //     5        yes
249    // In this case everything which can be moved up, should be. The new order would
250    // become:  0 1 2 4 5 3. But Frames 0 and 1 cannot be moved up, since they already
251    // are at the top positions.
252    int i = 0;
253 //    while (iSelected[i] == iAll[i] && i < int(iSelected.size()))
254    while (iSelected[i] == i && i < int(iSelected.size()))
255       ++ i;
256    for (; i < int(iSelected.size()); ++ i)
257    {
258       int
259          iOld = iSelected[i],
260          iNew = iOld - 1;
261       assert(iNew > 0);
262       std::swap(iAll[iOld], iAll[iNew]);
263    }
264 }
265 
isSelected(FFrameIndexList::value_type iVal,FFrameIndexList const & iSelected)266 static bool isSelected(FFrameIndexList::value_type iVal, FFrameIndexList const &iSelected)
267 {
268    FFrameIndexList::const_iterator
269       itSel = std::lower_bound(iSelected.begin(), iSelected.end(), iVal);
270    return itSel != iSelected.end() && *itSel == iVal;
271 }
272 
MoveSelectedToTop(FFrameIndexList & iAll_,FFrameIndexList const & iSelected)273 static void MoveSelectedToTop(FFrameIndexList &iAll_, FFrameIndexList const &iSelected)
274 {
275    // algorithm requires selected items to be sorted and mutually distinct.
276 // #ifdef _DEBUG
277    for (size_t i = 1; i < iSelected.size(); ++ i)
278       assert(iSelected[i-1] < iSelected[i]);
279 // #endif
280    // start out with the selected items.
281    FFrameIndexList
282       iAll;
283    iAll.reserve(iAll_.size());
284    for (size_t i = 0; i < iSelected.size(); ++ i)
285       iAll.push_back(iAll_[size_t(iSelected[i])]);
286 
287    // add all other items in original order, unless they were already included in the
288    // sorted subset.
289    for (size_t i = 0; i < iAll_.size(); ++ i) {
290       if (!isSelected(int(i), iSelected))
291          iAll.push_back(iAll_[i]);
292    }
293    iAll_.swap(iAll);
294 }
295 
296 
moveFramesUp()297 void FEditFramesForm::moveFramesUp()
298 {
299    FFrameIndexList
300       iAll = GetBaseFrameOrder(),
301       iSelected = GetSelectedFrameIndices();
302    MoveSelectedUp(iAll, iSelected);
303    SetNewFrameOrder(iAll);
304 }
305 
moveFramesDown()306 void FEditFramesForm::moveFramesDown()
307 {
308    FFrameIndexList
309       iAll = GetBaseFrameOrder(),
310       iSelected = GetSelectedFrameIndices();
311    InvertOrder(iAll, iSelected);
312    MoveSelectedUp(iAll, iSelected);
313    InvertOrder(iAll, iSelected);
314    SetNewFrameOrder(iAll);
315 }
316 
moveFramesToTop()317 void FEditFramesForm::moveFramesToTop()
318 {
319    FFrameIndexList
320       iAll = GetBaseFrameOrder(),
321       iSelected = GetSelectedFrameIndices();
322    MoveSelectedToTop(iAll, iSelected);
323    SetNewFrameOrder(iAll);
324 }
325 
moveFramesToBottom()326 void FEditFramesForm::moveFramesToBottom()
327 {
328    FFrameIndexList
329       iAll = GetBaseFrameOrder(),
330       iSelected = GetSelectedFrameIndices();
331    InvertOrder(iAll, iSelected);
332    MoveSelectedToTop(iAll, iSelected);
333    InvertOrder(iAll, iSelected);
334    SetNewFrameOrder(iAll);
335 }
336 
reverseFrameOrder()337 void FEditFramesForm::reverseFrameOrder()
338 {
339    FFrameIndexList
340       iAll = GetBaseFrameOrder(),
341       iSelected = GetSelectedFrameIndices();
342    if (iAll.empty())
343       return;
344    if (iSelected.empty() || iSelected.size() == 1u)
345       // nothing selected? invert entire list.
346       iSelected = iAll;
347    assert(iAll[0] == 0 && iAll.back() == int(iAll.size() - 1));
348 
349    // go through the first half of the selected elements, and exchange
350    // them with the second half. In case of odd number, center element stays
351    // where it is.
352    for (size_t i = 0; i != iSelected.size()/2; ++ i) {
353       size_t
354          j = iSelected.size() - i - 1;
355       assert(i < j);
356       size_t
357          iFrame = iSelected[i],
358          jFrame = iSelected[j];
359       assert(iFrame << iAll.size() && jFrame < iAll.size());
360       std::swap(iAll[iFrame], iAll[jFrame]);
361    }
362 //    for (size_t i = 0; i != iAll.size(); ++ i)
363 //       IvEmit("  NewFrame %1:   %2", i, iAll[i]  );
364    SetNewFrameOrder(iAll);
365 }
366 
deleteFrames()367 void FEditFramesForm::deleteFrames()
368 {
369    FFrameIndexList
370       iAll = GetBaseFrameOrder(),
371       iNew,
372       iSelected = GetSelectedFrameIndices();
373    if (iAll.empty() || iSelected.empty())
374       return;
375 
376    // go through the first half of the selected elements, and exchange
377    // them with the second half. In case of odd number, center element stays
378    // where it is.
379    for (size_t i = 0; i != iAll.size(); ++ i) {
380       if (!isSelected(i, iSelected))
381          iNew.push_back(iAll[i]);
382    }
383    SetNewFrameOrder(iNew, false);
384    // ^- false: do not update selection. In practice
385    // this means that the cursor stays in the row it was in before, which
386    // now points to the next frame. (good for deleting stuff sequentially)
387 }
388 
alignFrames()389 void FEditFramesForm::alignFrames()
390 {
391    QString
392       AlignMode;
393    switch(ui->comboBox_AlignWeight->currentIndex()) {
394       case 0: AlignMode = "mass"; break;
395       case 1: AlignMode = "charge"; break;
396       case 2: AlignMode = "coords"; break; // every atom gets the same weight (except excluded ones)
397    }
398    m_pDocument->AlignFrames(AlignMode);
399    m_OrbitalRelinkNeeded = true;
400 
401    // FIXME: once IRC coords are printed, we should also update them now...
402 
403    // what about LinkVisualConfigs?
404 }
405 
406 
SetNewFrameOrder(FFrameIndexList const & NewIndices,bool UpdateSelection)407 void FEditFramesForm::SetNewFrameOrder(FFrameIndexList const &NewIndices, bool UpdateSelection)
408 {
409    FFrameIndexList
410       iSelectedOld = GetSelectedFrameIndices();
411 
412    m_pDocument->ReorderOrRestrictFrameSet(NewIndices);
413 
414    if (UpdateSelection) {
415       // keep track of selection: Select new subset of frames which was selected before.
416       QItemSelection
417          newSelection;
418       for (size_t i = 0; i < NewIndices.size(); ++ i) {
419          if (isSelected(NewIndices[i], iSelectedOld))
420             newSelection.push_back(QItemSelectionRange(m_pFrameList->makeRowIndex(int(i))));
421       }
422       QItemSelectionModel
423          *selectionModel = ui->tableView_FrameList->selectionModel();
424       selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
425    }
426    m_OrbitalRelinkNeeded = true;
427 }
428 
GetBaseFrameOrder()429 FFrameIndexList FEditFramesForm::GetBaseFrameOrder()
430 {
431    FFrameIndexList r;
432    r.reserve(m_pDocument->GetNumFrames());
433    for (int iFrame = 0; iFrame < m_pDocument->GetNumFrames(); ++ iFrame)
434       r.push_back(iFrame);
435    return r;
436 }
437 
GetSelectedFrameIndices()438 FFrameIndexList FEditFramesForm::GetSelectedFrameIndices()
439 {
440    QItemSelectionModel
441       *selectionModel = ui->tableView_FrameList->selectionModel();
442    QModelIndexList
443       selectedRows = selectionModel->selectedRows();
444    FFrameIndexList
445       r;
446    r.reserve(selectedRows.size());
447    for (int i = 0; i < selectedRows.size(); ++ i)
448       r.push_back(selectedRows[i].row());
449    std::sort(r.begin(), r.end());
450    return r;
451 }
452 
453 
454 // this doesn't work reliably...
455 // only called if the window is closed by pressing X, not via "ok"/"cancel",
456 // or other means.
457 
458 // void FEditFramesForm::closeEvent(QCloseEvent *event)
459 // {
460 //    IvEmit("ENTERED CLOSE-EVENT.");
461 //    if (m_OrbitalRelinkNeeded) {
462 //       IvEmit("ENTERED RE-LINK ORBITALS.");
463 //       QProgressDialog
464 //          progress("Re-Linking corresponding orbitals...", QString(), 0, m_pDocument->GetNumFrames(), this);
465 //       progress.setMinimumDuration(0);
466 //       progress.setWindowModality(Qt::WindowModal);
467 //       progress.setValue(0);
468 //       m_pDocument->LinkOrbitals();
469 //       progress.setValue(m_pDocument->GetNumFrames());
470 //    }
471 //
472 //    QDialog::closeEvent(event);
473 // }
474 
DoPostProcessingOnClose()475 void FEditFramesForm::DoPostProcessingOnClose()
476 {
477    IvEmit("ENTERED POST-PROCESS-EVENT.");
478    if (m_OrbitalRelinkNeeded) {
479       IvEmit("ENTERED RE-LINK ORBITALS.");
480       QProgressDialog
481          progress("Re-Linking corresponding orbitals...", QString(), 0, m_pDocument->GetNumFrames(), this);
482       progress.setMinimumDuration(0);
483       progress.setWindowModality(Qt::WindowModal);
484       progress.setValue(0);
485       m_pDocument->LinkOrbitals();
486       progress.setValue(m_pDocument->GetNumFrames());
487    }
488 }
489