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