#include "lrcodeeditor.h" #include "lrglobal.h" #include "lrscripthighlighter.h" #include #include #include #include #include #include #include #include namespace LimeReport { CodeEditor::CodeEditor(QWidget* parent): QPlainTextEdit(parent), m_completer(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_completer = value; if (!m_completer) return; m_completer->setWidget(this); m_completer->setCompletionMode(QCompleter::PopupCompletion); m_completer->setCaseSensitivity(Qt::CaseInsensitive); connect(m_completer, SIGNAL(activated(QString)), this, SLOT(insertCompletion(QString))); } void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent* event) { QPainter painter(lineNumberArea); QStyleOption option; option.initFrom(this); // painter.fillRect(event->rect(), QPalette().window().color()); QColor bg = option.palette.window().color().darker(150); painter.fillRect(event->rect(), bg); 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(option.palette.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().boundingRect(QLatin1Char('9')).width() * 2 + fontMetrics().boundingRect(QLatin1Char('9')).width() * digits; return space; } void CodeEditor::keyPressEvent(QKeyEvent* e) { if (m_completer && m_completer->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: case Qt::Key_Right: case Qt::Key_Left: case Qt::Key_Up: case Qt::Key_Down: e->ignore(); return; default: break; } } bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); if (!m_completer || !isShortcut) QPlainTextEdit::keyPressEvent(e); const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); if (!m_completer || (ctrlOrShift && e->text().isEmpty())) return; bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift; QString completionPrefix = textUnderCursor(); if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 3 || Const::EOW.contains(e->text().right(1)))) { m_completer->popup()->hide(); return; } if (completionPrefix != m_completer->completionPrefix()) { m_completer->setCompletionPrefix(completionPrefix); m_completer->popup()->setCurrentIndex(m_completer->completionModel()->index(0, 0)); } QModelIndex ci = m_completer->completionModel()->index(0, 0); if (ci.isValid() && m_completer->completionModel()->data(ci).toString().compare(completionPrefix) == 0) { m_completer->popup()->hide(); return; } QRect cr = cursorRect(); cr.setWidth(m_completer->popup()->sizeHintForColumn(0) + m_completer->popup()->verticalScrollBar()->sizeHint().width()); m_completer->complete(cr); if (!completionPrefix.isEmpty() && completionPrefix.at(completionPrefix.length() - 1) == '.') { m_completer->popup(); } } void CodeEditor::focusInEvent(QFocusEvent* e) { if (m_completer) m_completer->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(); QString currentText; tc.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); QString blockText = tc.selectedText(); for (int i = blockText.length(); i > 0; --i) { if (!Const::EOW.contains(blockText.at(i - 1))) currentText = blockText.at(i - 1) + currentText; else break; } return currentText.trimmed(); } bool CodeEditor::matchLeftParenthesis(QTextBlock currentBlock, QChar parenthesisType, int i, int numLeftParentheses) { TextBlockData* data = static_cast(currentBlock.userData()); if (data) { 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()); if (data) { QVector parentheses = data->parentheses(); int docPos = currentBlock.position(); if (i == -2) i = parentheses.size() - 1; 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, -2, 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_completer->widget() != this) return; QTextCursor tc = textCursor(); // QString prefix = m_completer->completionPrefix(); // int extra = completion.length() - prefix.length(); for (int i = 0; i < m_completer->completionPrefix().length(); ++i) { tc.deletePreviousChar(); } tc.insertText(completion); // 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().window().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