1 #include "scriptsubview.hpp"
2
3 #include <stdexcept>
4
5 #include <QStatusBar>
6 #include <QStackedLayout>
7 #include <QSplitter>
8 #include <QTimer>
9
10 #include <components/debug/debuglog.hpp>
11
12 #include "../../model/doc/document.hpp"
13 #include "../../model/world/universalid.hpp"
14 #include "../../model/world/data.hpp"
15 #include "../../model/world/commands.hpp"
16 #include "../../model/world/idtable.hpp"
17 #include "../../model/prefs/state.hpp"
18
19 #include "scriptedit.hpp"
20 #include "recordbuttonbar.hpp"
21 #include "tablebottombox.hpp"
22 #include "genericcreator.hpp"
23 #include "scripterrortable.hpp"
24
addButtonBar()25 void CSVWorld::ScriptSubView::addButtonBar()
26 {
27 if (mButtons)
28 return;
29
30 mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this);
31
32 mLayout.insertWidget (1, mButtons);
33
34 connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int)));
35
36 connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)),
37 mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&)));
38 }
39
recompile()40 void CSVWorld::ScriptSubView::recompile()
41 {
42 if (!mCompileDelay->isActive() && !isDeleted())
43 mCompileDelay->start (CSMPrefs::get()["Scripts"]["compile-delay"].toInt());
44 }
45
isDeleted() const46 bool CSVWorld::ScriptSubView::isDeleted() const
47 {
48 return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt()
49 ==CSMWorld::RecordBase::State_Deleted;
50 }
51
updateDeletedState()52 void CSVWorld::ScriptSubView::updateDeletedState()
53 {
54 if (isDeleted())
55 {
56 mErrors->clear();
57 adjustSplitter();
58 mEditor->setEnabled (false);
59 }
60 else
61 {
62 mEditor->setEnabled (true);
63 recompile();
64 }
65 }
66
adjustSplitter()67 void CSVWorld::ScriptSubView::adjustSplitter()
68 {
69 QList<int> sizes;
70
71 if (mErrors->rowCount())
72 {
73 if (mErrors->height())
74 return; // keep old height if the error panel was already open
75
76 sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight;
77 }
78 else
79 {
80 if (mErrors->height())
81 mErrorHeight = mErrors->height();
82
83 sizes << 1 << 0;
84 }
85
86 mMain->setSizes (sizes);
87 }
88
ScriptSubView(const CSMWorld::UniversalId & id,CSMDoc::Document & document)89 CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document)
90 : SubView (id), mDocument (document), mColumn (-1), mBottom(nullptr), mButtons (nullptr),
91 mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())),
92 mErrorHeight (CSMPrefs::get()["Scripts"]["error-height"].toInt())
93 {
94 std::vector<std::string> selection (1, id.getId());
95 mCommandDispatcher.setSelection (selection);
96
97 mMain = new QSplitter (this);
98 mMain->setOrientation (Qt::Vertical);
99 mLayout.addWidget (mMain, 2);
100
101 mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this);
102 mMain->addWidget (mEditor);
103 mMain->setCollapsible (0, false);
104
105 mErrors = new ScriptErrorTable (document, this);
106 mMain->addWidget (mErrors);
107
108 QList<int> sizes;
109 sizes << 1 << 0;
110 mMain->setSizes (sizes);
111
112 QWidget *widget = new QWidget (this);;
113 widget->setLayout (&mLayout);
114 setWidget (widget);
115
116 mModel = &dynamic_cast<CSMWorld::IdTable&> (
117 *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts));
118
119 mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText);
120 mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
121 mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification);
122
123 QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString();
124
125 mEditor->setPlainText (source);
126 // bottom box and buttons
127 mBottom = new TableBottomBox (CreatorFactory<GenericCreator>(), document, id, this);
128
129 connect (mBottom, SIGNAL (requestFocus (const std::string&)),
130 this, SLOT (switchToId (const std::string&)));
131
132 mLayout.addWidget (mBottom);
133
134 // signals
135 connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged()));
136
137 connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
138 this, SLOT (dataChanged (const QModelIndex&, const QModelIndex&)));
139
140 connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)),
141 this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int)));
142
143 updateStatusBar();
144 connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar()));
145
146 mErrors->update (source.toUtf8().constData());
147
148 connect (mErrors, SIGNAL (highlightError (int, int)),
149 this, SLOT (highlightError (int, int)));
150
151 mCompileDelay = new QTimer (this);
152 mCompileDelay->setSingleShot (true);
153 connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest()));
154
155 updateDeletedState();
156
157 connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)),
158 this, SLOT (settingChanged (const CSMPrefs::Setting *)));
159 CSMPrefs::get()["Scripts"].update();
160 }
161
setStatusBar(bool show)162 void CSVWorld::ScriptSubView::setStatusBar (bool show)
163 {
164 mBottom->setStatusBar (show);
165 }
166
settingChanged(const CSMPrefs::Setting * setting)167 void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting)
168 {
169 if (*setting=="Scripts/toolbar")
170 {
171 if (setting->isTrue())
172 {
173 addButtonBar();
174 }
175 else if (mButtons)
176 {
177 mLayout.removeWidget (mButtons);
178 delete mButtons;
179 mButtons = nullptr;
180 }
181 }
182 else if (*setting=="Scripts/compile-delay")
183 {
184 mCompileDelay->setInterval (setting->toInt());
185 }
186 else if (*setting=="Scripts/warnings")
187 recompile();
188 }
189
updateStatusBar()190 void CSVWorld::ScriptSubView::updateStatusBar ()
191 {
192 mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1,
193 mEditor->textCursor().columnNumber() + 1);
194 }
195
setEditLock(bool locked)196 void CSVWorld::ScriptSubView::setEditLock (bool locked)
197 {
198 mEditor->setReadOnly (locked);
199
200 if (mButtons)
201 mButtons->setEditLock (locked);
202
203 mCommandDispatcher.setEditLock (locked);
204 }
205
useHint(const std::string & hint)206 void CSVWorld::ScriptSubView::useHint (const std::string& hint)
207 {
208 if (hint.empty())
209 return;
210
211 unsigned line = 0, column = 0;
212 char c;
213 std::istringstream stream (hint.c_str()+1);
214 switch(hint[0])
215 {
216 case 'R':
217 case 'r':
218 {
219 QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
220 QString source = mModel->data (index).toString();
221 unsigned stringSize = source.length();
222 unsigned pos, dummy;
223 if (!(stream >> c >> dummy >> pos) )
224 return;
225
226 if (pos > stringSize)
227 {
228 Log(Debug::Warning) << "CSVWorld::ScriptSubView: requested position is higher than actual string length";
229 pos = stringSize;
230 }
231
232 for (unsigned i = 0; i <= pos; ++i)
233 {
234 if (source[i] == '\n')
235 {
236 ++line;
237 column = i+1;
238 }
239 }
240 column = pos - column;
241 break;
242 }
243 case 'l':
244 if (!(stream >> c >> line >> column))
245 return;
246 }
247
248 QTextCursor cursor = mEditor->textCursor();
249
250 cursor.movePosition (QTextCursor::Start);
251 if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line))
252 cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column);
253
254 mEditor->setFocus();
255 mEditor->setTextCursor (cursor);
256 }
257
textChanged()258 void CSVWorld::ScriptSubView::textChanged()
259 {
260 if (mEditor->isChangeLocked())
261 return;
262
263 ScriptEdit::ChangeLock lock (*mEditor);
264
265 QString source = mEditor->toPlainText();
266
267 mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel,
268 mModel->getModelIndex (getUniversalId().getId(), mColumn), source));
269
270 recompile();
271 }
272
dataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)273 void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
274 {
275 if (mEditor->isChangeLocked())
276 return;
277
278 ScriptEdit::ChangeLock lock (*mEditor);
279
280 bool updateRequired = false;
281
282 for (int i=topLeft.row(); i<=bottomRight.row(); ++i)
283 {
284 std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData();
285 if (mErrors->clearLocals (id))
286 updateRequired = true;
287 }
288
289 QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
290
291 if (index.row()>=topLeft.row() && index.row()<=bottomRight.row())
292 {
293 if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column())
294 updateDeletedState();
295
296 if (mColumn>=topLeft.column() && mColumn<=bottomRight.column())
297 {
298 QString source = mModel->data (index).toString();
299
300 QTextCursor cursor = mEditor->textCursor();
301 mEditor->setPlainText (source);
302 mEditor->setTextCursor (cursor);
303
304 updateRequired = true;
305 }
306 }
307
308 if (updateRequired)
309 recompile();
310 }
311
rowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)312 void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end)
313 {
314 bool updateRequired = false;
315
316 for (int i=start; i<=end; ++i)
317 {
318 std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData();
319 if (mErrors->clearLocals (id))
320 updateRequired = true;
321 }
322
323 if (updateRequired)
324 recompile();
325
326 QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
327
328 if (!parent.isValid() && index.row()>=start && index.row()<=end)
329 emit closeRequest();
330 }
331
switchToRow(int row)332 void CSVWorld::ScriptSubView::switchToRow (int row)
333 {
334 int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
335 std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData();
336 setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id));
337
338 bool oldSignalsState = mEditor->blockSignals( true );
339 mEditor->setPlainText( mModel->data(mModel->index(row, mColumn)).toString() );
340 mEditor->blockSignals( oldSignalsState );
341
342 std::vector<std::string> selection (1, id);
343 mCommandDispatcher.setSelection (selection);
344
345 updateDeletedState();
346 }
347
switchToId(const std::string & id)348 void CSVWorld::ScriptSubView::switchToId (const std::string& id)
349 {
350 switchToRow (mModel->getModelIndex (id, 0).row());
351 }
352
highlightError(int line,int column)353 void CSVWorld::ScriptSubView::highlightError (int line, int column)
354 {
355 QTextCursor cursor = mEditor->textCursor();
356
357 cursor.movePosition (QTextCursor::Start);
358 if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line))
359 cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column);
360
361 mEditor->setFocus();
362 mEditor->setTextCursor (cursor);
363 }
364
updateRequest()365 void CSVWorld::ScriptSubView::updateRequest()
366 {
367 QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
368
369 QString source = mModel->data (index).toString();
370
371 mErrors->update (source.toUtf8().constData());
372
373 adjustSplitter();
374 }
375