diff --git a/limereport/items/lrtextitemeditor.cpp b/limereport/items/lrtextitemeditor.cpp index 76e6891..f46333d 100644 --- a/limereport/items/lrtextitemeditor.cpp +++ b/limereport/items/lrtextitemeditor.cpp @@ -33,7 +33,6 @@ #include "lrdatasourcemanager.h" #include "lrscriptenginemanager.h" #include "lrdatadesignintf.h" -#include "lrdatasourcemanager.h" #include #include diff --git a/limereport/limereport.pri b/limereport/limereport.pri index 69af57f..385ad4d 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 \ @@ -59,6 +60,9 @@ SOURCES += \ $$REPORT_PATH/objectinspector/lrpropertydelegate.cpp \ $$REPORT_PATH/objectsbrowser/lrobjectbrowser.cpp \ $$REPORT_PATH/scriptbrowser/lrscriptbrowser.cpp \ + $$REPORT_PATH/scripteditor/lrscripteditor.cpp \ + $$REPORT_PATH/scripteditor/lrcodeeditor.cpp \ + $$REPORT_PATH/scripteditor/lrscripthighlighter.cpp \ $$REPORT_PATH/items/lrsubitemparentpropitem.cpp \ $$REPORT_PATH/items/lralignpropitem.cpp \ $$REPORT_PATH/items/lrhorizontallayout.cpp \ @@ -161,6 +165,9 @@ HEADERS += \ $$REPORT_PATH/objectinspector/lrpropertydelegate.h \ $$REPORT_PATH/objectsbrowser/lrobjectbrowser.h \ $$REPORT_PATH/scriptbrowser/lrscriptbrowser.h \ + $$REPORT_PATH/scripteditor/lrscripteditor.h \ + $$REPORT_PATH/scripteditor/lrcodeeditor.h \ + $$REPORT_PATH/scripteditor/lrscripthighlighter.h \ $$REPORT_PATH/items/editors/lritemeditorwidget.h \ $$REPORT_PATH/items/editors/lrfonteditorwidget.h \ $$REPORT_PATH/items/editors/lrtextalignmenteditorwidget.h \ @@ -235,7 +242,8 @@ FORMS += \ $$REPORT_PATH/scriptbrowser/lrscriptbrowser.ui \ $$REPORT_PATH/items/lrchartitemeditor.ui \ $$REPORT_PATH/translationeditor/translationeditor.ui \ - $$PWD/translationeditor/languageselectdialog.ui + $$REPORT_PATH/translationeditor/languageselectdialog.ui \ + $$REPORT_PATH/scripteditor/lrscripteditor.ui RESOURCES += \ $$REPORT_PATH/objectinspector/lobjectinspector.qrc \ diff --git a/limereport/lrreportdesignwidget.cpp b/limereport/lrreportdesignwidget.cpp index 9472f13..0145940 100644 --- a/limereport/lrreportdesignwidget.cpp +++ b/limereport/lrreportdesignwidget.cpp @@ -35,6 +35,7 @@ #include "lrsettingdialog.h" #include "dialogdesigner/lrdialogdesigner.h" #include "translationeditor/translationeditor.h" +#include "scripteditor/lrscripteditor.h" #include #include @@ -183,6 +184,7 @@ void ReportDesignWidget::saveState(QSettings* settings) settings->setValue("vGridStep",m_verticalGridStep); settings->setValue("defaultFont",m_defaultFont); settings->setValue("useGrid",m_useGrid); + settings->setValue("ScriptEditorState", m_scriptEditor->saveState()); settings->endGroup(); } @@ -215,6 +217,12 @@ void ReportDesignWidget::loadState(QSettings* settings) if (v.isValid()){ m_useGrid = v.toBool(); } + + v = settings->value("ScriptEditorState"); + if (v.isValid()){ + m_scriptEditor->restoreState(v.toByteArray()); + } + settings->endGroup(); applySettings(); } @@ -241,7 +249,8 @@ void ReportDesignWidget::createTabs(){ this, SLOT(slotPagePropertyObjectNameChanged(QString,QString))); } - m_scriptEditor = new QTextEdit(this); + m_scriptEditor = new ScriptEditor(this); + m_scriptEditor->setReportEngine(m_report); pageIndex = m_tabWidget->addTab(m_scriptEditor,QIcon(),tr("Script")); m_tabWidget->setTabWhatsThis(pageIndex,"script"); m_tabWidget->setCurrentIndex(0); @@ -435,9 +444,11 @@ bool ReportDesignWidget::save() bool ReportDesignWidget::loadFromFile(const QString &fileName) { if (m_report->loadFromFile(fileName,false)){ + QByteArray editorState = m_scriptEditor->saveState(); createTabs(); //connectPage(m_report->pageAt(0)); m_scriptEditor->setPlainText(m_report->scriptContext()->initScript()); + m_scriptEditor->restoreState(editorState); emit loaded(); m_dialogChanged = false; return true; @@ -787,6 +798,12 @@ void ReportDesignWidget::slotCurrentTabChanged(int index) if (activeTabType() == Translations){ m_traslationEditor->setReportEngine(report()); } + + if (activeTabType() == Script){ + m_scriptEditor->initCompleter(); + m_scriptEditor->setFocus(); + } + emit activePageChanged(); } diff --git a/limereport/lrreportdesignwidget.h b/limereport/lrreportdesignwidget.h index 4d3b25d..b7e38b7 100644 --- a/limereport/lrreportdesignwidget.h +++ b/limereport/lrreportdesignwidget.h @@ -51,6 +51,8 @@ class ReportDesignWindow; class DialogDesignerManager; class DialogDesigner; class TranslationEditor; +class ScriptEditor; + class ReportDesignWidget : public QWidget { @@ -189,7 +191,7 @@ private: private: ReportEnginePrivate* m_report; QGraphicsView *m_view; - QTextEdit* m_scriptEditor; + ScriptEditor* m_scriptEditor; TranslationEditor* m_traslationEditor; #ifdef HAVE_QTDESIGNER_INTEGRATION DialogDesignerManager* m_dialogDesignerManager; @@ -205,5 +207,5 @@ private: bool m_dialogChanged; }; -} +} // namespace LimeReport #endif // LRREPORTDESIGNWIDGET_H diff --git a/limereport/lrreportdesignwindow.cpp b/limereport/lrreportdesignwindow.cpp index 0bdae75..1ad41c7 100644 --- a/limereport/lrreportdesignwindow.cpp +++ b/limereport/lrreportdesignwindow.cpp @@ -664,14 +664,21 @@ void ReportDesignWindow::writeState() { settings()->beginGroup("DesignerWindow"); switch (m_editorTabType) { - case ReportDesignWidget::Page: - settings()->setValue("PageEditorsState", saveState()); - settings()->setValue("DialogEditorsState", m_dialogEditorsState); - break; - default: - settings()->setValue("DialogEditorsState", saveState()); - settings()->setValue("PageEditorsState", m_pageEditorsState); - break; + case ReportDesignWidget::Page: + settings()->setValue("PageEditorsState", saveState()); + settings()->setValue("DialogEditorsState", m_dialogEditorsState); + settings()->setValue("ScriptEditorsState", m_scriptEditorState); + break; + case ReportDesignWidget::Script: + settings()->setValue("ScriptEditorsState", saveState()); + settings()->setValue("DialogEditorsState", m_dialogEditorsState); + settings()->setValue("PageEditorsState", m_pageEditorsState); + break; + default: + settings()->setValue("DialogEditorsState", saveState()); + settings()->setValue("PageEditorsState", m_pageEditorsState); + settings()->setValue("ScriptEditorsState", m_scriptEditorState); + break; } settings()->setValue("InspectorFirsColumnWidth",m_objectInspector->columnWidth(0)); settings()->endGroup(); @@ -775,6 +782,10 @@ void ReportDesignWindow::restoreSetting() if (v.isValid()){ m_dialogEditorsState = v.toByteArray(); } + v = settings()->value("ScriptEditorsState"); + if (v.isValid()){ + m_scriptEditorState = v.toByteArray(); + } v = settings()->value("InspectorFirsColumnWidth"); if (v.isValid()){ m_objectInspector->setColumnWidth(0,v.toInt()); @@ -1277,12 +1288,15 @@ void ReportDesignWindow::slotActivePageChanged() updateAvaibleBands(); switch (m_editorTabType) { - case ReportDesignWidget::Dialog: - m_dialogEditorsState = saveState(); + case ReportDesignWidget::Dialog: + m_dialogEditorsState = saveState(); #ifdef HAVE_UI_LOADER - m_scriptBrowser->updateDialogsTree(); + m_scriptBrowser->updateDialogsTree(); #endif - break; + break; + case ReportDesignWidget::Script: + m_scriptEditorState = saveState(); + break; default: m_pageEditorsState = saveState(); break; @@ -1291,20 +1305,30 @@ void ReportDesignWindow::slotActivePageChanged() m_editorTabType = m_reportDesignWidget->activeTabType(); switch (m_editorTabType) { - case ReportDesignWidget::Dialog: - if (!m_dialogEditorsState.isEmpty()) - restoreState(m_dialogEditorsState); - else - showDefaultEditors(); - showDefaultToolBars(); - break; - default: - if (!m_pageEditors.isEmpty()) - restoreState(m_pageEditorsState); - else - showDefaultEditors(); - showDefaultToolBars(); - break; + case ReportDesignWidget::Dialog: + if (!m_dialogEditorsState.isEmpty()){ + restoreState(m_dialogEditorsState); + } else { + showDefaultEditors(); + showDefaultToolBars(); + } + break; + case ReportDesignWidget::Script: + if (!m_scriptEditorState.isEmpty()){ + restoreState(m_scriptEditorState); + } else { + showDefaultEditors(); + showDefaultToolBars(); + } + break; + default: + if (!m_pageEditors.isEmpty()){ + restoreState(m_pageEditorsState); + } else { + showDefaultEditors(); + showDefaultToolBars(); + } + break; } } diff --git a/limereport/lrreportdesignwindow.h b/limereport/lrreportdesignwindow.h index 94c4bd5..1779ed3 100644 --- a/limereport/lrreportdesignwindow.h +++ b/limereport/lrreportdesignwindow.h @@ -264,6 +264,7 @@ private: ReportDesignWidget::EditorTabType m_editorTabType; QByteArray m_pageEditorsState; QByteArray m_dialogEditorsState; + QByteArray m_scriptEditorState; QVector m_pageTools; QVector m_dialogTools; diff --git a/limereport/scripteditor/lrcodeeditor.cpp b/limereport/scripteditor/lrcodeeditor.cpp new file mode 100644 index 0000000..273a1b3 --- /dev/null +++ b/limereport/scripteditor/lrcodeeditor.cpp @@ -0,0 +1,322 @@ +#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(); + new ScriptHighlighter(document()); + connect(this, SIGNAL(cursorPositionChanged()), + this, SLOT(matchParentheses())); +} + +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(); +} + +bool CodeEditor::matchLeftParenthesis(QTextBlock currentBlock, QChar parenthesisType, int i, int numLeftParentheses) +{ + TextBlockData *data = static_cast(currentBlock.userData()); + QVector infos = data->parentheses(); + + int docPos = currentBlock.position(); + for (; i < infos.size(); ++i) { + ParenthesisInfo *info = infos.at(i); + + if (info->character == parenthesisType) { + ++numLeftParentheses; + continue; + } + + if (info->character == getParenthesisReverceChar(parenthesisType)){ + if (numLeftParentheses == 0) { + createParenthesisSelection(docPos + info->position); + return true; + } else + --numLeftParentheses; + } + + } + + currentBlock = currentBlock.next(); + if (currentBlock.isValid()) + return matchLeftParenthesis(currentBlock, parenthesisType, 0, numLeftParentheses); + + return false; +} + +bool CodeEditor::matchRightParenthesis(QTextBlock currentBlock, QChar parenthesisType, int i, int numRightParentheses) +{ + TextBlockData *data = static_cast(currentBlock.userData()); + QVector parentheses = data->parentheses(); + + int docPos = currentBlock.position(); + for (; i > -1 && parentheses.size() > 0; --i) { + ParenthesisInfo *info = parentheses.at(i); + if (info->character == parenthesisType) { + ++numRightParentheses; + continue; + } + if (info->character == getParenthesisReverceChar(parenthesisType)){ + if (numRightParentheses == 0) { + createParenthesisSelection(docPos + info->position); + return true; + } else + --numRightParentheses; + } + } + + currentBlock = currentBlock.previous(); + if (currentBlock.isValid()) + return matchRightParenthesis(currentBlock, parenthesisType, 0, numRightParentheses); + + return false; +} + +void CodeEditor::createParenthesisSelection(int pos) +{ + QList selections = extraSelections(); + + QTextEdit::ExtraSelection selection; + QTextCharFormat format = selection.format; + format.setBackground(QColor("#619934")); + format.setForeground(QColor("#ffffff")); + selection.format = format; + + QTextCursor cursor = textCursor(); + cursor.setPosition(pos); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selection.cursor = cursor; + + selections.append(selection); + + setExtraSelections(selections); +} + +bool CodeEditor::charIsParenthesis(QChar character, ParenthesisType type) +{ + for (int i = 0; i < PARENHEIS_COUNT; ++i){ + if (character == parenthesisCharacters[type][i]) + return true; + } + return false; +} + +QChar CodeEditor::getParenthesisReverceChar(QChar parenthesisChar) +{ + for (int i = 0; i < PARENHEIS_COUNT; ++i){ + if ( parenthesisCharacters[RightParenthesis][i] == parenthesisChar) + return parenthesisCharacters[LeftParenthesis][i]; + if ( parenthesisCharacters[LeftParenthesis][i] == parenthesisChar) + return parenthesisCharacters[RightParenthesis][i]; + } + return ' '; +} + +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(100); + + 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); +} + +void CodeEditor::matchParentheses() +{ + QList selections; + setExtraSelections(selections); + + TextBlockData *data = static_cast(textCursor().block().userData()); + + if (data) { + QVector infos = data->parentheses(); + + int pos = textCursor().block().position(); + for (int i = 0; i < infos.size(); ++i) { + ParenthesisInfo *info = infos.at(i); + + int curPos = textCursor().position() - textCursor().block().position(); + + if ( (info->position == (curPos - 1)) && charIsParenthesis(info->character, LeftParenthesis)) + { + if (matchLeftParenthesis(textCursor().block(), info->character, i + 1, 0)) + createParenthesisSelection(pos + info->position); + } else if ( (info->position == (curPos - 1)) && charIsParenthesis(info->character, RightParenthesis)) { + if (matchRightParenthesis(textCursor().block(), info->character, i - 1, 0)) + createParenthesisSelection(pos + info->position); + } + } + } +} + +} //namespace LimeReport diff --git a/limereport/scripteditor/lrcodeeditor.h b/limereport/scripteditor/lrcodeeditor.h new file mode 100644 index 0000000..851583f --- /dev/null +++ b/limereport/scripteditor/lrcodeeditor.h @@ -0,0 +1,71 @@ +#ifndef LRCODEEDITOR_H +#define LRCODEEDITOR_H + +#include +#include +#include "lrscripthighlighter.h" + +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; + bool matchLeftParenthesis(QTextBlock currentBlock, QChar parenthesisType, int i, int numLeftParentheses); + bool matchRightParenthesis(QTextBlock currentBlock, QChar parenthesisType, int i, int numRightParentheses); + void createParenthesisSelection(int pos); + bool charIsParenthesis(QChar character, ParenthesisType type); + QChar getParenthesisReverceChar(QChar parenthesisChar); +private slots: + void insertCompletion(const QString& completion); + void updateLineNumberAreaWidth(int newBlockCount); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect &rect, int dy); + void matchParentheses(); +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 new file mode 100644 index 0000000..461a8f9 --- /dev/null +++ b/limereport/scripteditor/lrscripteditor.cpp @@ -0,0 +1,150 @@ +#include "lrscripteditor.h" +#include "ui_lrscripteditor.h" +#include + +#include "lrdatasourcemanager.h" +#include "lrscriptenginemanager.h" +#include "lrdatadesignintf.h" +#include "lrreportengine_p.h" + +namespace LimeReport{ + +ScriptEditor::ScriptEditor(QWidget *parent) : + QWidget(parent), + ui(new Ui::ScriptEditor) +{ + ui->setupUi(this); + setFocusProxy(ui->textEdit); + m_completer = new QCompleter(this); + ui->textEdit->setCompleter(m_completer); +} + +ScriptEditor::~ScriptEditor() +{ + delete ui; +} + +void ScriptEditor::setReportEngine(ReportEnginePrivate* reportEngine) +{ + m_reportEngine = reportEngine; + DataSourceManager* dm = m_reportEngine->dataManager(); + ScriptEngineManager& se = LimeReport::ScriptEngineManager::instance(); + se.setDataManager(dm); + + initCompleter(); + + if (dm){ + if (dm->isNeedUpdateDatasourceModel()) + dm->updateDatasourceModel(); + ui->twData->setModel(dm->datasourcesModel()); + ui->twScriptEngine->setModel(se.model()); + } + +} + +void ScriptEditor::initCompleter() +{ + QStringList dataWords; + DataSourceManager* dm = m_reportEngine->dataManager(); + ScriptEngineManager& se = LimeReport::ScriptEngineManager::instance(); + + QJSValue globalObject = se.scriptEngine()->globalObject(); + QJSValueIterator it(globalObject); + while (it.hasNext()){ + it.next(); + if (it.value().isCallable() ){ + dataWords << it.name(); + } + } + + foreach(const QString &dsName,dm->dataSourceNames()){ + dataWords << dsName; + foreach(const QString &field, dm->fieldNames(dsName)){ + dataWords<variableNames()) { + dataWords << varName.remove("#"); + } + + for ( int i = 0; i < m_reportEngine->pageCount(); ++i){ + PageDesignIntf* page = m_reportEngine->pageAt(i); + dataWords << page->pageItem()->objectName(); + foreach (BaseDesignIntf* item, page->pageItem()->childBaseItems()){ + addItemToCompleater(page->pageItem()->objectName(), item, dataWords); + } + } + + m_completer->setModel(new QStringListModel(dataWords,m_completer)); +} + +QByteArray ScriptEditor::saveState() +{ + return ui->splitter->saveState(); +} + +void ScriptEditor::restoreState(QByteArray state) +{ + ui->splitter->restoreState(state); +} + +void ScriptEditor::setPlainText(const QString& text) +{ + ui->textEdit->setPlainText(text); +} + +QString ScriptEditor::toPlainText() +{ + return ui->textEdit->toPlainText(); +} + +void ScriptEditor::addItemToCompleater(const QString& pageName, BaseDesignIntf* item, QStringList& dataWords) +{ + BandDesignIntf* band = dynamic_cast(item); + if (band){ + dataWords << band->objectName(); + dataWords << pageName+"_"+band->objectName(); + dataWords << pageName+"_"+band->objectName()+".beforeRender"; + dataWords << pageName+"_"+item->objectName()+".afterData"; + dataWords << pageName+"_"+band->objectName()+".afterRender"; + foreach (BaseDesignIntf* child, band->childBaseItems()){ + addItemToCompleater(pageName, child, dataWords); + } + } else { + dataWords << item->objectName(); + dataWords << pageName+"_"+item->objectName(); + dataWords << pageName+"_"+item->objectName()+".beforeRender"; + dataWords << pageName+"_"+item->objectName()+".afterData"; + dataWords << pageName+"_"+item->objectName()+".afterRender"; + } +} + +void ScriptEditor::on_twData_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) return; + LimeReport::DataNode* node = static_cast(index.internalPointer()); + if (node->type()==LimeReport::DataNode::Field){ + ui->textEdit->insertPlainText(QString("$D{%1.%2}").arg(node->parent()->name()).arg(node->name())); + } + if (node->type()==LimeReport::DataNode::Variable){ + ui->textEdit->insertPlainText(QString("$V{%1}").arg(node->name())); + } + ui->textEdit->setFocus(); +} + +void ScriptEditor::on_twScriptEngine_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) return; + LimeReport::ScriptEngineNode* node = static_cast(index.internalPointer()); + if (node->type()==LimeReport::ScriptEngineNode::Function){ + ui->textEdit->insertPlainText(node->name()+"()"); + } + ui->textEdit->setFocus(); +} + +} // namespace LimeReport + + + + diff --git a/limereport/scripteditor/lrscripteditor.h b/limereport/scripteditor/lrscripteditor.h new file mode 100644 index 0000000..eedb66d --- /dev/null +++ b/limereport/scripteditor/lrscripteditor.h @@ -0,0 +1,47 @@ +#ifndef LRSCRIPTEDITOR_H +#define LRSCRIPTEDITOR_H + +#include +#include +#include +#include +#include + +namespace LimeReport{ + +class ReportEnginePrivate; +class BaseDesignIntf; + +namespace Ui { +class ScriptEditor; +} + +class ScriptEditor : public QWidget +{ + Q_OBJECT +public: + explicit ScriptEditor(QWidget *parent = 0); + ~ScriptEditor(); + void setReportEngine(ReportEnginePrivate* reportEngine); + void initCompleter(); + QByteArray saveState(); + void restoreState(QByteArray state); + void setPlainText(const QString &text); + QString toPlainText(); +private slots: + void on_twData_doubleClicked(const QModelIndex &index); + + void on_twScriptEngine_doubleClicked(const QModelIndex &index); + +private: + void addItemToCompleater(const QString& pageName, BaseDesignIntf* item, QStringList& dataWords); +private: + Ui::ScriptEditor *ui; + ReportEnginePrivate* m_reportEngine; + QCompleter* m_completer; + +}; + +} // namespace LimeReport + +#endif // LRSCRIPTEDITOR_H diff --git a/limereport/scripteditor/lrscripteditor.ui b/limereport/scripteditor/lrscripteditor.ui new file mode 100644 index 0000000..27f8e7f --- /dev/null +++ b/limereport/scripteditor/lrscripteditor.ui @@ -0,0 +1,115 @@ + + + LimeReport::ScriptEditor + + + + 0 + 0 + 706 + 541 + + + + Form + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 0 + 0 + + + + + 12 + + + + + + QTabWidget::South + + + 0 + + + + Data + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + false + + + + + + + + Functions + + + + + + Qt::Vertical + + + + false + + + + + + + + + + + + + + + + + + + LimeReport::CodeEditor + QTextEdit +
lrcodeeditor.h
+
+
+ + +
diff --git a/limereport/scripteditor/lrscripthighlighter.cpp b/limereport/scripteditor/lrscripthighlighter.cpp new file mode 100644 index 0000000..d88e18f --- /dev/null +++ b/limereport/scripteditor/lrscripthighlighter.cpp @@ -0,0 +1,226 @@ +#include "lrscripthighlighter.h" +#include +#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 {Start, MayBeKeyWord, Code, MayBeComment, Comment, Comment2, MayBeComment2End, String, String2, MayBeNumber, Separator, StatesCount}; + +void ScriptHighlighter::createParentheisisInfo(const char& literal, TextBlockData *data, const QString& text) +{ + int pos = text.indexOf(literal); + while (pos != -1) { + ParenthesisInfo *info = new ParenthesisInfo; + info->character = literal; + info->position = pos; + data->insert(info); + pos = text.indexOf(literal, pos + 1); + } +} + +void ScriptHighlighter::highlightBlock(const QString& text) +{ + int literal = -1; + bool lastWasBackSlash = false; + int state = previousBlockState() != -1 ? previousBlockState() : Start ; + int oldState = -1; + int stateMaschine[StatesCount][LiteralsCount] = { + {Separator, MayBeKeyWord, MayBeNumber, Separator, MayBeComment, Separator, Separator, String, String2, Separator, Separator}, + {Separator, MayBeKeyWord, Code, Separator, MayBeComment, Separator, Separator, String, String2, Separator, Separator}, + {Separator, Code, Code, Separator, Separator, Separator, Separator, String, String2, Separator, Separator}, + {Separator, Code, MayBeNumber, 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, Separator, Separator, 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]; + + buffer += currentChar; + + if (oldState != state){ + switch( state ){ + case MayBeComment: + if (oldState == MayBeNumber){ + setFormat(i-(buffer.length()-1), buffer.length()-1, m_formats[NumberFormat]); + } + buffer.clear(); + buffer += currentChar; + + break; + case MayBeKeyWord: + case MayBeNumber: + buffer.clear(); + buffer += currentChar; + break; + case Comment2: + setCurrentBlockState(Comment2); + case Separator: + switch(oldState){ + case MayBeComment2End: + setFormat(i-(buffer.length()-1), buffer.length(), m_formats[CommentFormat]); + setCurrentBlockState(-1); + buffer.clear(); + break; + case MayBeKeyWord: + if (isKeyWord(buffer.left(buffer.length()-1))){ + setFormat(i-(buffer.length()-1), buffer.length()-1, m_formats[KeywordFormat]); + } + buffer.clear(); + break; + case MayBeNumber: + setFormat(i-(buffer.length()-1), buffer.length()-1, m_formats[NumberFormat]); + buffer.clear(); + case String: + case String2: + setFormat(i-(buffer.length()-1), buffer.length(), m_formats[StringFormat]); + buffer.clear(); + break; + } + default: + break; + } + } + + if ( state == Comment || state == Comment2 ){ + setFormat(i-(buffer.length()-1), buffer.length(), m_formats[CommentFormat]); + } + + if ( state == String || state == String2 ){ + setFormat(i-(buffer.length()-1), buffer.length(), m_formats[StringFormat]); + } + + i++; + if ( i>= text.length()) break; + } + + TextBlockData *data = new TextBlockData; + + + for (int i = 0; i < PARENHEIS_COUNT; ++i){ + createParentheisisInfo(parenthesisCharacters[LeftParenthesis][i].toLatin1(), data, text); + createParentheisisInfo(parenthesisCharacters[RightParenthesis][i].toLatin1(), data, text); + } +// createParentheisisInfo('(', data, text); +// createParentheisisInfo(')', data, text); +// createParentheisisInfo('{', data, text); +// createParentheisisInfo('}', data, text); +// createParentheisisInfo('[', data, text); +// createParentheisisInfo(']', data, text); + + setCurrentBlockUserData(data); +} + +bool ScriptHighlighter::isKeyWord(const QString& word) +{ + for (int i = 0; i < KEYWORDS_COUNT-1; ++i){ + if (QLatin1String(keywords[i]) == word) return true; + } + return false; +} + +bool ScriptHighlighter::isDark(QColor color) +{ + double a = 1 - ( 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255; + return (a < 0.5); +} + +ScriptHighlighter::ScriptHighlighter(QTextDocument* parent): + QSyntaxHighlighter(parent) +{ + + if ( isDark(QPalette().background().color())){ + m_formats[NumberFormat].setForeground(Qt::darkBlue); + m_formats[StringFormat].setForeground(Qt::darkGreen); + m_formats[KeywordFormat].setForeground(Qt::darkYellow); + m_formats[CommentFormat].setForeground(Qt::darkGreen); + m_formats[CommentFormat].setFontItalic(true); + } else { + m_formats[NumberFormat].setForeground(QColor("#ff6aad")); + m_formats[StringFormat].setForeground(QColor("#b27f40")); + m_formats[KeywordFormat].setForeground(QColor("#45c5d5")); + m_formats[CommentFormat].setForeground(QColor("#a1a4a9")); + m_formats[CommentFormat].setFontItalic(true); + } +} + +QVector TextBlockData::parentheses() +{ + return m_parentheses; +} + +void TextBlockData::insert(ParenthesisInfo* info) +{ + int i = 0; + while (i < m_parentheses.size() && + info->position > m_parentheses.at(i)->position) + ++i; + + m_parentheses.insert(i, info); +} + +} // namespace LimeReport diff --git a/limereport/scripteditor/lrscripthighlighter.h b/limereport/scripteditor/lrscripthighlighter.h new file mode 100644 index 0000000..4580484 --- /dev/null +++ b/limereport/scripteditor/lrscripthighlighter.h @@ -0,0 +1,50 @@ +#ifndef LRSCRIPTHIGHLIGHTER_H +#define LRSCRIPTHIGHLIGHTER_H + +#include + +namespace LimeReport{ + +enum ParenthesisType {LeftParenthesis, RightParenthesis, ParenthesisTypeCount}; + +#define PARENHEIS_COUNT 3 +const QChar parenthesisCharacters[ParenthesisTypeCount][PARENHEIS_COUNT] = { + {'(', '{', '['}, + {')', '}', ']'} +}; + +struct ParenthesisInfo +{ + char character; + int position; +}; + +class TextBlockData : public QTextBlockUserData +{ +public: + TextBlockData(){} + QVector parentheses(); + void insert(ParenthesisInfo *info); + +private: + QVector m_parentheses; +}; + +class ScriptHighlighter : QSyntaxHighlighter{ +public: + ScriptHighlighter(QTextDocument* parent); +protected: + void highlightBlock(const QString& text); + enum ScriptFormats { + NumberFormat, StringFormat, KeywordFormat, + CommentFormat, FormatsCount + }; + QTextCharFormat m_formats[FormatsCount]; + bool isKeyWord(const QString& word); + bool isDark(QColor color); + void createParentheisisInfo(const char& literal, TextBlockData *data, const QString& text); +}; + + +} +#endif // LRSCRIPTHIGHLIGHTER_H