diff --git a/limereport/limereport.pri b/limereport/limereport.pri index d27d6f7..649f8f6 100644 --- a/limereport/limereport.pri +++ b/limereport/limereport.pri @@ -12,7 +12,8 @@ INCLUDEPATH += \ $$REPORT_PATH/bands \ $$REPORT_PATH/base \ $$REPORT_PATH/objectinspector \ - $$REPORT_PATH/databrowser + $$REPORT_PATH/databrowser \ + $$REPORT_PATH/scripteditor SOURCES += \ $$REPORT_PATH/bands/lrpageheader.cpp \ @@ -60,6 +61,7 @@ SOURCES += \ $$REPORT_PATH/objectsbrowser/lrobjectbrowser.cpp \ $$REPORT_PATH/scriptbrowser/lrscriptbrowser.cpp \ $$REPORT_PATH/scripteditor/lrscripteditor.cpp \ + $$REPORT_PATH/scripteditor/lrcodeeditor.cpp \ $$REPORT_PATH/items/lrsubitemparentpropitem.cpp \ $$REPORT_PATH/items/lralignpropitem.cpp \ $$REPORT_PATH/items/lrhorizontallayout.cpp \ @@ -163,6 +165,7 @@ HEADERS += \ $$REPORT_PATH/objectsbrowser/lrobjectbrowser.h \ $$REPORT_PATH/scriptbrowser/lrscriptbrowser.h \ $$REPORT_PATH/scripteditor/lrscripteditor.h \ + $$REPORT_PATH/scripteditor/lrcodeeditor.h \ $$REPORT_PATH/items/editors/lritemeditorwidget.h \ $$REPORT_PATH/items/editors/lrfonteditorwidget.h \ $$REPORT_PATH/items/editors/lrtextalignmenteditorwidget.h \ @@ -237,7 +240,7 @@ FORMS += \ $$REPORT_PATH/scriptbrowser/lrscriptbrowser.ui \ $$REPORT_PATH/items/lrchartitemeditor.ui \ $$REPORT_PATH/translationeditor/translationeditor.ui \ - $$REPORT_PATH/translationeditor/languageselectdialog.ui + $$REPORT_PATH/translationeditor/languageselectdialog.ui \ $$REPORT_PATH/scripteditor/lrscripteditor.ui RESOURCES += \ diff --git a/limereport/limereport.pro b/limereport/limereport.pro index e98a9d1..fa57ae6 100644 --- a/limereport/limereport.pro +++ b/limereport/limereport.pro @@ -118,4 +118,10 @@ contains(CONFIG,build_translations){ #### EN AUTOMATIC TRANSLATIONS +HEADERS += \ + scripteditor/lrscripthighlighter.h + +SOURCES += \ + scripteditor/lrscripthighlighter.cpp + diff --git a/limereport/scripteditor/lrcodeeditor.cpp b/limereport/scripteditor/lrcodeeditor.cpp new file mode 100644 index 0000000..ae3aec9 --- /dev/null +++ b/limereport/scripteditor/lrcodeeditor.cpp @@ -0,0 +1,193 @@ +#include "lrcodeeditor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lrscripthighlighter.h" + +namespace LimeReport{ + +CodeEditor::CodeEditor(QWidget *parent) + : QPlainTextEdit(parent), m_compleater(0) +{ + lineNumberArea = new LineNumberArea(this); + + connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); + connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + + updateLineNumberAreaWidth(0); + highlightCurrentLine(); + (void) new ScriptHighlighter(document()); +} + +void CodeEditor::setCompleter(QCompleter *value) +{ + if (value) disconnect(value,0,this,0); + m_compleater = value; + if (!m_compleater) return; + m_compleater->setWidget(this); + m_compleater->setCompletionMode(QCompleter::PopupCompletion); + m_compleater->setCaseSensitivity(Qt::CaseInsensitive); + connect(m_compleater,SIGNAL(activated(QString)),this,SLOT(insertCompletion(QString))); +} + +void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent* event) +{ + QPainter painter(lineNumberArea); + painter.fillRect(event->rect(), QPalette().background().color()); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + + while (block.isValid() && top <= event->rect().bottom()) { + if (block.isVisible() && bottom >= event->rect().top()) { + QString number = QString::number(blockNumber + 1); + painter.setPen(QPalette().text().color()); + painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), + Qt::AlignCenter, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} + +int CodeEditor::lineNumberAreaWidth() +{ + int digits = 1; + int max = qMax(1, blockCount()); + while (max >= 10) { + max /= 10; + ++digits; + } + + int space = fontMetrics().width(QLatin1Char('9'))*2 + fontMetrics().width(QLatin1Char('9')) * digits; + + return space; +} + +void CodeEditor::keyPressEvent(QKeyEvent *e) +{ + if (m_compleater && m_compleater->popup()->isVisible()) { + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Escape: + case Qt::Key_Tab: + case Qt::Key_Backtab: + e->ignore(); + return; + default: + break; + } + } + + bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); + if (!m_compleater || !isShortcut) QPlainTextEdit::keyPressEvent(e); + + const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); + if (!m_compleater || (ctrlOrShift && e->text().isEmpty())) + return; + + static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word + bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift; + + QString completionPrefix = textUnderCursor(); + + if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3 + || eow.contains(e->text().right(1)))) { + m_compleater->popup()->hide(); + return; + } + + if (completionPrefix != m_compleater->completionPrefix()) { + m_compleater->setCompletionPrefix(completionPrefix); + m_compleater->popup()->setCurrentIndex(m_compleater->completionModel()->index(0, 0)); + } + + QRect cr = cursorRect(); + cr.setWidth(m_compleater->popup()->sizeHintForColumn(0) + + m_compleater->popup()->verticalScrollBar()->sizeHint().width()); + m_compleater->complete(cr); + +} + +void CodeEditor::focusInEvent(QFocusEvent *e) +{ + if (m_compleater) m_compleater->setWidget(this); + QPlainTextEdit::focusInEvent(e); +} + +void CodeEditor::resizeEvent(QResizeEvent* event) +{ + QPlainTextEdit::resizeEvent(event); + QRect cr = contentsRect(); + lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + +QString CodeEditor::textUnderCursor() const +{ + QTextCursor tc = textCursor(); + tc.select(QTextCursor::WordUnderCursor); + return tc.selectedText(); +} + +void CodeEditor::insertCompletion(const QString &completion) +{ + if (m_compleater->widget() != this) + return; + QTextCursor tc = textCursor(); + int extra = completion.length() - m_compleater->completionPrefix().length(); + tc.movePosition(QTextCursor::Left); + tc.movePosition(QTextCursor::EndOfWord); + tc.insertText(completion.right(extra)); + setTextCursor(tc); +} + +void CodeEditor::updateLineNumberAreaWidth(int newBlockCount) +{ + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + +void CodeEditor::highlightCurrentLine() +{ + QList extraSelections; + + if (!isReadOnly()) { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(QPalette().background().color()).darker(160); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + setExtraSelections(extraSelections); +} + +void CodeEditor::updateLineNumberArea(const QRect& rect, int dy) +{ + if (dy) + lineNumberArea->scroll(0, dy); + else + lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); + + if (rect.contains(viewport()->rect())) + updateLineNumberAreaWidth(0); +} + +} //namespace LimeReport diff --git a/limereport/scripteditor/lrcodeeditor.h b/limereport/scripteditor/lrcodeeditor.h new file mode 100644 index 0000000..83c2dc0 --- /dev/null +++ b/limereport/scripteditor/lrcodeeditor.h @@ -0,0 +1,64 @@ +#ifndef LRCODEEDITOR_H +#define LRCODEEDITOR_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QWidget; +class QCompleter; +class QKeyEvent; +class QScrollBar; +QT_END_NAMESPACE + +namespace LimeReport{ + +class CodeEditor :public QPlainTextEdit +{ + Q_OBJECT +public: + CodeEditor(QWidget* parent=0); + void setCompleter(QCompleter* value); + QCompleter* compleater() const{ return m_compleater;} + void lineNumberAreaPaintEvent(QPaintEvent *event); + int lineNumberAreaWidth(); +protected: + void keyPressEvent(QKeyEvent *e); + void focusInEvent(QFocusEvent *e); + void resizeEvent(QResizeEvent *event); +private: + QString textUnderCursor() const; +private slots: + void insertCompletion(const QString& completion); + void updateLineNumberAreaWidth(int newBlockCount); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect &rect, int dy); +private: + QCompleter* m_compleater; + QWidget *lineNumberArea; +}; + + +class LineNumberArea : public QWidget +{ +public: + LineNumberArea(CodeEditor *editor) : QWidget(editor) { + codeEditor = editor; + } + + QSize sizeHint() const { + return QSize(codeEditor->lineNumberAreaWidth(), 0); + } + +protected: + void paintEvent(QPaintEvent *event) { + codeEditor->lineNumberAreaPaintEvent(event); + } + +private: + CodeEditor *codeEditor; +}; + +} // namespace LimeReport + +#endif // LRCODEEDITOR_H diff --git a/limereport/scripteditor/lrscripteditor.cpp b/limereport/scripteditor/lrscripteditor.cpp index 80bbd27..613926e 100644 --- a/limereport/scripteditor/lrscripteditor.cpp +++ b/limereport/scripteditor/lrscripteditor.cpp @@ -9,93 +9,6 @@ namespace LimeReport{ -TextEditorWithCompleater::TextEditorWithCompleater(QWidget *parent) - : QTextEdit(parent),m_compleater(0) -{ -} - -void TextEditorWithCompleater::setCompleter(QCompleter *value) -{ - if (value) disconnect(value,0,this,0); - m_compleater = value; - if (!m_compleater) return; - m_compleater->setWidget(this); - m_compleater->setCompletionMode(QCompleter::PopupCompletion); - m_compleater->setCaseSensitivity(Qt::CaseInsensitive); - connect(m_compleater,SIGNAL(activated(QString)),this,SLOT(insertCompletion(QString))); -} - -void TextEditorWithCompleater::keyPressEvent(QKeyEvent *e) -{ - if (m_compleater && m_compleater->popup()->isVisible()) { - switch (e->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: - case Qt::Key_Escape: - case Qt::Key_Tab: - case Qt::Key_Backtab: - e->ignore(); - return; - default: - break; - } - } - - bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); - if (!m_compleater || !isShortcut) QTextEdit::keyPressEvent(e); - - const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); - if (!m_compleater || (ctrlOrShift && e->text().isEmpty())) - return; - - static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word - bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift; - - QString completionPrefix = textUnderCursor(); - - if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3 - || eow.contains(e->text().right(1)))) { - m_compleater->popup()->hide(); - return; - } - - if (completionPrefix != m_compleater->completionPrefix()) { - m_compleater->setCompletionPrefix(completionPrefix); - m_compleater->popup()->setCurrentIndex(m_compleater->completionModel()->index(0, 0)); - } - - QRect cr = cursorRect(); - cr.setWidth(m_compleater->popup()->sizeHintForColumn(0) - + m_compleater->popup()->verticalScrollBar()->sizeHint().width()); - m_compleater->complete(cr); - -} - -void TextEditorWithCompleater::focusInEvent(QFocusEvent *e) -{ - if (m_compleater) m_compleater->setWidget(this); - QTextEdit::focusInEvent(e); -} - -QString TextEditorWithCompleater::textUnderCursor() const -{ - QTextCursor tc = textCursor(); - tc.select(QTextCursor::WordUnderCursor); - return tc.selectedText(); -} - -void TextEditorWithCompleater::insertCompletion(const QString &completion) -{ - if (m_compleater->widget() != this) - return; - QTextCursor tc = textCursor(); - int extra = completion.length() - m_compleater->completionPrefix().length(); - tc.movePosition(QTextCursor::Left); - tc.movePosition(QTextCursor::EndOfWord); - tc.insertText(completion.right(extra)); - setTextCursor(tc); -} - ScriptEditor::ScriptEditor(QWidget *parent) : QWidget(parent), ui(new Ui::ScriptEditor) diff --git a/limereport/scripteditor/lrscripteditor.h b/limereport/scripteditor/lrscripteditor.h index 7674025..6943fcb 100644 --- a/limereport/scripteditor/lrscripteditor.h +++ b/limereport/scripteditor/lrscripteditor.h @@ -12,24 +12,6 @@ namespace LimeReport{ class ReportEnginePrivate; class BaseDesignIntf; -class TextEditorWithCompleater :public QTextEdit -{ - Q_OBJECT -public: - TextEditorWithCompleater(QWidget* parent=0); - void setCompleter(QCompleter* value); - QCompleter* compleater() const{ return m_compleater;} -protected: - virtual void keyPressEvent(QKeyEvent *e); - virtual void focusInEvent(QFocusEvent *e); -private: - QString textUnderCursor() const; -private slots: - void insertCompletion(const QString& completion); -private: - QCompleter* m_compleater; -}; - namespace Ui { class ScriptEditor; } diff --git a/limereport/scripteditor/lrscripteditor.ui b/limereport/scripteditor/lrscripteditor.ui index 5a5af17..49fcfb6 100644 --- a/limereport/scripteditor/lrscripteditor.ui +++ b/limereport/scripteditor/lrscripteditor.ui @@ -1,7 +1,7 @@ - ScriptEditor - + LimeReport::ScriptEditor + 0 @@ -52,7 +52,7 @@ Qt::Horizontal - + 12 @@ -129,9 +129,9 @@ - TextEditorWithCompleater + LimeReport::CodeEditor QTextEdit -
lrscripteditor.h
+
lrcodeeditor.h
diff --git a/limereport/scripteditor/lrscripthighlighter.cpp b/limereport/scripteditor/lrscripthighlighter.cpp new file mode 100644 index 0000000..d85a571 --- /dev/null +++ b/limereport/scripteditor/lrscripthighlighter.cpp @@ -0,0 +1,118 @@ +#include "lrscripthighlighter.h" +#include + +namespace LimeReport{ + +#define KEYWORDS_COUNT 59 + +static const char *const keywords[KEYWORDS_COUNT] = { + "do","if","in","for","int","new","try","var","byte","case","char","else","enum", + "goto","long","null","this","true","void","with","break","catch","class","const", + "false","final","float","short","super","throw","while","delete","double","export", + "import","native","public","return","static","switch","throws","typeof","boolean", + "default","extends","finally","package","private","abstract","continue","debugger", + "function","volatile","interface","protected","transient","implements","instanceof", + "synchronized" +}; + +enum LiteralsType{SpaceFound, AlpahabetFound, NumberFound, HashFound, SlashFound, AsterixFound, + BracketFound, QuotationFound, ApostropheFound, SeparatorFound, BackSlashFound, LiteralsCount}; +enum States {MayBeKeyWord, Code, MayBeComment, Comment, Comment2, MayBeComment2End, String, String2, MayBeNumber, Separator, StatesCount}; + +void ScriptHighlighter::highlightBlock(const QString& text) +{ + int literal = -1; + bool lastWasBackSlash = false; + int state = previousBlockState() != -1 ? previousBlockState() : MayBeKeyWord ; + int oldState = -1; + int stateMaschine[StatesCount][LiteralsCount] = { + {Separator, MayBeKeyWord, Code, Separator, MayBeComment, Separator, Separator, String, String2, Separator, Separator}, + {Separator, Code, Code, Separator, Separator, Separator, Separator, String, String2, Separator, Separator}, + {Separator, Code, Code, Code, Comment, Comment2, Code, String, String2, Separator, Code}, + {Comment, Comment, Comment, Comment, Comment, Comment, Comment, Comment, Comment, Comment, Comment}, + {Comment2, Comment2, Comment2, Comment2, Comment2, MayBeComment2End, Comment2, Comment2, Comment2, Comment2, Comment2}, + {Comment2, Comment2, Comment2, Comment2, Separator, Comment2, Comment2, Comment2, Comment2, Comment2, Comment2}, + {String, String, String, String, String, String, String, Separator, String, String, String}, + {String2, String2, String2, String2, String2, String2, String2, String2, Separator, String2, String2}, + {Separator, Code, MayBeNumber, Separator, MayBeComment, Separator, Separator, String, String2, Separator, Code}, + {Separator, MayBeKeyWord, MayBeNumber, Separator, MayBeComment, String, String2, Separator, Separator } + }; + + QString buffer; + + if (text.isEmpty()) return; + int i = 0; + for (;;){ + QChar currentChar = text.at(i); + switch(currentChar.toLatin1()){ + case ' ': + literal = SpaceFound; + break; + case '/': + literal = SlashFound; + break; + case '*': + literal = AsterixFound; + break; + case '#': + literal = HashFound; + break; + case '\'': + literal = ApostropheFound; + break; + case '\\': + literal = BackSlashFound; + break; + case '"': + literal = QuotationFound; + break; + case '{': case '[': case '(': + case '}': case ']': case ')': + literal = BracketFound; + break; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '0': + literal = NumberFound; + break; + default: + if (currentChar.isLetter()) + literal = AlpahabetFound; + else + literal = SeparatorFound; + }; + + lastWasBackSlash = !lastWasBackSlash && currentChar == QLatin1Char('\\'); + + oldState = state; + state = stateMaschine[state][literal]; + + if (oldState != state){ + switch( state ){ + case MayBeComment: + buffer.clear(); + buffer += currentChar; + break; + case Comment2: + buffer += currentChar; + qDebug()<= text.length()) break; + } +} + +} // namespace LimeReport diff --git a/limereport/scripteditor/lrscripthighlighter.h b/limereport/scripteditor/lrscripthighlighter.h new file mode 100644 index 0000000..3a54693 --- /dev/null +++ b/limereport/scripteditor/lrscripthighlighter.h @@ -0,0 +1,17 @@ +#ifndef LRSCRIPTHIGHLIGHTER_H +#define LRSCRIPTHIGHLIGHTER_H + +#include + +namespace LimeReport{ + +class ScriptHighlighter : QSyntaxHighlighter{ +public: + ScriptHighlighter(QTextDocument* parent): QSyntaxHighlighter(parent){} + // QSyntaxHighlighter interface +protected: + void highlightBlock(const QString& text); +}; + +} +#endif // LRSCRIPTHIGHLIGHTER_H