1 #include "table.hpp"
2 
3 #include <QHeaderView>
4 #include <QAction>
5 #include <QMenu>
6 #include <QContextMenuEvent>
7 #include <QString>
8 #include <QtCore/qnamespace.h>
9 
10 #include <components/debug/debuglog.hpp>
11 #include <components/misc/helpviewer.hpp>
12 #include <components/misc/stringops.hpp>
13 
14 #include "../../model/doc/document.hpp"
15 
16 #include "../../model/world/commands.hpp"
17 #include "../../model/world/infotableproxymodel.hpp"
18 #include "../../model/world/idtableproxymodel.hpp"
19 #include "../../model/world/idtablebase.hpp"
20 #include "../../model/world/idtable.hpp"
21 #include "../../model/world/landtexturetableproxymodel.hpp"
22 #include "../../model/world/record.hpp"
23 #include "../../model/world/columns.hpp"
24 #include "../../model/world/commanddispatcher.hpp"
25 
26 #include "../../model/prefs/state.hpp"
27 #include "../../model/prefs/shortcut.hpp"
28 
29 #include "tableeditidaction.hpp"
30 #include "util.hpp"
31 
contextMenuEvent(QContextMenuEvent * event)32 void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
33 {
34     // configure dispatcher
35     mDispatcher->setSelection (getSelectedIds());
36 
37     std::vector<CSMWorld::UniversalId> extendedTypes = mDispatcher->getExtendedTypes();
38 
39     mDispatcher->setExtendedTypes (extendedTypes);
40 
41     // create context menu
42     QModelIndexList selectedRows = selectionModel()->selectedRows();
43     QMenu menu (this);
44 
45     ///  \todo add menu items for select all and clear selection
46 
47     int currentRow = rowAt(event->y());
48     int currentColumn = columnAt(event->x());
49     if (mEditIdAction->isValidIdCell(currentRow, currentColumn))
50     {
51         mEditIdAction->setCell(currentRow, currentColumn);
52         menu.addAction(mEditIdAction);
53         menu.addSeparator();
54     }
55 
56     if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
57     {
58         if (selectedRows.size()==1)
59         {
60             menu.addAction (mEditAction);
61 
62             if (mCreateAction)
63                 menu.addAction(mCloneAction);
64         }
65 
66         if (mTouchAction)
67             menu.addAction (mTouchAction);
68 
69         if (mCreateAction)
70             menu.addAction (mCreateAction);
71 
72         if (mDispatcher->canRevert())
73         {
74             menu.addAction (mRevertAction);
75 
76             if (!extendedTypes.empty())
77                 menu.addAction (mExtendedRevertAction);
78         }
79 
80         if (mDispatcher->canDelete())
81         {
82             menu.addAction (mDeleteAction);
83 
84             if (!extendedTypes.empty())
85                 menu.addAction (mExtendedDeleteAction);
86         }
87 
88         if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_ReorderWithinTopic)
89         {
90             /// \todo allow reordering of multiple rows
91             if (selectedRows.size()==1)
92             {
93                 int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic);
94 
95                 if (column==-1)
96                     column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal);
97 
98                 if (column!=-1)
99                 {
100                     int row = mProxyModel->mapToSource (
101                         mProxyModel->index (selectedRows.begin()->row(), 0)).row();
102                     QString curData = mModel->data(mModel->index(row, column)).toString();
103 
104                     if (row > 0)
105                     {
106                         QString prevData = mModel->data(mModel->index(row - 1, column)).toString();
107                         if (Misc::StringUtils::ciEqual(curData.toStdString(), prevData.toStdString()))
108                         {
109                             menu.addAction(mMoveUpAction);
110                         }
111                     }
112 
113                     if (row < mModel->rowCount() - 1)
114                     {
115                         QString nextData = mModel->data(mModel->index(row + 1, column)).toString();
116                         if (Misc::StringUtils::ciEqual(curData.toStdString(), nextData.toStdString()))
117                         {
118                             menu.addAction(mMoveDownAction);
119                         }
120                     }
121                 }
122             }
123         }
124     }
125 
126     if (selectedRows.size()==1)
127     {
128         int row = selectedRows.begin()->row();
129 
130         row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
131 
132         if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View)
133         {
134             CSMWorld::UniversalId id = mModel->view (row).first;
135 
136             int index = mDocument.getData().getCells().searchId (id.getId());
137             // index==-1: the ID references a worldspace instead of a cell (ignore for now and go
138             // ahead)
139 
140             if (index==-1 || !mDocument.getData().getCells().getRecord (index).isDeleted())
141                 menu.addAction (mViewAction);
142         }
143 
144         if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Preview)
145         {
146             const CSMWorld::UniversalId id = getUniversalId(currentRow);
147             const CSMWorld::UniversalId::Type type = id.getType();
148 
149             QModelIndex index = mModel->index (row,
150                 mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification));
151 
152             CSMWorld::RecordBase::State state = static_cast<CSMWorld::RecordBase::State> (
153                 mModel->data (index).toInt());
154 
155             if (state!=CSMWorld::RecordBase::State_Deleted && type != CSMWorld::UniversalId::Type_ItemLevelledList)
156                 menu.addAction (mPreviewAction);
157         }
158     }
159 
160     if (mHelpAction)
161         menu.addAction (mHelpAction);
162 
163     menu.exec (event->globalPos());
164 }
165 
mouseDoubleClickEvent(QMouseEvent * event)166 void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event)
167 {
168     Qt::KeyboardModifiers modifiers =
169         event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier);
170 
171     QModelIndex index = currentIndex();
172 
173     selectionModel()->select (index,
174         QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows);
175 
176     std::map<Qt::KeyboardModifiers, DoubleClickAction>::iterator iter =
177         mDoubleClickActions.find (modifiers);
178 
179     if (iter==mDoubleClickActions.end())
180     {
181         event->accept();
182         return;
183     }
184 
185     switch (iter->second)
186     {
187         case Action_None:
188 
189             event->accept();
190             break;
191 
192         case Action_InPlaceEdit:
193 
194             DragRecordTable::mouseDoubleClickEvent (event);
195             break;
196 
197         case Action_EditRecord:
198 
199             event->accept();
200             editRecord();
201             break;
202 
203         case Action_View:
204 
205             event->accept();
206             viewRecord();
207             break;
208 
209         case Action_Revert:
210 
211             event->accept();
212             if (mDispatcher->canRevert())
213                 mDispatcher->executeRevert();
214             break;
215 
216         case Action_Delete:
217 
218             event->accept();
219             if (mDispatcher->canDelete())
220                 mDispatcher->executeDelete();
221             break;
222 
223         case Action_EditRecordAndClose:
224 
225             event->accept();
226             editRecord();
227             emit closeRequest();
228             break;
229 
230         case Action_ViewAndClose:
231 
232             event->accept();
233             viewRecord();
234             emit closeRequest();
235             break;
236     }
237 }
238 
Table(const CSMWorld::UniversalId & id,bool createAndDelete,bool sorting,CSMDoc::Document & document)239 CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
240     bool createAndDelete, bool sorting, CSMDoc::Document& document)
241     : DragRecordTable(document), mCreateAction (nullptr), mCloneAction(nullptr), mTouchAction(nullptr),
242     mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false)
243 {
244     mModel = &dynamic_cast<CSMWorld::IdTableBase&> (*mDocument.getData().getTableModel (id));
245 
246     bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos ||
247                        id.getType() == CSMWorld::UniversalId::Type_JournalInfos;
248     bool isLtexTable = (id.getType() == CSMWorld::UniversalId::Type_LandTextures);
249     if (isInfoTable)
250     {
251         mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this);
252         connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords);
253     }
254     else if (isLtexTable)
255     {
256         mProxyModel = new CSMWorld::LandTextureTableProxyModel (this);
257     }
258     else
259     {
260         mProxyModel = new CSMWorld::IdTableProxyModel (this);
261     }
262     mProxyModel->setSourceModel (mModel);
263 
264     mDispatcher = new CSMWorld::CommandDispatcher (document, id, this);
265 
266     setModel (mProxyModel);
267     horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive);
268     verticalHeader()->hide();
269     setSelectionBehavior (QAbstractItemView::SelectRows);
270     setSelectionMode (QAbstractItemView::ExtendedSelection);
271 
272     setSortingEnabled (sorting);
273     if (sorting)
274     {
275         sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder);
276     }
277 
278     int columns = mModel->columnCount();
279     for (int i=0; i<columns; ++i)
280     {
281         int flags = mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt();
282 
283         if (flags & CSMWorld::ColumnBase::Flag_Table)
284         {
285             CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display> (
286                 mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
287 
288             CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display,
289                 mDispatcher, document, this);
290 
291             mDelegates.push_back (delegate);
292             setItemDelegateForColumn (i, delegate);
293         }
294         else
295             hideColumn (i);
296     }
297 
298     mEditAction = new QAction (tr ("Edit Record"), this);
299     connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
300     mEditAction->setIcon(QIcon(":edit-edit"));
301     addAction (mEditAction);
302     CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this);
303     editShortcut->associateAction(mEditAction);
304 
305     if (createAndDelete)
306     {
307         mCreateAction = new QAction (tr ("Add Record"), this);
308         connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
309         mCreateAction->setIcon(QIcon(":edit-add"));
310         addAction (mCreateAction);
311         CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this);
312         createShortcut->associateAction(mCreateAction);
313 
314         mCloneAction = new QAction (tr ("Clone Record"), this);
315         connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
316         mCloneAction->setIcon(QIcon(":edit-clone"));
317         addAction(mCloneAction);
318         CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this);
319         cloneShortcut->associateAction(mCloneAction);
320     }
321 
322     if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch)
323     {
324         mTouchAction = new QAction(tr("Touch Record"), this);
325         connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
326         mTouchAction->setIcon(QIcon(":edit-touch"));
327         addAction(mTouchAction);
328         CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
329         touchShortcut->associateAction(mTouchAction);
330     }
331 
332     mRevertAction = new QAction (tr ("Revert Record"), this);
333     connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
334     mRevertAction->setIcon(QIcon(":edit-undo"));
335     addAction (mRevertAction);
336     CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this);
337     revertShortcut->associateAction(mRevertAction);
338 
339     mDeleteAction = new QAction (tr ("Delete Record"), this);
340     connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete()));
341     mDeleteAction->setIcon(QIcon(":edit-delete"));
342     addAction (mDeleteAction);
343     CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this);
344     deleteShortcut->associateAction(mDeleteAction);
345 
346     mMoveUpAction = new QAction (tr ("Move Up"), this);
347     connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
348     mMoveUpAction->setIcon(QIcon(":record-up"));
349     addAction (mMoveUpAction);
350     CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this);
351     moveUpShortcut->associateAction(mMoveUpAction);
352 
353     mMoveDownAction = new QAction (tr ("Move Down"), this);
354     connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
355     mMoveDownAction->setIcon(QIcon(":record-down"));
356     addAction (mMoveDownAction);
357     CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this);
358     moveDownShortcut->associateAction(mMoveDownAction);
359 
360     mViewAction = new QAction (tr ("View"), this);
361     connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord()));
362     mViewAction->setIcon(QIcon(":/cell.png"));
363     addAction (mViewAction);
364     CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this);
365     viewShortcut->associateAction(mViewAction);
366 
367     mPreviewAction = new QAction (tr ("Preview"), this);
368     connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord()));
369     mPreviewAction->setIcon(QIcon(":edit-preview"));
370     addAction (mPreviewAction);
371     CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this);
372     previewShortcut->associateAction(mPreviewAction);
373 
374     mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this);
375     connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete()));
376     mExtendedDeleteAction->setIcon(QIcon(":edit-delete"));
377     addAction (mExtendedDeleteAction);
378     CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this);
379     extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
380 
381     mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this);
382     connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert()));
383     mExtendedRevertAction->setIcon(QIcon(":edit-undo"));
384     addAction (mExtendedRevertAction);
385     CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this);
386     extendedRevertShortcut->associateAction(mExtendedRevertAction);
387 
388     mEditIdAction = new TableEditIdAction (*this, this);
389     connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell()));
390     addAction (mEditIdAction);
391 
392     mHelpAction = new QAction (tr ("Help"), this);
393     connect (mHelpAction, SIGNAL (triggered()), this, SLOT (openHelp()));
394     mHelpAction->setIcon(QIcon(":/info.png"));
395     addAction (mHelpAction);
396     CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this);
397     openHelpShortcut->associateAction(mHelpAction);
398 
399     connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)),
400         this, SLOT (tableSizeUpdate()));
401 
402     connect (mProxyModel, SIGNAL (rowAdded (const std::string &)),
403         this, SLOT (rowAdded (const std::string &)));
404 
405     /// \note This signal could instead be connected to a slot that filters out changes not affecting
406     /// the records status column (for permanence reasons)
407     connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
408         this, SLOT (tableSizeUpdate()));
409 
410     connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)),
411         this, SLOT (selectionSizeUpdate ()));
412 
413     setAcceptDrops(true);
414 
415     mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_InPlaceEdit));
416     mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord));
417     mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View));
418     mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose));
419 
420     connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)),
421         this, SLOT (settingChanged (const CSMPrefs::Setting *)));
422     CSMPrefs::get()["ID Tables"].update();
423 }
424 
setEditLock(bool locked)425 void CSVWorld::Table::setEditLock (bool locked)
426 {
427     for (std::vector<CommandDelegate *>::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter)
428         (*iter)->setEditLock (locked);
429 
430     DragRecordTable::setEditLock(locked);
431     mDispatcher->setEditLock (locked);
432 }
433 
getUniversalId(int row) const434 CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const
435 {
436     row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
437 
438     int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
439     int typeColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType);
440 
441     return CSMWorld::UniversalId (
442         static_cast<CSMWorld::UniversalId::Type> (mModel->data (mModel->index (row, typeColumn)).toInt()),
443         mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData());
444 }
445 
getSelectedIds() const446 std::vector<std::string> CSVWorld::Table::getSelectedIds() const
447 {
448     std::vector<std::string> ids;
449     QModelIndexList selectedRows = selectionModel()->selectedRows();
450     int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
451 
452     for (QModelIndexList::const_iterator iter (selectedRows.begin());
453          iter != selectedRows.end();
454          ++iter)
455     {
456         int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row();
457         ids.emplace_back(mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData());
458     }
459     return ids;
460 }
461 
editRecord()462 void CSVWorld::Table::editRecord()
463 {
464     if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
465     {
466         QModelIndexList selectedRows = selectionModel()->selectedRows();
467 
468         if (selectedRows.size()==1)
469             emit editRequest (getUniversalId (selectedRows.begin()->row()), "");
470     }
471 }
472 
cloneRecord()473 void CSVWorld::Table::cloneRecord()
474 {
475     if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
476     {
477         QModelIndexList selectedRows = selectionModel()->selectedRows();
478         const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row());
479         if (selectedRows.size() == 1)
480         {
481             emit cloneRequest (toClone);
482         }
483     }
484 }
485 
touchRecord()486 void CSVWorld::Table::touchRecord()
487 {
488     if (!mEditLock && mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch)
489     {
490         std::vector<CSMWorld::UniversalId> touchIds;
491 
492         QModelIndexList selectedRows = selectionModel()->selectedRows();
493         for (auto it = selectedRows.begin(); it != selectedRows.end(); ++it)
494         {
495             touchIds.push_back(getUniversalId(it->row()));
496         }
497 
498         emit touchRequest(touchIds);
499     }
500 }
501 
moveUpRecord()502 void CSVWorld::Table::moveUpRecord()
503 {
504     if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
505         return;
506 
507     QModelIndexList selectedRows = selectionModel()->selectedRows();
508 
509     if (selectedRows.size()==1)
510     {
511         int row2 =selectedRows.begin()->row();
512 
513         if (row2>0)
514         {
515             int row = row2-1;
516 
517             row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
518             row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row();
519 
520             if (row2<=row)
521                 throw std::runtime_error ("Inconsistent row order");
522 
523             std::vector<int> newOrder (row2-row+1);
524             newOrder[0] = row2-row;
525             newOrder[row2-row] = 0;
526             for (int i=1; i<row2-row; ++i)
527                 newOrder[i] = i;
528 
529             mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand (
530                 dynamic_cast<CSMWorld::IdTable&> (*mModel), row, newOrder));
531         }
532     }
533 }
534 
moveDownRecord()535 void CSVWorld::Table::moveDownRecord()
536 {
537     if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
538         return;
539 
540     QModelIndexList selectedRows = selectionModel()->selectedRows();
541 
542     if (selectedRows.size()==1)
543     {
544         int row =selectedRows.begin()->row();
545 
546         if (row<mProxyModel->rowCount()-1)
547         {
548             int row2 = row+1;
549 
550             row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
551             row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row();
552 
553             if (row2<=row)
554                 throw std::runtime_error ("Inconsistent row order");
555 
556             std::vector<int> newOrder (row2-row+1);
557             newOrder[0] = row2-row;
558             newOrder[row2-row] = 0;
559             for (int i=1; i<row2-row; ++i)
560                 newOrder[i] = i;
561 
562             mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand (
563                 dynamic_cast<CSMWorld::IdTable&> (*mModel), row, newOrder));
564         }
565     }
566 }
567 
moveRecords(QDropEvent * event)568 void CSVWorld::Table::moveRecords(QDropEvent *event)
569 {
570     if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
571         return;
572 
573     QModelIndex targedIndex = indexAt(event->pos());
574 
575     QModelIndexList selectedRows = selectionModel()->selectedRows();
576     int targetRowRaw = targedIndex.row();
577     int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row();
578     int baseRowRaw = targedIndex.row() - 1;
579     int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row();
580     int highestDifference = 0;
581 
582     for (const auto& thisRowData : selectedRows)
583     {
584         int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row();
585         if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow);
586         if (thisRow - 1 < baseRow) baseRow = thisRow - 1;
587     }
588 
589     std::vector<int> newOrder (highestDifference + 1);
590 
591     for (long unsigned int i = 0; i < newOrder.size(); ++i)
592     {
593         newOrder[i] = i;
594     }
595 
596     if (selectedRows.size() > 1)
597     {
598         Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented.";
599         return;
600     }
601 
602     for (const auto& thisRowData : selectedRows)
603     {
604         /*
605             Moving algorithm description
606             a) Remove the (ORIGIN + 1)th list member.
607             b) Add (ORIGIN+1)th list member with value TARGET
608             c) If ORIGIN > TARGET,d_INC; ELSE d_DEC
609             d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address
610             d_DEC)  decrease all members after the ORIGIN by one, stop after hitting address TARGET
611         */
612 
613         int originRowRaw = thisRowData.row();
614         int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row();
615 
616         newOrder.erase(newOrder.begin() +  originRow - baseRow - 1);
617         newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1);
618 
619         if (originRow > targetRow)
620         {
621             for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i)
622             {
623                 ++newOrder[i];
624             }
625         }
626         else
627         {
628             for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i)
629             {
630                 --newOrder[i];
631             }
632         }
633 
634     }
635     mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand (
636         dynamic_cast<CSMWorld::IdTable&> (*mModel), baseRow + 1, newOrder));
637 }
638 
editCell()639 void CSVWorld::Table::editCell()
640 {
641     emit editRequest(mEditIdAction->getCurrentId(), "");
642 }
643 
openHelp()644 void CSVWorld::Table::openHelp()
645 {
646     Misc::HelpViewer::openHelp("manuals/openmw-cs/tables.html");
647 }
648 
viewRecord()649 void CSVWorld::Table::viewRecord()
650 {
651     if (!(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View))
652         return;
653 
654     QModelIndexList selectedRows = selectionModel()->selectedRows();
655 
656     if (selectedRows.size()==1)
657     {
658         int row = selectedRows.begin()->row();
659 
660         row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
661 
662         std::pair<CSMWorld::UniversalId, std::string> params = mModel->view (row);
663 
664         if (params.first.getType()!=CSMWorld::UniversalId::Type_None)
665             emit editRequest (params.first, params.second);
666     }
667 }
668 
previewRecord()669 void CSVWorld::Table::previewRecord()
670 {
671     QModelIndexList selectedRows = selectionModel()->selectedRows();
672 
673     if (selectedRows.size()==1)
674     {
675         std::string id = getUniversalId (selectedRows.begin()->row()).getId();
676 
677         QModelIndex index = mModel->getModelIndex (id,
678             mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification));
679 
680         if (mModel->data (index)!=CSMWorld::RecordBase::State_Deleted)
681             emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id),
682                 "");
683     }
684 }
685 
executeExtendedDelete()686 void CSVWorld::Table::executeExtendedDelete()
687 {
688     if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue())
689     {
690         emit extendedDeleteConfigRequest(getSelectedIds());
691     }
692     else
693     {
694         QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection);
695     }
696 }
697 
executeExtendedRevert()698 void CSVWorld::Table::executeExtendedRevert()
699 {
700     if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue())
701     {
702         emit extendedRevertConfigRequest(getSelectedIds());
703     }
704     else
705     {
706         QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection);
707     }
708 }
709 
settingChanged(const CSMPrefs::Setting * setting)710 void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting)
711 {
712     if (*setting=="ID Tables/jump-to-added")
713     {
714         if (setting->toString()=="Jump and Select")
715         {
716             mJumpToAddedRecord = true;
717             mUnselectAfterJump = false;
718         }
719         else if (setting->toString()=="Jump Only")
720         {
721             mJumpToAddedRecord = true;
722             mUnselectAfterJump = true;
723         }
724         else // No Jump
725         {
726             mJumpToAddedRecord = false;
727             mUnselectAfterJump = false;
728         }
729     }
730     else if (*setting=="Records/type-format" || *setting=="Records/status-format")
731     {
732         int columns = mModel->columnCount();
733 
734         for (int i=0; i<columns; ++i)
735             if (QAbstractItemDelegate *delegate = itemDelegateForColumn (i))
736             {
737                 dynamic_cast<CommandDelegate&> (*delegate).settingChanged (setting);
738                 emit dataChanged (mModel->index (0, i),
739                     mModel->index (mModel->rowCount()-1, i));
740             }
741     }
742     else if (setting->getParent()->getKey()=="ID Tables" &&
743         setting->getKey().substr (0, 6)=="double")
744     {
745         std::string modifierString = setting->getKey().substr (6);
746 
747         Qt::KeyboardModifiers modifiers;
748 
749         if (modifierString=="-s")
750             modifiers = Qt::ShiftModifier;
751         else if (modifierString=="-c")
752             modifiers = Qt::ControlModifier;
753         else if (modifierString=="-sc")
754             modifiers = Qt::ShiftModifier | Qt::ControlModifier;
755 
756         DoubleClickAction action = Action_None;
757 
758         std::string value = setting->toString();
759 
760         if (value=="Edit in Place")
761             action = Action_InPlaceEdit;
762         else if (value=="Edit Record")
763             action = Action_EditRecord;
764         else if (value=="View")
765             action = Action_View;
766         else if (value=="Revert")
767             action = Action_Revert;
768         else if (value=="Delete")
769             action = Action_Delete;
770         else if (value=="Edit Record and Close")
771             action = Action_EditRecordAndClose;
772         else if (value=="View and Close")
773             action = Action_ViewAndClose;
774 
775         mDoubleClickActions[modifiers] = action;
776     }
777 }
778 
tableSizeUpdate()779 void CSVWorld::Table::tableSizeUpdate()
780 {
781     int size = 0;
782     int deleted = 0;
783     int modified = 0;
784 
785     if (mProxyModel->columnCount()>0)
786     {
787         int rows = mProxyModel->rowCount();
788 
789         int columnIndex = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Modification);
790 
791         if (columnIndex!=-1)
792         {
793             for (int i=0; i<rows; ++i)
794             {
795                 QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (i, 0));
796 
797                 int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt();
798 
799                 switch (state)
800                 {
801                     case CSMWorld::RecordBase::State_BaseOnly: ++size; break;
802                     case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break;
803                     case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break;
804                     case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break;
805                 }
806             }
807         }
808         else
809             size = rows;
810     }
811 
812     emit tableSizeChanged (size, deleted, modified);
813 }
814 
selectionSizeUpdate()815 void CSVWorld::Table::selectionSizeUpdate()
816 {
817     emit selectionSizeChanged (selectionModel()->selectedRows().size());
818 }
819 
requestFocus(const std::string & id)820 void CSVWorld::Table::requestFocus (const std::string& id)
821 {
822     QModelIndex index = mProxyModel->getModelIndex (id, 0);
823 
824     if (index.isValid())
825     {
826         // This will scroll to the row.
827         selectRow (index.row());
828         // This will actually select it.
829         selectionModel()->select (index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
830     }
831 }
832 
recordFilterChanged(std::shared_ptr<CSMFilter::Node> filter)833 void CSVWorld::Table::recordFilterChanged (std::shared_ptr<CSMFilter::Node> filter)
834 {
835     mProxyModel->setFilter (filter);
836     tableSizeUpdate();
837     selectionSizeUpdate();
838 }
839 
mouseMoveEvent(QMouseEvent * event)840 void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event)
841 {
842     if (event->buttons() & Qt::LeftButton)
843     {
844         startDragFromTable(*this);
845     }
846 }
847 
getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const848 std::vector<std::string> CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const
849 {
850     const int count = mModel->columnCount();
851 
852     std::vector<std::string> titles;
853     for (int i = 0; i < count; ++i)
854     {
855         CSMWorld::ColumnBase::Display columndisplay = static_cast<CSMWorld::ColumnBase::Display>
856                                                      (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
857 
858         if (display == columndisplay)
859         {
860             titles.emplace_back(mModel->headerData (i, Qt::Horizontal).toString().toUtf8().constData());
861         }
862     }
863     return titles;
864 }
865 
getDraggedRecords() const866 std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const
867 {
868     QModelIndexList selectedRows = selectionModel()->selectedRows();
869     std::vector<CSMWorld::UniversalId> idToDrag;
870 
871     for (QModelIndex& it : selectedRows)
872         idToDrag.push_back (getUniversalId (it.row()));
873 
874     return idToDrag;
875 }
876 
rowAdded(const std::string & id)877 void CSVWorld::Table::rowAdded(const std::string &id)
878 {
879     tableSizeUpdate();
880     if(mJumpToAddedRecord)
881     {
882         int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
883         selectRow(mProxyModel->getModelIndex(id, idColumn).row());
884 
885         if(mUnselectAfterJump)
886             clearSelection();
887     }
888 }
889