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