Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951#include "table.hpp"
#include <QAction>#include <QContextMenuEvent>#include <QHeaderView>#include <QMenu>#include <QMetaObject>#include <QString>
#include <algorithm>#include <stdexcept>#include <type_traits>#include <utility>
#include <apps/opencs/model/prefs/category.hpp>#include <apps/opencs/model/prefs/setting.hpp>#include <apps/opencs/model/world/columnbase.hpp>#include <apps/opencs/model/world/columns.hpp>#include <apps/opencs/model/world/data.hpp>#include <apps/opencs/model/world/idcollection.hpp>#include <apps/opencs/model/world/idtableproxymodel.hpp>#include <apps/opencs/model/world/record.hpp>#include <apps/opencs/model/world/universalid.hpp>#include <apps/opencs/view/world/dragrecordtable.hpp>
#include <components/debug/debuglog.hpp>#include <components/misc/helpviewer.hpp>#include <components/misc/scalableicon.hpp>#include <components/misc/strings/algorithm.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/world/commanddispatcher.hpp"#include "../../model/world/commands.hpp"#include "../../model/world/idtable.hpp"#include "../../model/world/idtablebase.hpp"#include "../../model/world/infotableproxymodel.hpp"#include "../../model/world/landtexturetableproxymodel.hpp"
#include "../../model/prefs/shortcut.hpp"#include "../../model/prefs/state.hpp"
#include "tableeditidaction.hpp"#include "tableheadermouseeventhandler.hpp"#include "util.hpp"
namespace CSMFilter{ class Node;}
void CSVWorld::Table::contextMenuEvent(QContextMenuEvent* event){ // configure dispatcher mDispatcher->setSelection(getSelectedIds());
std::vector<CSMWorld::UniversalId> extendedTypes = mDispatcher->getExtendedTypes();
mDispatcher->setExtendedTypes(extendedTypes);
// create context menu QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this);
/// \todo add menu items for select all and clear selection
int currentRow = rowAt(event->y()); int currentColumn = columnAt(event->x()); if (mEditIdAction->setCell(currentRow, currentColumn)) { menu.addAction(mEditIdAction); menu.addSeparator(); }
if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { if (selectedRows.size() == 1) { menu.addAction(mEditAction);
if (mCreateAction) menu.addAction(mCloneAction); }
if (mTouchAction) menu.addAction(mTouchAction);
if (mCreateAction) menu.addAction(mCreateAction);
if (mDispatcher->canRevert()) { menu.addAction(mRevertAction);
if (!extendedTypes.empty()) menu.addAction(mExtendedRevertAction); }
if (mDispatcher->canDelete()) { menu.addAction(mDeleteAction);
if (!extendedTypes.empty()) menu.addAction(mExtendedDeleteAction); }
if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows if (selectedRows.size() == 1) { int column = mModel->searchColumnIndex(CSMWorld::Columns::ColumnId_Topic);
if (column == -1) column = mModel->searchColumnIndex(CSMWorld::Columns::ColumnId_Journal);
if (column != -1) { int row = mProxyModel->mapToSource(mProxyModel->index(selectedRows.begin()->row(), 0)).row(); QString curData = mModel->data(mModel->index(row, column)).toString();
if (row > 0) { QString prevData = mModel->data(mModel->index(row - 1, column)).toString(); if (Misc::StringUtils::ciEqual(curData.toStdString(), prevData.toStdString())) { menu.addAction(mMoveUpAction); } }
if (row < mModel->rowCount() - 1) { QString nextData = mModel->data(mModel->index(row + 1, column)).toString(); if (Misc::StringUtils::ciEqual(curData.toStdString(), nextData.toStdString())) { menu.addAction(mMoveDownAction); } } } } } }
if (selectedRows.size() == 1) { int row = selectedRows.begin()->row();
row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row();
if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View) { CSMWorld::UniversalId id = mModel->view(row).first;
const int index = mDocument.getData().getCells().searchId(ESM::RefId::stringRefId(id.getId())); // index==-1: the ID references a worldspace instead of a cell (ignore for now and go // ahead)
if (index == -1 || !mDocument.getData().getCells().getRecord(index).isDeleted()) menu.addAction(mViewAction); }
if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Preview) { const CSMWorld::UniversalId id = getUniversalId(currentRow); const CSMWorld::UniversalId::Type type = id.getType();
QModelIndex index = mModel->index(row, mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification));
CSMWorld::RecordBase::State state = static_cast<CSMWorld::RecordBase::State>(mModel->data(index).toInt());
if (state != CSMWorld::RecordBase::State_Deleted && type != CSMWorld::UniversalId::Type_ItemLevelledList) menu.addAction(mPreviewAction); } }
if (mHelpAction) menu.addAction(mHelpAction);
menu.exec(event->globalPos());}
void CSVWorld::Table::mouseDoubleClickEvent(QMouseEvent* event){ Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier);
QModelIndex index = currentIndex();
selectionModel()->select( index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows);
std::map<Qt::KeyboardModifiers, DoubleClickAction>::iterator iter = mDoubleClickActions.find(modifiers);
if (iter == mDoubleClickActions.end()) { event->accept(); return; }
switch (iter->second) { case Action_None:
event->accept(); break;
case Action_InPlaceEdit:
DragRecordTable::mouseDoubleClickEvent(event); break;
case Action_EditRecord:
event->accept(); editRecord(); break;
case Action_View:
event->accept(); viewRecord(); break;
case Action_Revert:
event->accept(); if (mDispatcher->canRevert()) mDispatcher->executeRevert(); break;
case Action_Delete:
event->accept(); if (mDispatcher->canDelete()) mDispatcher->executeDelete(); break;
case Action_EditRecordAndClose:
event->accept(); editRecord(); emit closeRequest(); break;
case Action_ViewAndClose:
event->accept(); viewRecord(); emit closeRequest(); break; }}
CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : DragRecordTable(document) , mCreateAction(nullptr) , mCloneAction(nullptr) , mTouchAction(nullptr) , mRecordStatusDisplay(0) , mJumpToAddedRecord(false) , mUnselectAfterJump(false) , mAutoJump(false){ mModel = &dynamic_cast<CSMWorld::IdTableBase&>(*mDocument.getData().getTableModel(id));
bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || id.getType() == CSMWorld::UniversalId::Type_JournalInfos; bool isLtexTable = (id.getType() == CSMWorld::UniversalId::Type_LandTextures); if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); connect(this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); connect(this, &CSVWorld::DragRecordTable::createNewInfoRecord, this, &CSVWorld::Table::createRecordsDirectlyRequest); } else if (isLtexTable) { mProxyModel = new CSMWorld::LandTextureTableProxyModel(this); } else { mProxyModel = new CSMWorld::IdTableProxyModel(this); } mProxyModel->setSourceModel(mModel);
mDispatcher = new CSMWorld::CommandDispatcher(document, id, this);
setModel(mProxyModel); horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); // The number is arbitrary but autoresize would be way too slow otherwise. constexpr int autoResizePrecision = 500; horizontalHeader()->setResizeContentsPrecision(autoResizePrecision); resizeColumnsToContents(); verticalHeader()->hide(); setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::ExtendedSelection);
int columns = mModel->columnCount(); for (int i = 0; i < columns; ++i) { int flags = mModel->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt();
if (flags & CSMWorld::ColumnBase::Flag_Table) { CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display>( mModel->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
CommandDelegate* delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, mDispatcher, document, this);
mDelegates.push_back(delegate); setItemDelegateForColumn(i, delegate); } else hideColumn(i); }
if (sorting) { // FIXME: some tables (e.g. CellRef) have this column hidden, which makes it confusing sortByColumn(mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); } setSortingEnabled(sorting);
mEditAction = new QAction(tr("Edit Record"), this); connect(mEditAction, &QAction::triggered, this, &Table::editRecord); mEditAction->setIcon(Misc::ScalableIcon::load(":edit-edit")); addAction(mEditAction); CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this); editShortcut->associateAction(mEditAction);
if (createAndDelete) { mCreateAction = new QAction(tr("Add Record"), this); connect(mCreateAction, &QAction::triggered, this, &Table::createRequest); mCreateAction->setIcon(Misc::ScalableIcon::load(":edit-add")); addAction(mCreateAction); CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this); createShortcut->associateAction(mCreateAction);
mCloneAction = new QAction(tr("Clone Record"), this); connect(mCloneAction, &QAction::triggered, this, &Table::cloneRecord); mCloneAction->setIcon(Misc::ScalableIcon::load(":edit-clone")); addAction(mCloneAction); CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this); cloneShortcut->associateAction(mCloneAction); }
if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) { mTouchAction = new QAction(tr("Touch Record"), this); connect(mTouchAction, &QAction::triggered, this, &Table::touchRecord); mTouchAction->setIcon(Misc::ScalableIcon::load(":edit-touch")); addAction(mTouchAction); CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this); touchShortcut->associateAction(mTouchAction); }
mRevertAction = new QAction(tr("Revert Record"), this); connect(mRevertAction, &QAction::triggered, mDispatcher, &CSMWorld::CommandDispatcher::executeRevert); mRevertAction->setIcon(Misc::ScalableIcon::load(":edit-undo")); addAction(mRevertAction); CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this); revertShortcut->associateAction(mRevertAction);
mDeleteAction = new QAction(tr("Delete Record"), this); connect(mDeleteAction, &QAction::triggered, mDispatcher, &CSMWorld::CommandDispatcher::executeDelete); mDeleteAction->setIcon(Misc::ScalableIcon::load(":edit-delete")); addAction(mDeleteAction); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this); deleteShortcut->associateAction(mDeleteAction);
mMoveUpAction = new QAction(tr("Move Up"), this); connect(mMoveUpAction, &QAction::triggered, this, &Table::moveUpRecord); mMoveUpAction->setIcon(Misc::ScalableIcon::load(":record-up")); addAction(mMoveUpAction); CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this); moveUpShortcut->associateAction(mMoveUpAction);
mMoveDownAction = new QAction(tr("Move Down"), this); connect(mMoveDownAction, &QAction::triggered, this, &Table::moveDownRecord); mMoveDownAction->setIcon(Misc::ScalableIcon::load(":record-down")); addAction(mMoveDownAction); CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this); moveDownShortcut->associateAction(mMoveDownAction);
mViewAction = new QAction(tr("View"), this); connect(mViewAction, &QAction::triggered, this, &Table::viewRecord); mViewAction->setIcon(Misc::ScalableIcon::load(":cell")); addAction(mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction);
mPreviewAction = new QAction(tr("Preview"), this); connect(mPreviewAction, &QAction::triggered, this, &Table::previewRecord); mPreviewAction->setIcon(Misc::ScalableIcon::load(":edit-preview")); addAction(mPreviewAction); CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this); previewShortcut->associateAction(mPreviewAction);
mExtendedDeleteAction = new QAction(tr("Extended Delete Record"), this); connect(mExtendedDeleteAction, &QAction::triggered, this, &Table::executeExtendedDelete); mExtendedDeleteAction->setIcon(Misc::ScalableIcon::load(":edit-delete")); addAction(mExtendedDeleteAction); CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this); extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
mExtendedRevertAction = new QAction(tr("Extended Revert Record"), this); connect(mExtendedRevertAction, &QAction::triggered, this, &Table::executeExtendedRevert); mExtendedRevertAction->setIcon(Misc::ScalableIcon::load(":edit-undo")); addAction(mExtendedRevertAction); CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this); extendedRevertShortcut->associateAction(mExtendedRevertAction);
mEditIdAction = new TableEditIdAction(*this, mDocument.getData(), this); connect(mEditIdAction, &QAction::triggered, this, &Table::editCell); addAction(mEditIdAction);
mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &Table::openHelp); mHelpAction->setIcon(Misc::ScalableIcon::load(":info")); addAction(mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction);
connect(mProxyModel, &CSMWorld::IdTableProxyModel::rowsRemoved, this, &Table::tableSizeUpdate);
connect(mProxyModel, &CSMWorld::IdTableProxyModel::rowAdded, this, &Table::rowAdded);
/// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) connect(mProxyModel, &CSMWorld::IdTableProxyModel::dataChanged, this, &Table::dataChangedEvent);
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &Table::selectionSizeUpdate);
setAcceptDrops(true);
mDoubleClickActions.insert(std::make_pair(Qt::NoModifier, Action_InPlaceEdit)); mDoubleClickActions.insert(std::make_pair(Qt::ShiftModifier, Action_EditRecord)); mDoubleClickActions.insert(std::make_pair(Qt::ControlModifier, Action_View)); mDoubleClickActions.insert(std::make_pair(Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose));
connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &Table::settingChanged); CSMPrefs::get()["ID Tables"].update();
new TableHeaderMouseEventHandler(this);}
void CSVWorld::Table::setEditLock(bool locked){ for (std::vector<CommandDelegate*>::iterator iter(mDelegates.begin()); iter != mDelegates.end(); ++iter) (*iter)->setEditLock(locked);
DragRecordTable::setEditLock(locked); mDispatcher->setEditLock(locked);}
CSMWorld::UniversalId CSVWorld::Table::getUniversalId(int row) const{ row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row();
int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); int typeColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
return CSMWorld::UniversalId( static_cast<CSMWorld::UniversalId::Type>(mModel->data(mModel->index(row, typeColumn)).toInt()), mModel->data(mModel->index(row, idColumn)).toString().toUtf8().constData());}
std::vector<std::string> CSVWorld::Table::getSelectedIds() const{ std::vector<std::string> ids; QModelIndexList selectedRows = selectionModel()->selectedRows(); int columnIndex = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { int row = mProxyModel->mapToSource(mProxyModel->index(iter->row(), 0)).row(); ids.emplace_back(mModel->data(mModel->index(row, columnIndex)).toString().toUtf8().constData()); } return ids;}
void CSVWorld::Table::editRecord(){ if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size() == 1) emit editRequest(getUniversalId(selectedRows.begin()->row()), ""); }}
void CSVWorld::Table::cloneRecord(){ if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { QModelIndexList selectedRows = selectionModel()->selectedRows(); const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); if (selectedRows.size() == 1) { emit cloneRequest(toClone); } }}
void CSVWorld::Table::touchRecord(){ if (!mEditLock && mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) { std::vector<CSMWorld::UniversalId> touchIds;
QModelIndexList selectedRows = selectionModel()->selectedRows(); for (auto it = selectedRows.begin(); it != selectedRows.end(); ++it) { touchIds.push_back(getUniversalId(it->row())); }
emit touchRequest(touchIds); }}
void CSVWorld::Table::moveUpRecord(){ if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return;
QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size() == 1) { int row2 = selectedRows.begin()->row();
if (row2 > 0) { int row = row2 - 1;
row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); row2 = mProxyModel->mapToSource(mProxyModel->index(row2, 0)).row();
if (row2 <= row) throw std::runtime_error("Inconsistent row order");
std::vector<int> newOrder(row2 - row + 1); newOrder[0] = row2 - row; newOrder[row2 - row] = 0; for (int i = 1; i < row2 - row; ++i) newOrder[i] = i;
mDocument.getUndoStack().push( new CSMWorld::ReorderRowsCommand(dynamic_cast<CSMWorld::IdTable&>(*mModel), row, newOrder)); } }}
void CSVWorld::Table::moveDownRecord(){ if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return;
QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size() == 1) { int row = selectedRows.begin()->row();
if (row < mProxyModel->rowCount() - 1) { int row2 = row + 1;
row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); row2 = mProxyModel->mapToSource(mProxyModel->index(row2, 0)).row();
if (row2 <= row) throw std::runtime_error("Inconsistent row order");
std::vector<int> newOrder(row2 - row + 1); newOrder[0] = row2 - row; newOrder[row2 - row] = 0; for (int i = 1; i < row2 - row; ++i) newOrder[i] = i;
mDocument.getUndoStack().push( new CSMWorld::ReorderRowsCommand(dynamic_cast<CSMWorld::IdTable&>(*mModel), row, newOrder)); } }}
void CSVWorld::Table::moveRecords(QDropEvent* event){ if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return;
QModelIndex targedIndex = indexAt(event->position().toPoint());
QModelIndexList selectedRows = selectionModel()->selectedRows(); int targetRowRaw = targedIndex.row(); int targetRow = mProxyModel->mapToSource(mProxyModel->index(targetRowRaw, 0)).row(); int baseRowRaw = targedIndex.row() - 1; int baseRow = mProxyModel->mapToSource(mProxyModel->index(baseRowRaw, 0)).row(); int highestDifference = 0;
for (const auto& thisRowData : selectedRows) { int thisRow = mProxyModel->mapToSource(mProxyModel->index(thisRowData.row(), 0)).row(); if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow); if (thisRow - 1 < baseRow) baseRow = thisRow - 1; }
std::vector<int> newOrder(highestDifference + 1);
for (int i = 0; i <= highestDifference; ++i) { newOrder[i] = i; }
if (selectedRows.size() > 1) { Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented."; return; }
for (const auto& thisRowData : selectedRows) { /* Moving algorithm description a) Remove the (ORIGIN + 1)th list member. b) Add (ORIGIN+1)th list member with value TARGET c) If ORIGIN > TARGET,d_INC; ELSE d_DEC d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET */
int originRowRaw = thisRowData.row(); int originRow = mProxyModel->mapToSource(mProxyModel->index(originRowRaw, 0)).row();
newOrder.erase(newOrder.begin() + originRow - baseRow - 1); newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1);
if (originRow > targetRow) { for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i) { ++newOrder[i]; } } else { for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i) { --newOrder[i]; } } } mDocument.getUndoStack().push( new CSMWorld::ReorderRowsCommand(dynamic_cast<CSMWorld::IdTable&>(*mModel), baseRow + 1, newOrder));}
void CSVWorld::Table::editCell(){ emit editRequest(mEditIdAction->getCurrentId(), "");}
void CSVWorld::Table::openHelp(){ Misc::HelpViewer::openHelp("manuals/openmw-cs/tables.html");}
void CSVWorld::Table::viewRecord(){ if (!(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View)) return;
QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size() == 1) { int row = selectedRows.begin()->row();
row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row();
std::pair<CSMWorld::UniversalId, std::string> params = mModel->view(row);
if (params.first.getType() != CSMWorld::UniversalId::Type_None) emit editRequest(params.first, params.second); }}
void CSVWorld::Table::previewRecord(){ QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size() == 1) { CSMWorld::UniversalId id = getUniversalId(selectedRows.begin()->row());
QModelIndex index = mModel->getModelIndex(id.getId(), mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification));
if (mModel->data(index) != CSMWorld::RecordBase::State_Deleted) emit editRequest(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, id), ""); }}
void CSVWorld::Table::executeExtendedDelete(){ if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { emit extendedDeleteConfigRequest(getSelectedIds()); } else { QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection); }}
void CSVWorld::Table::executeExtendedRevert(){ if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { emit extendedRevertConfigRequest(getSelectedIds()); } else { QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection); }}
void CSVWorld::Table::settingChanged(const CSMPrefs::Setting* setting){ if (*setting == "ID Tables/jump-to-added") { if (setting->toString() == "Jump and Select") { mJumpToAddedRecord = true; mUnselectAfterJump = false; } else if (setting->toString() == "Jump Only") { mJumpToAddedRecord = true; mUnselectAfterJump = true; } else // No Jump { mJumpToAddedRecord = false; mUnselectAfterJump = false; } } else if (*setting == "Records/type-format" || *setting == "Records/status-format") { int columns = mModel->columnCount();
for (int i = 0; i < columns; ++i) if (QAbstractItemDelegate* delegate = itemDelegateForColumn(i)) { dynamic_cast<CommandDelegate&>(*delegate).settingChanged(setting); emit dataChanged(mModel->index(0, i), mModel->index(mModel->rowCount() - 1, i)); } } else if (setting->getParent()->getKey() == "ID Tables" && setting->getKey().substr(0, 6) == "double") { std::string modifierString = setting->getKey().substr(6);
Qt::KeyboardModifiers modifiers;
if (modifierString == "-s") modifiers = Qt::ShiftModifier; else if (modifierString == "-c") modifiers = Qt::ControlModifier; else if (modifierString == "-sc") modifiers = Qt::ShiftModifier | Qt::ControlModifier;
DoubleClickAction action = Action_None;
std::string value = setting->toString();
if (value == "Edit in Place") action = Action_InPlaceEdit; else if (value == "Edit Record") action = Action_EditRecord; else if (value == "View") action = Action_View; else if (value == "Revert") action = Action_Revert; else if (value == "Delete") action = Action_Delete; else if (value == "Edit Record and Close") action = Action_EditRecordAndClose; else if (value == "View and Close") action = Action_ViewAndClose;
mDoubleClickActions[modifiers] = action; }}
void CSVWorld::Table::tableSizeUpdate(){ int size = 0; int deleted = 0; int modified = 0;
if (mProxyModel->columnCount() > 0) { int rows = mProxyModel->rowCount();
int columnIndex = mModel->searchColumnIndex(CSMWorld::Columns::ColumnId_Modification);
if (columnIndex != -1) { for (int i = 0; i < rows; ++i) { QModelIndex index = mProxyModel->mapToSource(mProxyModel->index(i, 0));
int state = mModel->data(mModel->index(index.row(), columnIndex)).toInt();
switch (state) { case CSMWorld::RecordBase::State_BaseOnly: ++size; break; case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; case CSMWorld::RecordBase::State_Deleted: ++deleted; ++modified; break; } } } else size = rows; }
emit tableSizeChanged(size, deleted, modified);}
void CSVWorld::Table::selectionSizeUpdate(){ emit selectionSizeChanged(selectionModel()->selectedRows().size());}
void CSVWorld::Table::requestFocus(const std::string& id){ QModelIndex index = mProxyModel->getModelIndex(id, 0);
if (index.isValid()) { // This will scroll to the row. selectRow(index.row()); // This will actually select it. selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); }}
void CSVWorld::Table::recordFilterChanged(std::shared_ptr<CSMFilter::Node> filter){ mProxyModel->setFilter(filter); tableSizeUpdate(); selectionSizeUpdate();}
void CSVWorld::Table::mouseMoveEvent(QMouseEvent* event){ if (event->buttons() & Qt::LeftButton) { startDragFromTable(*this, indexAt(event->position().toPoint())); }}
std::vector<std::string> CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const{ const int count = mModel->columnCount();
std::vector<std::string> titles; for (int i = 0; i < count; ++i) { CSMWorld::ColumnBase::Display columndisplay = static_cast<CSMWorld::ColumnBase::Display>( mModel->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
if (display == columndisplay) { titles.emplace_back(mModel->headerData(i, Qt::Horizontal).toString().toUtf8().constData()); } } return titles;}
std::vector<CSMWorld::UniversalId> CSVWorld::Table::getDraggedRecords() const{ QModelIndexList selectedRows = selectionModel()->selectedRows(); std::vector<CSMWorld::UniversalId> idToDrag;
for (QModelIndex& it : selectedRows) idToDrag.push_back(getUniversalId(it.row()));
return idToDrag;}
// parent, start and end depend on the model sending the signal, in this case mProxyModel//// If, for example, mModel was used instead, then scrolTo() should use the index// mProxyModel->mapFromSource(mModel->index(end, 0))void CSVWorld::Table::rowAdded(const std::string& id){ tableSizeUpdate(); if (mJumpToAddedRecord) { int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); int end = mProxyModel->getModelIndex(id, idColumn).row(); selectRow(end);
// without this delay the scroll works but goes to top for add/clone QMetaObject::invokeMethod(this, "queuedScrollTo", Qt::QueuedConnection, Q_ARG(int, end));
if (mUnselectAfterJump) clearSelection(); }}
void CSVWorld::Table::queuedScrollTo(int row){ scrollTo(mProxyModel->index(row, 0), QAbstractItemView::PositionAtCenter);}
void CSVWorld::Table::dataChangedEvent(const QModelIndex& topLeft, const QModelIndex& bottomRight){ tableSizeUpdate();
if (mAutoJump) { selectRow(bottomRight.row()); scrollTo(bottomRight, QAbstractItemView::PositionAtCenter); }}
void CSVWorld::Table::jumpAfterModChanged(int state){ if (state == Qt::Checked) mAutoJump = true; else mAutoJump = false;}