1 #include "commanddispatcher.hpp"
2
3 #include <algorithm>
4 #include <memory>
5
6 #include <components/misc/stringops.hpp>
7 #include <components/misc/constants.hpp>
8
9 #include "../doc/document.hpp"
10
11 #include "idtable.hpp"
12 #include "record.hpp"
13 #include "commands.hpp"
14 #include "idtableproxymodel.hpp"
15 #include "commandmacro.hpp"
16
getDeletableRecords() const17 std::vector<std::string> CSMWorld::CommandDispatcher::getDeletableRecords() const
18 {
19 std::vector<std::string> result;
20
21 IdTable& model = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
22
23 int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification);
24
25 for (std::vector<std::string>::const_iterator iter (mSelection.begin());
26 iter!=mSelection.end(); ++iter)
27 {
28 int row = model.getModelIndex (*iter, 0).row();
29
30 // check record state
31 RecordBase::State state = static_cast<RecordBase::State> (
32 model.data (model.index (row, stateColumnIndex)).toInt());
33
34 if (state==RecordBase::State_Deleted)
35 continue;
36
37 // check other columns (only relevant for a subset of the tables)
38 int dialogueTypeIndex = model.searchColumnIndex (Columns::ColumnId_DialogueType);
39
40 if (dialogueTypeIndex!=-1)
41 {
42 int type = model.data (model.index (row, dialogueTypeIndex)).toInt();
43
44 if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal)
45 continue;
46 }
47
48 result.push_back (*iter);
49 }
50
51 return result;
52 }
53
getRevertableRecords() const54 std::vector<std::string> CSMWorld::CommandDispatcher::getRevertableRecords() const
55 {
56 std::vector<std::string> result;
57
58 IdTable& model = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
59
60 /// \todo Reverting temporarily disabled on tables that support reordering, because
61 /// revert logic currently can not handle reordering.
62 if (model.getFeatures() & IdTable::Feature_ReorderWithinTopic)
63 return result;
64
65 int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification);
66
67 for (std::vector<std::string>::const_iterator iter (mSelection.begin());
68 iter!=mSelection.end(); ++iter)
69 {
70 int row = model.getModelIndex (*iter, 0).row();
71
72 // check record state
73 RecordBase::State state = static_cast<RecordBase::State> (
74 model.data (model.index (row, stateColumnIndex)).toInt());
75
76 if (state==RecordBase::State_BaseOnly)
77 continue;
78
79 result.push_back (*iter);
80 }
81
82 return result;
83 }
84
CommandDispatcher(CSMDoc::Document & document,const CSMWorld::UniversalId & id,QObject * parent)85 CSMWorld::CommandDispatcher::CommandDispatcher (CSMDoc::Document& document,
86 const CSMWorld::UniversalId& id, QObject *parent)
87 : QObject (parent), mLocked (false), mDocument (document), mId (id)
88 {}
89
setEditLock(bool locked)90 void CSMWorld::CommandDispatcher::setEditLock (bool locked)
91 {
92 mLocked = locked;
93 }
94
setSelection(const std::vector<std::string> & selection)95 void CSMWorld::CommandDispatcher::setSelection (const std::vector<std::string>& selection)
96 {
97 mSelection = selection;
98 std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace);
99 std::sort (mSelection.begin(), mSelection.end());
100 }
101
setExtendedTypes(const std::vector<UniversalId> & types)102 void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector<UniversalId>& types)
103 {
104 mExtendedTypes = types;
105 }
106
canDelete() const107 bool CSMWorld::CommandDispatcher::canDelete() const
108 {
109 if (mLocked)
110 return false;
111
112 return getDeletableRecords().size()!=0;
113 }
114
canRevert() const115 bool CSMWorld::CommandDispatcher::canRevert() const
116 {
117 if (mLocked)
118 return false;
119
120 return getRevertableRecords().size()!=0;
121 }
122
getExtendedTypes() const123 std::vector<CSMWorld::UniversalId> CSMWorld::CommandDispatcher::getExtendedTypes() const
124 {
125 std::vector<CSMWorld::UniversalId> tables;
126
127 if (mId==UniversalId::Type_Cells)
128 {
129 tables.push_back (mId);
130 tables.emplace_back(UniversalId::Type_References);
131 /// \todo add other cell-specific types
132 }
133
134 return tables;
135 }
136
executeModify(QAbstractItemModel * model,const QModelIndex & index,const QVariant & new_)137 void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_)
138 {
139 if (mLocked)
140 return;
141
142 std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell;
143
144 std::unique_ptr<CSMWorld::ModifyCommand> modifyDataRefNum;
145
146 int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt();
147
148 if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)
149 {
150 const float oldPosition = model->data (index).toFloat();
151
152 // Modulate by cell size, update cell id if reference has been moved to a new cell
153 if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits))
154 - std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) >= 0.5f)
155 {
156 IdTableProxyModel *proxy = dynamic_cast<IdTableProxyModel *> (model);
157
158 int row = proxy ? proxy->mapToSource (index).row() : index.row();
159
160 // This is not guaranteed to be the same as \a model, since a proxy could be used.
161 IdTable& model2 = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
162
163 int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell);
164
165 if (cellColumn!=-1)
166 {
167 QModelIndex cellIndex = model2.index (row, cellColumn);
168
169 std::string cellId = model2.data (cellIndex).toString().toUtf8().data();
170
171 if (cellId.find ('#')!=std::string::npos)
172 {
173 // Need to recalculate the cell and (if necessary) clear the instance's refNum
174 modifyCell.reset (new UpdateCellCommand (model2, row));
175
176 // Not sure which model this should be applied to
177 int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum);
178
179 if (refNumColumn!=-1)
180 modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0));
181 }
182 }
183 }
184 }
185
186 std::unique_ptr<CSMWorld::ModifyCommand> modifyData (
187 new CSMWorld::ModifyCommand (*model, index, new_));
188
189 if (modifyCell.get())
190 {
191 CommandMacro macro (mDocument.getUndoStack());
192 macro.push (modifyData.release());
193 macro.push (modifyCell.release());
194 if (modifyDataRefNum.get())
195 macro.push (modifyDataRefNum.release());
196 }
197 else
198 mDocument.getUndoStack().push (modifyData.release());
199 }
200
executeDelete()201 void CSMWorld::CommandDispatcher::executeDelete()
202 {
203 if (mLocked)
204 return;
205
206 std::vector<std::string> rows = getDeletableRecords();
207
208 if (rows.empty())
209 return;
210
211 IdTable& model = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
212
213 int columnIndex = model.findColumnIndex (Columns::ColumnId_Id);
214
215 CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Delete multiple records" : "");
216 for (std::vector<std::string>::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter)
217 {
218 std::string id = model.data (model.getModelIndex (*iter, columnIndex)).
219 toString().toUtf8().constData();
220
221 if (mId.getType() == UniversalId::Type_Referenceables)
222 {
223 macro.push (new CSMWorld::DeleteCommand (model, id,
224 static_cast<CSMWorld::UniversalId::Type>(model.data (model.index (
225 model.getModelIndex (id, columnIndex).row(),
226 model.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType))).toInt())));
227 }
228 else
229 mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id));
230 }
231 }
232
executeRevert()233 void CSMWorld::CommandDispatcher::executeRevert()
234 {
235 if (mLocked)
236 return;
237
238 std::vector<std::string> rows = getRevertableRecords();
239
240 if (rows.empty())
241 return;
242
243 IdTable& model = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
244
245 int columnIndex = model.findColumnIndex (Columns::ColumnId_Id);
246
247 CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Revert multiple records" : "");
248 for (std::vector<std::string>::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter)
249 {
250 std::string id = model.data (model.getModelIndex (*iter, columnIndex)).
251 toString().toUtf8().constData();
252
253 macro.push (new CSMWorld::RevertCommand (model, id));
254 }
255 }
256
executeExtendedDelete()257 void CSMWorld::CommandDispatcher::executeExtendedDelete()
258 {
259 CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended delete of multiple records") : "");
260
261 for (std::vector<UniversalId>::const_iterator iter (mExtendedTypes.begin());
262 iter!=mExtendedTypes.end(); ++iter)
263 {
264 if (*iter==mId)
265 executeDelete();
266 else if (*iter==UniversalId::Type_References)
267 {
268 IdTable& model = dynamic_cast<IdTable&> (
269 *mDocument.getData().getTableModel (*iter));
270
271 const RefCollection& collection = mDocument.getData().getReferences();
272
273 int size = collection.getSize();
274
275 for (int i=size-1; i>=0; --i)
276 {
277 const Record<CellRef>& record = collection.getRecord (i);
278
279 if (record.mState==RecordBase::State_Deleted)
280 continue;
281
282 if (!std::binary_search (mSelection.begin(), mSelection.end(),
283 Misc::StringUtils::lowerCase (record.get().mCell)))
284 continue;
285
286 macro.push (new CSMWorld::DeleteCommand (model, record.get().mId));
287 }
288 }
289 }
290 }
291
executeExtendedRevert()292 void CSMWorld::CommandDispatcher::executeExtendedRevert()
293 {
294 CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended revert of multiple records") : "");
295
296 for (std::vector<UniversalId>::const_iterator iter (mExtendedTypes.begin());
297 iter!=mExtendedTypes.end(); ++iter)
298 {
299 if (*iter==mId)
300 executeRevert();
301 else if (*iter==UniversalId::Type_References)
302 {
303 IdTable& model = dynamic_cast<IdTable&> (
304 *mDocument.getData().getTableModel (*iter));
305
306 const RefCollection& collection = mDocument.getData().getReferences();
307
308 int size = collection.getSize();
309
310 for (int i=size-1; i>=0; --i)
311 {
312 const Record<CellRef>& record = collection.getRecord (i);
313
314 if (!std::binary_search (mSelection.begin(), mSelection.end(),
315 Misc::StringUtils::lowerCase (record.get().mCell)))
316 continue;
317
318 macro.push (new CSMWorld::RevertCommand (model, record.get().mId));
319 }
320 }
321 }
322 }
323