diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 6e240c998..9e00b7d1a 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -225,7 +225,19 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() Setting *autoDelete = createSetting (Type_CheckBox, "auto-delete", "Delete row from result table after a successful replace"); autoDelete->setDefaultValue ("true"); } - + + declareSection ("script-editor", "Script Editor"); + { + Setting *lineNum = createSetting (Type_CheckBox, "show-linenum", "Show Line Numbers"); + lineNum->setDefaultValue ("true"); + lineNum->setToolTip ("Show line numbers to the left of the script editor window." + "The current row and column numbers of the text cursor are shown at the bottom."); + + Setting *monoFont = createSetting (Type_CheckBox, "mono-font", "Use monospace font"); + monoFont->setDefaultValue ("true"); + monoFont->setToolTip ("Whether to use monospaced fonts on script edit subview."); + } + { /****************************************************************** * There are three types of values: diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 271b0316d..b4f4234f1 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -5,11 +5,14 @@ #include #include #include +#include +#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/settings/usersettings.hpp" CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) @@ -28,7 +31,11 @@ CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighli : QPlainTextEdit (parent), mDocument (document), mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive), - mChangeLocked (0) + mChangeLocked (0), + mLineNumberArea(0), + mShowLineNum(false), + mDefaultFont(font()), + mMonoFont(QFont("Monospace")) { // setAcceptRichText (false); setLineWrapMode (QPlainTextEdit::NoWrap); @@ -72,6 +79,41 @@ CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighli connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); mUpdateTimer.setSingleShot (true); + + // TODO: provide a font selector dialogue + mMonoFont.setStyleHint(QFont::TypeWriter); + std::string useMonoFont = + CSMSettings::UserSettings::instance().setting("script-editor/mono-font", "true").toStdString(); + if (useMonoFont == "true") + setFont(mMonoFont); + + mLineNumberArea = new LineNumberArea(this); + updateLineNumberAreaWidth(0); + + connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); + connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); + + std::string showStatusBar = + CSMSettings::UserSettings::instance().settingValue("script-editor/show-linenum").toStdString(); + + showLineNum(showStatusBar == "true"); +} + +void CSVWorld::ScriptEdit::showLineNum(bool show) +{ + if(show!=mShowLineNum) + { + mShowLineNum = show; + updateLineNumberAreaWidth(0); + } +} + +void CSVWorld::ScriptEdit::setMonoFont(bool show) +{ + if(show) + setFont(mMonoFont); + else + setFont(mDefaultFont); } bool CSVWorld::ScriptEdit::isChangeLocked() const @@ -157,3 +199,112 @@ void CSVWorld::ScriptEdit::updateHighlighting() mHighlighter->rehighlight(); } + +int CSVWorld::ScriptEdit::lineNumberAreaWidth() +{ + if(!mShowLineNum) + return 0; + + int digits = 1; + int max = qMax(1, blockCount()); + while (max >= 10) + { + max /= 10; + ++digits; + } + + int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; + + return space; +} + +void CSVWorld::ScriptEdit::updateLineNumberAreaWidth(int /* newBlockCount */) +{ + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + +void CSVWorld::ScriptEdit::updateLineNumberArea(const QRect &rect, int dy) +{ + if (dy) + mLineNumberArea->scroll(0, dy); + else + mLineNumberArea->update(0, rect.y(), mLineNumberArea->width(), rect.height()); + + if (rect.contains(viewport()->rect())) + updateLineNumberAreaWidth(0); +} + +void CSVWorld::ScriptEdit::resizeEvent(QResizeEvent *e) +{ + QPlainTextEdit::resizeEvent(e); + + QRect cr = contentsRect(); + mLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + +void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) +{ + QPainter painter(mLineNumberArea); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + + int startBlock = textCursor().blockNumber(); + int endBlock = textCursor().blockNumber(); + if(textCursor().hasSelection()) + { + QString str = textCursor().selection().toPlainText(); + int selectedLines = str.count("\n")+1; + if(textCursor().position() < textCursor().anchor()) + endBlock += selectedLines; + else + startBlock -= selectedLines; + } + painter.setBackgroundMode(Qt::OpaqueMode); + QFont font = painter.font(); + QBrush background = painter.background(); + + while (block.isValid() && top <= event->rect().bottom()) + { + if (block.isVisible() && bottom >= event->rect().top()) + { + QFont newFont = painter.font(); + QString number = QString::number(blockNumber + 1); + if(blockNumber >= startBlock && blockNumber <= endBlock) + { + painter.setBackground(Qt::cyan); + painter.setPen(Qt::darkMagenta); + newFont.setBold(true); + } + else + { + painter.setBackground(background); + painter.setPen(Qt::black); + } + painter.setFont(newFont); + painter.drawText(0, top, mLineNumberArea->width(), fontMetrics().height(), + Qt::AlignRight, number); + painter.setFont(font); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} + +CSVWorld::LineNumberArea::LineNumberArea(ScriptEdit *editor) : QWidget(editor), mScriptEdit(editor) +{} + +QSize CSVWorld::LineNumberArea::sizeHint() const +{ + return QSize(mScriptEdit->lineNumberAreaWidth(), 0); +} + +void CSVWorld::LineNumberArea::paintEvent(QPaintEvent *event) +{ + mScriptEdit->lineNumberAreaPaintEvent(event); +} diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index 0192bc550..a19cee486 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -2,14 +2,15 @@ #define SCRIPTEDIT_H #include +#include #include #include +#include #include "../../model/world/universalid.hpp" #include "scripthighlighter.hpp" -class QWidget; class QRegExp; namespace CSMDoc @@ -19,6 +20,8 @@ namespace CSMDoc namespace CSVWorld { + class LineNumberArea; + class ScriptEdit : public QPlainTextEdit { Q_OBJECT @@ -45,6 +48,10 @@ namespace CSVWorld int mChangeLocked; ScriptHighlighter *mHighlighter; QTimer mUpdateTimer; + bool mShowLineNum; + LineNumberArea *mLineNumberArea; + QFont mDefaultFont; + QFont mMonoFont; public: @@ -56,6 +63,15 @@ namespace CSVWorld /// \note This mechanism is used to avoid infinite update recursions bool isChangeLocked() const; + void lineNumberAreaPaintEvent(QPaintEvent *event); + int lineNumberAreaWidth(); + void showLineNum(bool show); + void setMonoFont(bool show); + + protected: + + virtual void resizeEvent(QResizeEvent *e); + private: QVector mAllowedTypes; const CSMDoc::Document& mDocument; @@ -74,6 +90,23 @@ namespace CSVWorld void idListChanged(); void updateHighlighting(); + + void updateLineNumberAreaWidth(int newBlockCount); + void updateLineNumberArea(const QRect &, int); + }; + + class LineNumberArea : public QWidget + { + ScriptEdit *mScriptEdit; + + public: + + LineNumberArea(ScriptEdit *editor); + QSize sizeHint() const; + + protected: + + void paintEvent(QPaintEvent *event); }; } #endif // SCRIPTEDIT_H diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index ea9dcee8c..411eb3660 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -1,21 +1,42 @@ - #include "scriptsubview.hpp" #include +#include +#include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" #include "../../model/world/columnbase.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/settings/usersettings.hpp" #include "scriptedit.hpp" CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1) +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0) { - setWidget (mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this)); + QVBoxLayout *layout = new QVBoxLayout; + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + mBottom = new QWidget(this); + QStackedLayout *bottmLayout = new QStackedLayout(mBottom); + bottmLayout->setContentsMargins (0, 0, 0, 0); + QStatusBar *statusBar = new QStatusBar(mBottom); + mStatus = new QLabel(mBottom); + statusBar->addWidget (mStatus); + bottmLayout->addWidget (statusBar); + mBottom->setLayout (bottmLayout); + + layout->addWidget (mBottom, 0); + layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + + QWidget *widget = new QWidget; + widget->setLayout (layout); + setWidget (widget); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); @@ -40,6 +61,33 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); + + updateStatusBar(); + connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); +} + +void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) +{ + if (name == "script-editor/show-linenum") + { + std::string showLinenum = value.at(0).toStdString(); + mEditor->showLineNum(showLinenum == "true"); + mBottom->setVisible(showLinenum == "true"); + } + else if (name == "script-editor/mono-font") + { + mEditor->setMonoFont(value.at(0).toStdString() == "true"); + } +} + +void CSVWorld::ScriptSubView::updateStatusBar () +{ + std::ostringstream stream; + + stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", " + << mEditor->textCursor().columnNumber() + 1 << ")"; + + mStatus->setText (QString::fromUtf8 (stream.str().c_str())); } void CSVWorld::ScriptSubView::setEditLock (bool locked) diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 561476577..1c6474e54 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -4,6 +4,7 @@ #include "../doc/subview.hpp" class QModelIndex; +class QLabel; namespace CSMDoc { @@ -27,6 +28,8 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; + QWidget *mBottom; + QLabel *mStatus; public: @@ -36,6 +39,8 @@ namespace CSVWorld virtual void useHint (const std::string& hint); + virtual void updateUserSetting (const QString& name, const QStringList& value); + public slots: void textChanged(); @@ -43,6 +48,10 @@ namespace CSVWorld void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + private slots: + + void updateStatusBar(); }; }