You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/apps/opencs/view/world/scriptsubview.cpp

380 lines
11 KiB
C++

#include "scriptsubview.hpp"
#include <istream>
#include <vector>
#include <QSplitter>
#include <QTimer>
#include <apps/opencs/model/prefs/category.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/model/world/columns.hpp>
#include <apps/opencs/model/world/commanddispatcher.hpp>
#include <apps/opencs/model/world/record.hpp>
#include <apps/opencs/view/doc/subview.hpp>
#include <apps/opencs/view/world/creator.hpp>
#include <apps/opencs/view/world/scripthighlighter.hpp>
#include <components/debug/debuglog.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/universalid.hpp"
#include "genericcreator.hpp"
#include "recordbuttonbar.hpp"
#include "scriptedit.hpp"
#include "scripterrortable.hpp"
#include "tablebottombox.hpp"
void CSVWorld::ScriptSubView::addButtonBar()
{
if (mButtons)
return;
mButtons = new RecordButtonBar(getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this);
mLayout.insertWidget(1, mButtons);
connect(mButtons, &RecordButtonBar::switchToRow, this, &ScriptSubView::switchToRow);
connect(this, &ScriptSubView::universalIdChanged, mButtons, &RecordButtonBar::universalIdChanged);
}
void CSVWorld::ScriptSubView::recompile()
{
if (!mCompileDelay->isActive() && !isDeleted())
mCompileDelay->start(CSMPrefs::get()["Scripts"]["compile-delay"].toInt());
}
bool CSVWorld::ScriptSubView::isDeleted() const
{
return mModel->data(mModel->getModelIndex(getUniversalId().getId(), mStateColumn)).toInt()
== CSMWorld::RecordBase::State_Deleted;
}
void CSVWorld::ScriptSubView::updateDeletedState()
{
if (isDeleted())
{
mErrors->clear();
adjustSplitter();
mEditor->setEnabled(false);
}
else
{
mEditor->setEnabled(true);
recompile();
}
}
void CSVWorld::ScriptSubView::adjustSplitter()
{
QList<int> sizes;
if (mErrors->rowCount())
{
if (mErrors->height())
return; // keep old height if the error panel was already open
sizes << (mMain->height() - mErrorHeight - mMain->handleWidth()) << mErrorHeight;
}
else
{
if (mErrors->height())
mErrorHeight = mErrors->height();
sizes << 1 << 0;
}
mMain->setSizes(sizes);
}
CSVWorld::ScriptSubView::ScriptSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document)
: SubView(id)
, mDocument(document)
, mColumn(-1)
, mBottom(nullptr)
, mButtons(nullptr)
, mCommandDispatcher(document, CSMWorld::UniversalId::getParentType(id.getType()))
, mErrorHeight(CSMPrefs::get()["Scripts"]["error-height"].toInt())
{
std::vector<std::string> selection(1, id.getId());
mCommandDispatcher.setSelection(selection);
mMain = new QSplitter(this);
mMain->setOrientation(Qt::Vertical);
mLayout.addWidget(mMain, 2);
mEditor = new ScriptEdit(mDocument, ScriptHighlighter::Mode_General, this);
mMain->addWidget(mEditor);
mMain->setCollapsible(0, false);
mErrors = new ScriptErrorTable(document, this);
mMain->addWidget(mErrors);
QList<int> sizes;
sizes << 1 << 0;
mMain->setSizes(sizes);
QWidget* widget = new QWidget(this);
widget->setLayout(&mLayout);
setWidget(widget);
mModel = &dynamic_cast<CSMWorld::IdTable&>(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Scripts));
mColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_ScriptText);
mIdColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
mStateColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification);
QString source = mModel->data(mModel->getModelIndex(id.getId(), mColumn)).toString();
mEditor->setPlainText(source);
// bottom box and buttons
mBottom = new TableBottomBox(CreatorFactory<GenericCreator>(), document, id, this);
connect(mBottom, &TableBottomBox::requestFocus, this, &ScriptSubView::switchToId);
mLayout.addWidget(mBottom);
// signals
connect(mEditor, &ScriptEdit::textChanged, this, &ScriptSubView::textChanged);
connect(mModel, &CSMWorld::IdTable::dataChanged, this, &ScriptSubView::dataChanged);
connect(mModel, &CSMWorld::IdTable::rowsAboutToBeRemoved, this, &ScriptSubView::rowsAboutToBeRemoved);
updateStatusBar();
connect(mEditor, &ScriptEdit::cursorPositionChanged, this, &ScriptSubView::updateStatusBar);
mErrors->update(source.toUtf8().constData());
connect(mErrors, &ScriptErrorTable::highlightError, this, &ScriptSubView::highlightError);
mCompileDelay = new QTimer(this);
mCompileDelay->setSingleShot(true);
connect(mCompileDelay, &QTimer::timeout, this, &ScriptSubView::updateRequest);
updateDeletedState();
connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &ScriptSubView::settingChanged);
CSMPrefs::get()["Scripts"].update();
}
void CSVWorld::ScriptSubView::setStatusBar(bool show)
{
mBottom->setStatusBar(show);
}
void CSVWorld::ScriptSubView::settingChanged(const CSMPrefs::Setting* setting)
{
if (*setting == "Scripts/toolbar")
{
if (setting->isTrue())
{
addButtonBar();
}
else if (mButtons)
{
mLayout.removeWidget(mButtons);
delete mButtons;
mButtons = nullptr;
}
}
else if (*setting == "Scripts/compile-delay")
{
mCompileDelay->setInterval(setting->toInt());
}
else if (*setting == "Scripts/warnings")
recompile();
}
void CSVWorld::ScriptSubView::updateStatusBar()
{
mBottom->positionChanged(mEditor->textCursor().blockNumber() + 1, mEditor->textCursor().columnNumber() + 1);
}
void CSVWorld::ScriptSubView::setEditLock(bool locked)
{
mEditor->setReadOnly(locked);
if (mButtons)
mButtons->setEditLock(locked);
mCommandDispatcher.setEditLock(locked);
}
void CSVWorld::ScriptSubView::useHint(const std::string& hint)
{
if (hint.empty())
return;
int line = 0, column = 0;
char c;
std::istringstream stream(hint.c_str() + 1);
switch (hint[0])
{
case 'R':
case 'r':
{
QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn);
QString source = mModel->data(index).toString();
int stringSize = static_cast<int>(source.length());
int pos, dummy;
if (!(stream >> c >> dummy >> pos))
return;
if (pos > stringSize)
{
Log(Debug::Warning)
<< "CSVWorld::ScriptSubView: requested position is higher than actual string length";
pos = stringSize;
}
for (int i = 0; i <= pos; ++i)
{
if (source[i] == '\n')
{
++line;
column = i + 1;
}
}
column = pos - column;
break;
}
case 'l':
if (!(stream >> c >> line >> column))
return;
}
QTextCursor cursor = mEditor->textCursor();
cursor.movePosition(QTextCursor::Start);
if (cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line))
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
mEditor->setFocus();
mEditor->setTextCursor(cursor);
}
void CSVWorld::ScriptSubView::textChanged()
{
if (mEditor->isChangeLocked())
return;
ScriptEdit::ChangeLock lock(*mEditor);
QString source = mEditor->toPlainText();
mDocument.getUndoStack().push(
new CSMWorld::ModifyCommand(*mModel, mModel->getModelIndex(getUniversalId().getId(), mColumn), source));
recompile();
}
void CSVWorld::ScriptSubView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
if (mEditor->isChangeLocked())
return;
ScriptEdit::ChangeLock lock(*mEditor);
bool updateRequired = false;
for (int i = topLeft.row(); i <= bottomRight.row(); ++i)
{
std::string id = mModel->data(mModel->index(i, mIdColumn)).toString().toUtf8().constData();
if (mErrors->clearLocals(id))
updateRequired = true;
}
QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn);
if (index.row() >= topLeft.row() && index.row() <= bottomRight.row())
{
if (mStateColumn >= topLeft.column() && mStateColumn <= bottomRight.column())
updateDeletedState();
if (mColumn >= topLeft.column() && mColumn <= bottomRight.column())
{
QString source = mModel->data(index).toString();
QTextCursor cursor = mEditor->textCursor();
mEditor->setPlainText(source);
mEditor->setTextCursor(cursor);
updateRequired = true;
}
}
if (updateRequired)
recompile();
}
void CSVWorld::ScriptSubView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
bool updateRequired = false;
for (int i = start; i <= end; ++i)
{
std::string id = mModel->data(mModel->index(i, mIdColumn)).toString().toUtf8().constData();
if (mErrors->clearLocals(id))
updateRequired = true;
}
if (updateRequired)
recompile();
QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn);
if (!parent.isValid() && index.row() >= start && index.row() <= end)
emit closeRequest();
}
void CSVWorld::ScriptSubView::switchToRow(int row)
{
int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
std::string id = mModel->data(mModel->index(row, idColumn)).toString().toUtf8().constData();
setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Script, id));
bool oldSignalsState = mEditor->blockSignals(true);
mEditor->setPlainText(mModel->data(mModel->index(row, mColumn)).toString());
mEditor->blockSignals(oldSignalsState);
std::vector<std::string> selection(1, id);
mCommandDispatcher.setSelection(selection);
updateDeletedState();
}
void CSVWorld::ScriptSubView::switchToId(const std::string& id)
{
switchToRow(mModel->getModelIndex(id, 0).row());
}
void CSVWorld::ScriptSubView::highlightError(int line, int column)
{
QTextCursor cursor = mEditor->textCursor();
cursor.movePosition(QTextCursor::Start);
if (cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line))
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
mEditor->setFocus();
mEditor->setTextCursor(cursor);
}
void CSVWorld::ScriptSubView::updateRequest()
{
QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn);
QString source = mModel->data(index).toString();
mErrors->update(source.toUtf8().constData());
adjustSplitter();
}