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