--- /dev/null
+SOURCES += \
+ $$PWD/scripthighlighter.cpp \
+ $$PWD/textedit.cpp \
+ $$PWD/tabsettings.cpp \
+ $$PWD/scriptedit.cpp
+
+HEADERS += \
+ $$PWD/scripthighlighter.h \
+ $$PWD/textedit.h \
+ $$PWD/tabsettings.h \
+ $$PWD/scriptedit.h
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "scriptedit.h"
+
+#include "scripthighlighter.h"
+
+#include <QtGui/QStyle>
+#include <QtGui/QPainter>
+#include <QtCore/QTimer>
+#include <QtCore/QDebug>
+
+
+ScriptEdit::ScriptEdit(QWidget *parent)
+ : TextEdit(parent)
+{
+ highlighter = new ScriptHighlighter(this);
+}
+
+ScriptEdit::~ScriptEdit()
+{
+}
+
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef SCRIPTRUNNER_SCRIPTEDIT_H
+#define SCRIPTRUNNER_SCRIPTEDIT_H
+
+#include "textedit.h"
+
+class ScriptHighlighter;
+
+
+class ScriptEdit : public TextEdit
+{
+ Q_OBJECT
+
+public:
+ explicit ScriptEdit(QWidget *parent = 0);
+ ~ScriptEdit();
+
+private:
+ ScriptHighlighter *highlighter;
+ int m_executingLineNumber;
+};
+
+
+#endif // SCRIPTRUNNER_SCRIPTEDIT_H
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+
+#include "scripthighlighter.h"
+
+enum ScriptIds {
+ Comment = 1,
+ Number,
+ String,
+ Type,
+ Keyword,
+ PreProcessor,
+ Label
+};
+
+#define MAX_KEYWORD 63
+static const char *const keywords[MAX_KEYWORD] = {
+ "Infinity",
+ "NaN",
+ "abstract",
+ "boolean",
+ "break",
+ "byte",
+ "case",
+ "catch",
+ "char",
+ "class",
+ "const",
+ "constructor",
+ "continue",
+ "debugger",
+ "default",
+ "delete",
+ "do",
+ "double",
+ "else",
+ "enum",
+ "export",
+ "extends",
+ "false",
+ "final",
+ "finally",
+ "float",
+ "for",
+ "function",
+ "goto",
+ "if",
+ "implements",
+ "import",
+ "in",
+ "instanceof",
+ "int",
+ "interface",
+ "long",
+ "native",
+ "new",
+ "package",
+ "private",
+ "protected",
+ "public",
+ "return",
+ "short",
+ "static",
+ "super",
+ "switch",
+ "synchronized",
+ "this",
+ "throw",
+ "throws",
+ "transient",
+ "true",
+ "try",
+ "typeof",
+ "undefined",
+ "var",
+ "void",
+ "volatile",
+ "while",
+ "with", // end of array
+ 0
+};
+
+struct KeywordHelper
+{
+ inline KeywordHelper(const QString &word) : needle(word) {}
+ const QString needle;
+};
+
+static bool operator<(const KeywordHelper &helper, const char *kw)
+{
+ return helper.needle < QLatin1String(kw);
+}
+
+static bool operator<(const char *kw, const KeywordHelper &helper)
+{
+ return QLatin1String(kw) < helper.needle;
+}
+
+static bool isKeyword(const QString &word)
+{
+ const char * const *start = &keywords[0];
+ const char * const *end = &keywords[MAX_KEYWORD - 1];
+ const char * const *kw = qBinaryFind(start, end, KeywordHelper(word));
+
+ return kw != end;
+}
+
+ScriptHighlighter::ScriptHighlighter(TextEdit *editor) :
+ QSyntaxHighlighter(editor)
+{
+ setDocument(editor->document());
+ textEdit = editor;
+
+ m_formats[ScriptNumberFormat].setForeground(Qt::darkBlue);
+ m_formats[ScriptStringFormat].setForeground(Qt::darkGreen);
+ m_formats[ScriptTypeFormat].setForeground(Qt::darkMagenta);
+ m_formats[ScriptKeywordFormat].setForeground(Qt::darkYellow);
+ m_formats[ScriptPreprocessorFormat].setForeground(Qt::darkBlue);
+ m_formats[ScriptLabelFormat].setForeground(Qt::darkRed);
+ m_formats[ScriptCommentFormat].setForeground(Qt::darkGreen);
+ m_formats[ScriptCommentFormat].setFontItalic(true);
+
+ // parentheses matcher
+ m_formatRange = true;
+ m_matchFormat.setForeground(Qt::red);
+ m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
+ m_mismatchFormat.setBackground(Qt::magenta);
+
+}
+
+void ScriptHighlighter::highlightBlock(const QString &text)
+{
+
+ // states
+ enum States { StateStandard, StateCommentStart1, StateCCommentStart2,
+ StateScriptCommentStart2, StateCComment, StateScriptComment, StateCCommentEnd1,
+ StateCCommentEnd2, StateStringStart, StateString, StateStringEnd,
+ StateString2Start, StateString2, StateString2End,
+ StateNumber, StatePreProcessor, NumStates };
+
+ // tokens
+ enum Tokens { InputAlpha, InputNumber, InputAsterix, InputSlash, InputParen,
+ InputSpace, InputHash, InputQuotation, InputApostrophe, InputSep, NumTokens };
+
+ static uchar table[NumStates][NumTokens] = {
+ { StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStandard
+ { StateStandard, StateNumber, StateCCommentStart2, StateScriptCommentStart2, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCommentStart1
+ { StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentStart2
+ { StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // ScriptCommentStart2
+ { StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCComment
+ { StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // StateScriptComment
+ { StateCComment, StateCComment, StateCCommentEnd1, StateCCommentEnd2, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentEnd1
+ { StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCCommentEnd2
+ { StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateStringStart
+ { StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateString
+ { StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStringEnd
+ { StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2Start
+ { StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2
+ { StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateString2End
+ { StateNumber, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateNumber
+ { StatePreProcessor, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard } // StatePreProcessor
+ };
+
+ QString buffer;
+ buffer.reserve(text.length());
+
+ QTextCharFormat emptyFormat;
+
+ int state = StateStandard;
+ int braceDepth = 0;
+ const int previousState = previousBlockState();
+ if (previousState != -1) {
+ state = previousState & 0xff;
+ braceDepth = previousState >> 8;
+ }
+
+ if (text.isEmpty()) {
+ setCurrentBlockState(previousState);
+ TextEditDocumentLayout::clearParentheses(currentBlock());
+ return;
+ }
+ Parentheses parentheses;
+ parentheses.reserve(20); // assume wizard level ;-)
+ int input = -1;
+ int i = 0;
+ bool lastWasBackSlash = false;
+ bool makeLastStandard = false;
+
+ static const QString alphabeth = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ static const QString mathChars = QLatin1String("xXeE");
+ static const QString numbers = QLatin1String("0123456789");
+ bool questionMark = false;
+ QChar lastChar;
+
+ int firstNonSpace = -1;
+ int lastNonSpace = -1;
+
+ for (;;) {
+ const QChar c = text.at(i);
+
+ if (lastWasBackSlash) {
+ input = InputSep;
+ } else {
+ switch (c.toAscii()) {
+ case '*':
+ input = InputAsterix;
+ break;
+ case '/':
+ input = InputSlash;
+ break;
+ case '{':
+ braceDepth++;
+ // fall through
+ case '(': case '[':
+ input = InputParen;
+ switch (state) {
+ case StateStandard:
+ case StateNumber:
+ case StatePreProcessor:
+ case StateCCommentEnd2:
+ case StateCCommentEnd1:
+ case StateString2End:
+ case StateStringEnd:
+ parentheses.push_back(Parenthesis(Parenthesis::Opened, c, i));
+ break;
+ default:
+ break;
+ }
+ break;
+ case '}':
+ if (--braceDepth < 0)
+ braceDepth = 0;
+ // fall through
+ case ')': case ']':
+ input = InputParen;
+ switch (state) {
+ case StateStandard:
+ case StateNumber:
+ case StatePreProcessor:
+ case StateCCommentEnd2:
+ case StateCCommentEnd1:
+ case StateString2End:
+ case StateStringEnd:
+ parentheses.push_back(Parenthesis(Parenthesis::Closed, c, i));
+ break;
+ default:
+ break;
+ }
+ break;
+ case '#':
+ input = InputHash;
+ break;
+ case '"':
+ input = InputQuotation;
+ break;
+ case '\'':
+ input = InputApostrophe;
+ break;
+ case ' ':
+ input = InputSpace;
+ break;
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9': case '0':
+ if (alphabeth.contains(lastChar)
+ && (!mathChars.contains(lastChar) || !numbers.contains(text.at(i - 1)))
+ ) {
+ input = InputAlpha;
+ } else {
+ if (input == InputAlpha && numbers.contains(lastChar))
+ input = InputAlpha;
+ else
+ input = InputNumber;
+ }
+ break;
+ case ':': {
+ input = InputAlpha;
+ const QChar colon = QLatin1Char(':');
+ if (state == StateStandard && !questionMark && lastChar != colon) {
+ const QChar nextChar = i < text.length() - 1 ? text.at(i + 1) : QLatin1Char(' ');
+ if (nextChar != colon)
+ for (int j = 0; j < i; ++j) {
+ if (format(j) == emptyFormat )
+ setFormat(j, 1, m_formats[ScriptLabelFormat]);
+ }
+ }
+ } break;
+ default:
+ if (!questionMark && c == QLatin1Char('?'))
+ questionMark = true;
+ if (c.isLetter() || c == QLatin1Char('_'))
+ input = InputAlpha;
+ else
+ input = InputSep;
+ break;
+ }
+ }
+
+ if (input != InputSpace) {
+ if (firstNonSpace < 0)
+ firstNonSpace = i;
+ lastNonSpace = i;
+ }
+
+ lastWasBackSlash = !lastWasBackSlash && c == QLatin1Char('\\');
+
+ if (input == InputAlpha)
+ buffer += c;
+
+ state = table[state][input];
+
+ switch (state) {
+ case StateStandard: {
+ setFormat(i, 1, emptyFormat);
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ if (input != InputAlpha) {
+ highlightWord(i, buffer);
+ buffer = QString::null;
+ }
+ } break;
+ case StateCommentStart1:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = true;
+ buffer = QString::null;
+ break;
+ case StateCCommentStart2:
+ setFormat(i - 1, 2, m_formats[ScriptCommentFormat]);
+ makeLastStandard = false;
+ parentheses.push_back(Parenthesis(Parenthesis::Opened, QLatin1Char('/'), i-1));
+ buffer = QString::null;
+ break;
+ case StateScriptCommentStart2:
+ setFormat(i - 1, 2, m_formats[ScriptCommentFormat]);
+ makeLastStandard = false;
+ buffer = QString::null;
+ break;
+ case StateCComment:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptCommentFormat]);
+ buffer = QString::null;
+ break;
+ case StateScriptComment:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptCommentFormat]);
+ buffer = QString::null;
+ break;
+ case StateCCommentEnd1:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptCommentFormat]);
+ buffer = QString::null;
+ break;
+ case StateCCommentEnd2:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptCommentFormat]);
+ parentheses.push_back(Parenthesis(Parenthesis::Closed, QLatin1Char('/'), i));
+ buffer = QString::null;
+ break;
+ case StateStringStart:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, emptyFormat);
+ buffer = QString::null;
+ break;
+ case StateString:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptStringFormat]);
+ buffer = QString::null;
+ break;
+ case StateStringEnd:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, emptyFormat);
+ buffer = QString::null;
+ break;
+ case StateString2Start:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, emptyFormat);
+ buffer = QString::null;
+ break;
+ case StateString2:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptStringFormat]);
+ buffer = QString::null;
+ break;
+ case StateString2End:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, emptyFormat);
+ buffer = QString::null;
+ break;
+ case StateNumber:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptNumberFormat]);
+ buffer = QString::null;
+ break;
+ case StatePreProcessor:
+ if (makeLastStandard)
+ setFormat(i - 1, 1, emptyFormat);
+ makeLastStandard = false;
+ setFormat(i, 1, m_formats[ScriptPreprocessorFormat]);
+ buffer = QString::null;
+ break;
+ }
+
+ lastChar = c;
+ i++;
+ if (i >= text.length()) {
+ int collapse = Parenthesis::collapseAtPos(parentheses);
+ if (collapse >= 0) {
+ if (collapse == firstNonSpace)
+ TextEditDocumentLayout::userData(currentBlock())->setCollapse(TextBlockUserData::CollapseThis);
+ else
+ TextEditDocumentLayout::userData(currentBlock())->setCollapse(TextBlockUserData::CollapseAfter);
+ } else if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) {
+ userData->setCollapse(TextBlockUserData::NoCollapse);
+ }
+ break;
+ }
+ }
+
+ highlightWord(text.length(), buffer);
+
+ switch (state) {
+ case StateCComment:
+ case StateCCommentEnd1:
+ case StateCCommentStart2:
+ state = StateCComment;
+ break;
+ case StateString:
+ // quotes cannot span multiple lines, so if somebody starts
+ // typing a quoted string we don't need to look for the ending
+ // quote in another line (or highlight until the end of the
+ // document) and therefore slow down editing.
+ state = StateStandard;
+ break;
+ case StateString2:
+ state = StateStandard;
+ break;
+ default:
+ state = StateStandard;
+ break;
+ }
+
+ TextEditDocumentLayout::setParentheses(currentBlock(), parentheses);
+
+ setCurrentBlockState((braceDepth << 8) | state);
+}
+
+void ScriptHighlighter::highlightWord(int currentPos, const QString &buffer)
+{
+ if (buffer.isEmpty())
+ return;
+
+ // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY
+ // but don't highlight words like 'Query'
+ if (buffer.length() > 1
+ && buffer.at(0) == QLatin1Char('Q')
+ && (buffer.at(1).isUpper()
+ || buffer.at(1) == QLatin1Char('_')
+ || buffer.at(1) == QLatin1Char('t'))) {
+ setFormat(currentPos - buffer.length(), buffer.length(), m_formats[ScriptTypeFormat]);
+ } else {
+ if (isKeyword(buffer))
+ setFormat(currentPos - buffer.length(), buffer.length(), m_formats[ScriptKeywordFormat]);
+ }
+}
+
+
+ScriptHighlighter::MatchType ScriptHighlighter::checkOpenParenthesis(QTextCursor *cursor, QChar c)
+{
+ if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+ return NoMatch;
+
+ Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+ Parenthesis openParen, closedParen;
+ QTextBlock closedParenParag = cursor->block();
+
+ const int cursorPos = cursor->position() - closedParenParag.position();
+ int i = 0;
+ int ignore = 0;
+ bool foundOpen = false;
+ for (;;) {
+ if (!foundOpen) {
+ if (i >= parenList.count())
+ return NoMatch;
+ openParen = parenList.at(i);
+ if (openParen.pos != cursorPos) {
+ ++i;
+ continue;
+ } else {
+ foundOpen = true;
+ ++i;
+ }
+ }
+
+ if (i >= parenList.count()) {
+ for (;;) {
+ closedParenParag = closedParenParag.next();
+ if (!closedParenParag.isValid())
+ return NoMatch;
+ if (TextEditDocumentLayout::hasParentheses(closedParenParag)) {
+ parenList = TextEditDocumentLayout::parentheses(closedParenParag);
+ break;
+ }
+ }
+ i = 0;
+ }
+
+ closedParen = parenList.at(i);
+ if (closedParen.type == Parenthesis::Opened) {
+ ignore++;
+ ++i;
+ continue;
+ } else {
+ if (ignore > 0) {
+ ignore--;
+ ++i;
+ continue;
+ }
+
+ cursor->clearSelection();
+ cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);
+
+ if (c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}')
+ || c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')')
+ || c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']')
+ )
+ return Mismatch;
+
+ return Match;
+ }
+ }
+}
+
+ScriptHighlighter::MatchType ScriptHighlighter::checkClosedParenthesis(QTextCursor *cursor, QChar c)
+{
+
+ if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+ return NoMatch;
+
+ Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+ Parenthesis openParen, closedParen;
+ QTextBlock openParenParag = cursor->block();
+
+ const int cursorPos = cursor->position() - openParenParag.position();
+ int i = parenList.count() - 1;
+ int ignore = 0;
+ bool foundClosed = false;
+ for (;;) {
+ if (!foundClosed) {
+ if (i < 0)
+ return NoMatch;
+ closedParen = parenList.at(i);
+ if (closedParen.pos != cursorPos - 1) {
+ --i;
+ continue;
+ } else {
+ foundClosed = true;
+ --i;
+ }
+ }
+
+ if (i < 0) {
+ for (;;) {
+ openParenParag = openParenParag.previous();
+ if (!openParenParag.isValid())
+ return NoMatch;
+
+ if (TextEditDocumentLayout::hasParentheses(openParenParag)) {
+ parenList = TextEditDocumentLayout::parentheses(openParenParag);
+ break;
+ }
+ }
+ i = parenList.count() - 1;
+ }
+
+ openParen = parenList.at(i);
+ if (openParen.type == Parenthesis::Closed) {
+ ignore++;
+ --i;
+ continue;
+ } else {
+ if (ignore > 0) {
+ ignore--;
+ --i;
+ continue;
+ }
+
+ cursor->clearSelection();
+ cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);
+
+ if ( c == '}' && openParen.chr != '{' ||
+ c == ')' && openParen.chr != '(' ||
+ c == ']' && openParen.chr != '[' )
+ return Mismatch;
+
+ return Match;
+ }
+ }
+}
+ScriptHighlighter::MatchType ScriptHighlighter::matchCursor(QTextCursor *cursor)
+{
+ cursor->clearSelection();
+
+ const QTextBlock block = cursor->block();
+
+
+ if (!TextEditDocumentLayout::hasParentheses(block))
+ return NoMatch;
+
+ const int relPos = cursor->position() - block.position();
+
+ Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
+ const Parentheses::const_iterator cend = parentheses.constEnd();
+ for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
+ const Parenthesis &paren = *it;
+ if (paren.pos == relPos
+ && paren.type == Parenthesis::Opened) {
+ return checkOpenParenthesis(cursor, paren.chr);
+ } else if (paren.pos == relPos - 1
+ && paren.type == Parenthesis::Closed) {
+ return checkClosedParenthesis(cursor, paren.chr);
+ }
+ }
+ return NoMatch;
+}
+
+void ScriptHighlighter::matchParentheses()
+{
+ if (textEdit->isReadOnly())
+ return;
+
+ QTextCursor currentMatch = textEdit->textCursor();
+ const MatchType matchType = matchCursor(¤tMatch);
+ if (currentMatch.hasSelection()) {
+ QList<QTextEdit::ExtraSelection> extraSelections = textEdit->extraSelections();
+ QTextEdit::ExtraSelection sel;
+ if (matchType == Mismatch) {
+ sel.cursor = currentMatch;
+ sel.format = m_mismatchFormat;
+ } else {
+
+ if (m_formatRange) {
+ sel.cursor = currentMatch;
+ sel.format = m_rangeFormat;
+ extraSelections.append(sel);
+ }
+
+ sel.cursor = currentMatch;
+ sel.format = m_matchFormat;
+
+ sel.cursor.setPosition(currentMatch.selectionStart());
+ sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+ extraSelections.append(sel);
+
+ sel.cursor.setPosition(currentMatch.selectionEnd());
+ sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
+ }
+ extraSelections.append(sel);
+ textEdit->setExtraSelections(extraSelections);
+ }
+}
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef SCRIPTRUNNER_SCRIPTHIGHLIGHTER_H
+#define SCRIPTRUNNER_SCRIPTHIGHLIGHTER_H
+
+#include "textedit.h"
+
+#include <QtGui/QSyntaxHighlighter>
+#include <QtGui/QTextCharFormat>
+#include <QtCore/QtAlgorithms>
+
+
+class ScriptHighlighter : public QSyntaxHighlighter
+{
+ Q_OBJECT
+
+public:
+ ScriptHighlighter(TextEdit *editor);
+
+ virtual void highlightBlock(const QString &text);
+
+// // Set formats from a sequence of type QTextCharFormat
+// template <class InputIterator>
+// void setFormats(InputIterator begin, InputIterator end) {
+// qCopy(begin, end, m_formats);
+// }
+
+ enum ScriptFormats {
+ ScriptTextFormat, ScriptNumberFormat,
+ ScriptStringFormat, ScriptTypeFormat,
+ ScriptKeywordFormat, ScriptPreprocessorFormat,
+ ScriptLabelFormat, ScriptCommentFormat,
+ NumScriptFormats
+ };
+
+public Q_SLOTS:
+ void matchParentheses();
+
+private:
+ enum MatchType { NoMatch, Match, Mismatch };
+ static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c);
+ static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c);
+ static MatchType matchCursor(QTextCursor *cursor);
+
+ void highlightWord(int currentPos, const QString &buffer);
+
+ QTextCharFormat m_formats[NumScriptFormats];
+
+ // parentheses matcher
+ bool m_formatRange;
+ QTextCharFormat m_matchFormat;
+ QTextCharFormat m_mismatchFormat;
+ QTextCharFormat m_rangeFormat;
+
+ TextEdit *textEdit;
+};
+
+
+#endif // SCRIPTRUNNER_SCRIPTEDIT_H
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "tabsettings.h"
+#include <QtCore/QSettings>
+#include <QtCore/QString>
+#include <QtGui/QTextCursor>
+#include <QtGui/QTextDocument>
+
+static const char* spacesForTabsKey="SpacesForTabs";
+static const char* autoIndentKey="AutoIndent";
+static const char* indentSelectionKey="IndentSelection";
+static const char* indentSizeKey="IndentSize";
+static const char* groupPostfix ="TabSettings";
+
+
+TabSettings::TabSettings() :
+ m_spacesForTabs(true),
+ m_autoIndent(true),
+ m_indentSelection(true),
+ m_indentSize(4)
+{
+}
+
+void TabSettings::toSettings(const QString &category, QSettings *s) const
+{
+ QString group = QLatin1String(groupPostfix);
+ if (!category.isEmpty())
+ group.insert(0, category);
+ s->beginGroup(group);
+ s->setValue(QLatin1String(spacesForTabsKey), m_spacesForTabs);
+ s->setValue(QLatin1String(autoIndentKey), m_autoIndent);
+ s->setValue(QLatin1String(indentSelectionKey), m_indentSelection);
+ s->setValue(QLatin1String(indentSizeKey), m_indentSize);
+ s->endGroup();
+}
+
+void TabSettings::fromSettings(const QString &category, const QSettings *s)
+{
+ QString group = QLatin1String(groupPostfix);
+ if (!category.isEmpty())
+ group.insert(0, category);
+ group += QLatin1Char('/');
+
+ *this = TabSettings();
+
+ m_spacesForTabs = s->value(group + QLatin1String(spacesForTabsKey), m_spacesForTabs).toBool();
+ m_autoIndent = s->value(group + QLatin1String(autoIndentKey), m_autoIndent).toBool();
+ m_indentSelection = s->value(group + QLatin1String(indentSelectionKey), m_indentSelection).toBool();
+ m_indentSize = s->value(group + QLatin1String(indentSizeKey), m_indentSize).toInt();
+}
+
+void TabSettings::tabify(QString &s) const
+{
+ const QChar tab = QLatin1Char('\t');
+ const QChar blank = QLatin1Char(' ');
+ const QChar newLine = QLatin1Char('\n');
+
+ if (m_spacesForTabs) {
+ s.replace(tab, QString(m_indentSize, blank));
+ } else {
+ int i = 0;
+ forever {
+ for (int j = i; j < s.length(); ++j) {
+ if (s.at(j) != blank && s.at(j) != tab) {
+ if (j > i) {
+ const QString t = s.mid(i, j - i);
+ int spaces = 0;
+ for (int k = 0; k < t.length(); ++k)
+ spaces += (t.at(k) == blank ? 1 : m_indentSize);
+ s.remove(i, t.length());
+ const int tabs = spaces / m_indentSize;
+ spaces = spaces - (m_indentSize * tabs);
+ if (spaces > 0)
+ s.insert(i, QString(spaces, blank));
+ if (tabs > 0)
+ s.insert(i, QString(tabs, tab));
+ }
+ break;
+ }
+ }
+ i = s.indexOf(newLine, i);
+ if (i == -1)
+ break;
+ ++i;
+ }
+ }
+}
+
+QString TabSettings::indentationString(int indent) const
+{
+ if (indent == 0)
+ return QString();
+ QString indentString(indent, QLatin1Char(' '));
+ indentString.append(QLatin1Char('a'));
+ tabify(indentString);
+ indentString.truncate(indentString.length() - 1);
+ return indentString;
+}
+
+TabSettings::IndentationNumCharsPair TabSettings::indentation(const QString &s, IndentationCountMode icm) const
+{
+ IndentationNumCharsPair rc = IndentationNumCharsPair(0, 0);
+ const int size = s.size();
+ if (size == 0)
+ return rc;
+
+ const QChar tab = QLatin1Char('\t');
+ const QChar blank = QLatin1Char(' ');
+
+ for ( ; rc.second < size; rc.second++) {
+ const QChar c = s.at(rc.second);
+ if (c == tab)
+ rc.first += m_indentSize;
+ else {
+ if (c == blank || (icm == CountNonAlphaNumerical && !c.isLetterOrNumber()))
+ rc.first++;
+ else
+ break;
+ }
+ }
+ return rc;
+}
+
+void TabSettings::indentLine(QTextBlock block, int newIndent) const
+{
+ const QString blockText = block.text();
+ const int oldBlockLength = blockText.size();
+
+ // Quickly check whether indenting is required.
+ if (oldBlockLength == 0 && newIndent == 0)
+ return;
+
+ const QString indentString = indentationString(newIndent);
+ newIndent = indentString.length();
+
+ if (oldBlockLength == indentString.length() && blockText == indentString)
+ return;
+
+ if (oldBlockLength > indentString.length() &&
+ blockText.startsWith(indentString) &&
+ !blockText.at(indentString.length()).isSpace()) {
+ return;
+ }
+
+ QTextCursor cursor(block);
+ cursor.movePosition(QTextCursor::StartOfBlock);
+
+ const IndentationNumCharsPair oldIndentation = indentation(blockText);
+ if (oldIndentation.second > 0) {
+ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, oldIndentation.second);
+ cursor.removeSelectedText();
+ }
+
+ if (!indentString.isEmpty()) {
+ cursor.insertText(indentString);
+ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, indentString.length());
+ }
+}
+
+bool TabSettings::equals(const TabSettings &ts) const
+{
+ return m_spacesForTabs == ts.m_spacesForTabs &&
+ m_autoIndent == ts.m_autoIndent &&
+ m_indentSelection == ts.m_indentSelection &&
+ m_indentSize == ts.m_indentSize;
+}
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef TABSETTING_H
+#define TABSETTING_H
+#include <QtCore/QPair>
+#include <QtGui/QTextBlock>
+
+class QSettings;
+class QString;
+class QTextDocument;
+class QTextCursor;
+
+// Tab settings: Data type the TabSettingsPage acts on
+// with some convenience functions for formatting.
+struct TabSettings {
+ TabSettings();
+
+ void toSettings(const QString &category, QSettings *s) const;
+ void fromSettings(const QString &category, const QSettings *s);
+
+ void tabify(QString &s) const;
+ void indentLine(QTextBlock block, int newIndent) const;
+
+ // Determine indentation string
+ QString indentationString(int indent) const;
+
+ // A pair of <indentation>/<number of leading space characters<, returned by indentation.
+ typedef QPair<int, int> IndentationNumCharsPair;
+
+ // How to determine the indentation
+ enum IndentationCountMode {
+ // Count space characters only.
+ CountSpaceCharacters,
+ // Consider non-alphanumerical characters to be blanks.
+ // This can be used for plain text editors for writing bulleted lists
+ // with items that span several lines.
+ CountNonAlphaNumerical };
+
+ // Determine indentation
+ IndentationNumCharsPair indentation(const QString &, IndentationCountMode icm = CountSpaceCharacters) const;
+
+ bool m_spacesForTabs;
+ bool m_autoIndent;
+ bool m_indentSelection;
+ int m_indentSize;
+
+ bool equals(const TabSettings &ts) const;
+};
+
+inline bool operator==(const TabSettings &t1, const TabSettings &t2) { return t1.equals(t2); }
+inline bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); }
+
+
+#endif
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "textedit.h"
+
+#include <QtGui/QTextDocument>
+#include <QtGui/QTextBlock>
+#include <QtGui/QStyle>
+#include <QtGui/QPainter>
+#include <QtGui/QShortcut>
+#include <QtGui/QScrollBar>
+#include <QtGui/QToolTip>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QTimer>
+#include <QtCore/QDebug>
+
+#include <limits.h>
+
+
+int Parenthesis::collapseAtPos(const Parentheses &parentheses)
+{
+ int result = -1;
+
+ int depth = 0;
+ for (int i = 0; i < parentheses.size(); ++i) {
+ const Parenthesis &p = parentheses.at(i);
+ if (p.chr == QLatin1Char('{')) {
+ if (depth == 0)
+ result = p.pos;
+ ++depth;
+ } else if (p.chr == QLatin1Char('}')) {
+ if (--depth < 0)
+ depth = 0;
+ result = -1;
+ }
+ }
+ return result;
+}
+
+
+bool TextBlockUserData::hasClosingCollapse() const
+{
+ int depth = 0;
+ for (int i = 0; i < m_parentheses.size(); ++i) {
+ const Parenthesis &p = m_parentheses.at(i);
+ if (p.chr == QLatin1Char('{')) {
+ ++depth;
+ } else if (p.chr == QLatin1Char('}')) {
+ if (--depth < 0)
+ return true;
+ }
+ }
+ return false;
+}
+
+int TextBlockUserData::collapseAtPos() const
+{
+ return Parenthesis::collapseAtPos(m_parentheses);
+}
+
+
+void TextEditDocumentLayout::setParentheses(const QTextBlock &block, const Parentheses &parentheses)
+{
+ if (parentheses.isEmpty()) {
+ if (TextBlockUserData *userData = testUserData(block))
+ userData->clearParentheses();
+ } else {
+ userData(block)->setParentheses(parentheses);
+ }
+}
+
+Parentheses TextEditDocumentLayout::parentheses(const QTextBlock &block)
+{
+ if (TextBlockUserData *userData = testUserData(block))
+ return userData->parentheses();
+ return Parentheses();
+}
+
+bool TextEditDocumentLayout::hasParentheses(const QTextBlock &block)
+{
+ if (TextBlockUserData *userData = testUserData(block))
+ return userData->hasParentheses();
+ return false;
+}
+
+
+
+
+
+QRectF TextEditDocumentLayout::blockBoundingRect(const QTextBlock &block) const
+{
+ QRectF r = QPlainTextDocumentLayout::blockBoundingRect(block);
+ return r;
+}
+
+class TextEditExtraArea : public QWidget {
+ TextEdit *textEdit;
+public:
+ TextEditExtraArea(TextEdit *edit):QWidget(edit) {
+ textEdit = edit;
+ setAutoFillBackground(true);
+ }
+public:
+
+ QSize sizeHint() const {
+ return QSize(textEdit->extraAreaWidth(), 0);
+ }
+protected:
+ void paintEvent(QPaintEvent *event){
+ textEdit->extraAreaPaintEvent(event);
+ }
+ void mousePressEvent(QMouseEvent *event){
+ textEdit->extraAreaMouseEvent(event);
+ }
+ void mouseMoveEvent(QMouseEvent *event){
+ textEdit->extraAreaMouseEvent(event);
+ }
+ void mouseReleaseEvent(QMouseEvent *event){
+ textEdit->extraAreaMouseEvent(event);
+ }
+
+ void wheelEvent(QWheelEvent *event) {
+ QCoreApplication::sendEvent(textEdit->viewport(), event);
+ }
+};
+
+
+
+TextEdit::TextEdit(QWidget *parent) : QPlainTextEdit(parent)
+{
+ viewport()->setMouseTracking(true);
+ extraAreaSelectionAnchorBlockNumber = extraAreaToggleBreakpointBlockNumber = -1;
+ QTextDocument *doc = document();
+ doc->setDocumentLayout(new TextEditDocumentLayout(doc));
+
+ connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
+ connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool)));
+ connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
+ m_extraArea = new TextEditExtraArea(this);
+ m_extraArea->setMouseTracking(true);
+ connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(slotUpdateRequest(QRect, int)));
+
+ baseLine = 1;
+
+ executionIcon = QIcon(":/images/location.svg");
+ bookmarkIcon = QIcon(":/images/bookmark.svg");
+ breakpointIcon = QIcon(":/images/breakpoint.svg");
+ breakpointPendingIcon = QIcon(":/images/pending.svg");
+
+ // parentheses matcher
+ m_formatRange = true;
+ m_matchFormat.setForeground(Qt::red);
+ m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
+ m_mismatchFormat.setBackground(Qt::magenta);
+ m_parenthesesMatchingTimer = new QTimer(this);
+ m_parenthesesMatchingTimer->setSingleShot(true);
+ connect(m_parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(matchParentheses()));
+
+ slotUpdateExtraAreaWidth();
+ slotCursorPositionChanged();
+}
+
+TextEdit::~TextEdit()
+{
+}
+
+
+bool TextEdit::viewportEvent(QEvent *event)
+{
+ if (event->type() == QEvent::ToolTip) {
+ QString text;
+ QRect box;
+ if (collapsedBlockAt(static_cast<QHelpEvent*>(event)->pos(), &text, &box).isValid()) {
+ QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), text, viewport(), box);
+ }
+ return true;
+ }
+ return QPlainTextEdit::viewportEvent(event);
+}
+
+
+void TextEdit::resizeEvent(QResizeEvent *e)
+{
+ QPlainTextEdit::resizeEvent(e);
+ QRect cr = contentsRect();
+ m_extraArea->setGeometry(
+ QStyle::visualRect(layoutDirection(), cr,
+ QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
+}
+
+QRect TextEdit::collapseBox(const QTextBlock &block)
+{
+ QRectF br = blockBoundingGeometry(block).translated(contentOffset());
+ int collapseBoxWidth = fontMetrics().lineSpacing();
+ return QRect(m_extraArea->width() - collapseBoxWidth + collapseBoxWidth/4,
+ int(br.top()) + collapseBoxWidth/4,
+ 2 * (collapseBoxWidth/4) + 1, 2 * (collapseBoxWidth/4) + 1);
+
+}
+
+QTextBlock TextEdit::collapsedBlockAt(const QPoint &pos, QString *text, QRect *box) const {
+ QPointF offset(contentOffset());
+ QTextBlock block = firstVisibleBlock();
+ int top = (int)blockBoundingGeometry(block).translated(offset).top();
+ int bottom = top + (int)blockBoundingRect(block).height();
+
+ int viewportHeight = viewport()->height();
+
+ while (block.isValid() && top <= viewportHeight) {
+ QTextBlock nextBlock = block.next();
+ if (block.isVisible() && bottom >= 0) {
+ if (nextBlock.isValid() && !nextBlock.isVisible()) {
+ QTextLayout *layout = block.layout();
+ QTextLine line = layout->lineAt(layout->lineCount()-1);
+ QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
+ lineRect.adjust(0, 0, -1, -1);
+
+ QRectF collapseRect(lineRect.right() + 8, lineRect.top(),
+ fontMetrics().width(QLatin1String(" {...}; ")),
+ lineRect.height());
+ if (collapseRect.contains(pos)) {
+ QTextBlock result = block;
+ if (box)
+ *box = collapseRect.toAlignedRect();
+ if (text) {
+ *text = QString();
+ block = nextBlock;
+ int lineCount = 15;
+ while (nextBlock.isValid() && !nextBlock.isVisible()) {
+ block = nextBlock;
+ if (lineCount) {
+ --lineCount;
+ if (!text->isEmpty())
+ *text += QLatin1Char('\n');
+ *text += block.text();
+ }
+ nextBlock = block.next();
+ }
+ if (lineCount == 0)
+ *text += QLatin1String("\n...");
+ }
+ return result;
+ } else {
+ block = nextBlock;
+ while (nextBlock.isValid() && !nextBlock.isVisible()) {
+ block = nextBlock;
+ nextBlock = block.next();
+ }
+ }
+ }
+ }
+
+ block = nextBlock;
+ top = bottom;
+ bottom = top + (int)blockBoundingRect(block).height();
+ }
+ return QTextBlock();
+}
+
+void TextEdit::paintEvent(QPaintEvent *e)
+{
+ QPlainTextEdit::paintEvent(e);
+
+ QPainter painter(viewport());
+ QPointF offset(contentOffset());
+ QTextBlock block = firstVisibleBlock();
+ int top = (int)blockBoundingGeometry(block).translated(offset).top();
+ int bottom = top + (int)blockBoundingRect(block).height();
+
+ while (block.isValid() && top <= e->rect().bottom()) {
+ QTextBlock nextBlock = block.next();
+ if (block.isVisible() && bottom >= e->rect().top()) {
+ if (nextBlock.isValid() && !nextBlock.isVisible()) {
+ QTextLayout *layout = block.layout();
+ QTextLine line = layout->lineAt(layout->lineCount()-1);
+ QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
+ lineRect.adjust(0, 0, -1, -1);
+
+ QRectF collapseRect(lineRect.right() + 8, lineRect.top(),
+ fontMetrics().width(QLatin1String(" {...}; ")),
+ lineRect.height());
+ painter.drawRect(collapseRect.adjusted(0, 0, 0, -1));
+
+
+ QString replacement = QLatin1String("...");
+
+ QTextBlock info = block;
+ if (block.userData()
+ && static_cast<TextBlockUserData*>(block.userData())->collapse() == TextBlockUserData::CollapseAfter)
+ ;
+ else if (block.next().userData()
+ && static_cast<TextBlockUserData*>(block.next().userData())->collapse()
+ == TextBlockUserData::CollapseThis) {
+ replacement.prepend(nextBlock.text().trimmed().left(1));
+ info = nextBlock;
+ }
+
+ block = nextBlock;
+ while (nextBlock.isValid() && !nextBlock.isVisible()) {
+ block = nextBlock;
+ nextBlock = block.next();
+ }
+ if (static_cast<TextBlockUserData*>(info.userData())->collapseIncludesClosure()) {
+ QString right = block.text().trimmed();
+ if (right.endsWith(QLatin1Char(';'))) {
+ right.chop(1);
+ right = right.trimmed();
+ replacement.append(right.right(1));
+ replacement.append(QLatin1Char(';'));
+ } else {
+ replacement.append(right.right(1));
+ }
+ }
+ painter.drawText(collapseRect, Qt::AlignCenter, replacement);
+ }
+ }
+
+ block = nextBlock;
+ top = bottom;
+ bottom = top + (int)blockBoundingRect(block).height();
+ }
+
+
+}
+
+void TextEdit::slotUpdateExtraAreaWidth()
+{
+ if (isLeftToRight())
+ setViewportMargins(extraAreaWidth(), 0, 0, 0);
+ else
+ setViewportMargins(0, 0, extraAreaWidth(), 0);
+}
+
+
+QWidget *TextEdit::extraArea() const {
+ return m_extraArea;
+}
+
+int TextEdit::extraAreaWidth(int *markWidthPtr) const {
+ int digits = 1;
+ int max = qMax(1, blockCount());
+ while (max >= 10) {
+ max /= 10;
+ ++digits;
+ }
+ QFontMetrics fm(fontMetrics());
+ int space = fm.width(QLatin1Char('9')) * digits;
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+ Q_ASSERT(documentLayout);
+ int markWidth = 0;
+ markWidth += fm.lineSpacing();
+ if (documentLayout->doubleMarkCount)
+ markWidth += fm.lineSpacing() / 3;
+
+ if (markWidthPtr)
+ *markWidthPtr = markWidth;
+
+ space += markWidth;
+
+
+ space += 4;
+
+ space += fm.lineSpacing();
+
+ return space;
+}
+
+void TextEdit::slotModificationChanged(bool m)
+{
+ if (!m) {
+ QTextDocument *doc = document();
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+ Q_ASSERT(documentLayout);
+ int oldLastSaveRevision = documentLayout->lastSaveRevision;
+ documentLayout->lastSaveRevision = doc->revision();
+ QTextBlock block = doc->begin();
+ while (block.isValid()) {
+ if (block.revision() < 0 || block.revision() != oldLastSaveRevision) {
+ block.setRevision(-documentLayout->lastSaveRevision - 1);
+ } else {
+ block.setRevision(documentLayout->lastSaveRevision);
+ }
+ block = block.next();
+ }
+ m_extraArea->update();
+ }
+}
+
+void TextEdit::slotUpdateRequest(const QRect &r, int dy)
+{
+ if (dy)
+ m_extraArea->scroll(0, dy);
+ else
+ m_extraArea->update(0, r.y(), m_extraArea->width(), r.height());
+
+ if (r.contains(viewport()->rect()))
+ slotUpdateExtraAreaWidth();
+}
+
+
+int TextEdit::executionBlockNumber() const
+{
+ QTextDocument *doc = document();
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+ Q_ASSERT(documentLayout);
+ return documentLayout->executionBlockNumber();
+}
+
+void TextEdit::setExecutionBlockNumber(int blockNumber)
+{
+ QTextDocument *doc = document();
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+ Q_ASSERT(documentLayout);
+ documentLayout->setExecutionBlockNumber(blockNumber);
+
+ if (true) {
+ QTextBlock block = document()->findBlockByNumber(blockNumber);
+ if (block.isValid()) {
+ QTextCursor cursor = textCursor();
+ cursor.setPosition(block.position());
+ setTextCursor(cursor);
+ }
+ }
+}
+
+void TextEdit::extraAreaPaintEvent(QPaintEvent *e)
+{
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+ Q_ASSERT(documentLayout);
+
+ QPalette pal = palette();
+ QPainter painter(m_extraArea);
+ QFontMetrics fm(painter.fontMetrics());
+ int fmLineSpacing = fm.lineSpacing();
+
+ int markWidth = 0;
+ markWidth += fm.lineSpacing();
+ if (documentLayout->doubleMarkCount)
+ markWidth += fm.lineSpacing() / 3;
+
+
+ int collapseBoxWidth = fmLineSpacing;
+ int extraAreaWidth = m_extraArea->width() - collapseBoxWidth;
+
+ QLinearGradient gradient(QPointF(extraAreaWidth - 10, 0), QPointF(extraAreaWidth, 0));
+ gradient.setColorAt(0, pal.color(QPalette::Background));
+ gradient.setColorAt(1, pal.color(QPalette::Base));
+ painter.fillRect(e->rect(), gradient);
+
+ QLinearGradient gradient2(QPointF(0, 0), QPointF(markWidth, 0));
+ gradient2.setColorAt(0, pal.color(QPalette::Dark));
+ gradient2.setColorAt(1, pal.color(QPalette::Background));
+ painter.fillRect(e->rect().intersected(QRect(0, 0, markWidth, INT_MAX)), gradient2);
+
+
+ painter.setPen(QPen(pal.color(QPalette::Background), 2));
+ painter.drawLine(extraAreaWidth-1, e->rect().top(), extraAreaWidth-1, e->rect().bottom());
+ painter.setRenderHint(QPainter::Antialiasing);
+
+
+ QTextBlock block = firstVisibleBlock();
+ int blockNumber = block.blockNumber();
+ int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top();
+ int bottom = top + (int)blockBoundingRect(block).height();
+
+ int executionBlock = executionBlockNumber();
+
+ while (block.isValid() && top <= e->rect().bottom()) {
+ bool collapseThis = false;
+ bool collapseAfter = false;
+ bool hasClosingCollapse = false;
+ QTextBlock nextBlock = block.next();
+ if (block.isVisible() && bottom >= e->rect().top()) {
+ QString number = QString::number(blockNumber + baseLine);
+ painter.setPen(pal.color(QPalette::Highlight));
+
+ if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) {
+ int x = 0;
+ int radius = fmLineSpacing - 1;
+ if (userData->bookmark()) {
+ QRect r(x, top, radius, radius);
+ bookmarkIcon.paint(&painter, r);
+ x += fmLineSpacing / 3;
+ }
+ if (userData->breakpoint() || userData->pendingBreakpoint()) {
+ QRect r(x, top, radius, radius);
+ breakpointIcon.paint(&painter, r);
+ if (!userData->breakpoint()) { // pending only
+ breakpointPendingIcon.paint(&painter, r);
+ }
+ }
+
+ collapseAfter = (userData->collapse() == TextBlockUserData::CollapseAfter);
+ collapseThis = (userData->collapse() == TextBlockUserData::CollapseThis);
+ hasClosingCollapse = userData->hasClosingCollapse();
+ }
+
+
+ if (executionBlock == blockNumber) {
+ QRect br(0, top, markWidth, fmLineSpacing);
+ executionIcon.paint(&painter, br);
+ }
+
+
+ int previousBraceDepth = block.previous().userState();
+ if (previousBraceDepth >= 0)
+ previousBraceDepth >>= 8;
+ else
+ previousBraceDepth = 0;
+ int braceDepth = block.userState();
+ if (!nextBlock.isVisible()) {
+ QTextBlock b = nextBlock;
+ while (b.isValid() && !b.isVisible())
+ b = b.next();
+ braceDepth = b.userState();
+ }
+ if (braceDepth >= 0)
+ braceDepth >>= 8;
+ else
+ braceDepth = 0;
+
+ QRect box(extraAreaWidth + collapseBoxWidth/4, top + collapseBoxWidth/4,
+ 2 * (collapseBoxWidth/4) + 1, 2 * (collapseBoxWidth/4) + 1);
+ QPoint boxCenter = box.center();
+ painter.save();
+ painter.setPen(pal.text().color());
+ painter.setRenderHint(QPainter::Antialiasing, false);
+
+ bool collapseNext = (nextBlock.userData() && static_cast<TextBlockUserData*>(nextBlock.userData())->collapse() == TextBlockUserData::CollapseThis);
+
+ if (previousBraceDepth || collapseThis)
+ painter.drawLine(boxCenter.x(), top, boxCenter.x(), boxCenter.y());
+
+ if (braceDepth || (collapseNext && nextBlock.isVisible()))
+ painter.drawLine(boxCenter.x(), boxCenter.y(), boxCenter.x(), bottom-1);
+
+ if (collapseAfter || collapseNext) {
+ painter.setBrush(pal.background());
+ painter.drawRect(box.adjusted(0, 0, -1, -1));
+ if (!nextBlock.isVisible())
+ painter.drawLine(boxCenter.x(), box.top() + 2, boxCenter.x(), box.bottom() - 2);
+ painter.drawLine(box.left() + 2, boxCenter.y(), box.right() - 2, boxCenter.y());
+ } else if (hasClosingCollapse) {
+ painter.drawLine(boxCenter.x(), boxCenter.y(), box.right() - 1, boxCenter.y());
+ }
+
+ painter.restore();
+
+
+// qDebug() << "block" << block.blockNumber() << "revision:" << block.revision() << "lastSaved:" << documentLayout->lastSaveRevision;
+ if (block.revision() != documentLayout->lastSaveRevision) {
+ painter.save();
+ painter.setRenderHint(QPainter::Antialiasing, false);
+ if (block.revision() < 0)
+ painter.setPen(QPen(Qt::darkGreen, 2));
+ else
+ painter.setPen(QPen(Qt::red, 2));
+ painter.drawLine(extraAreaWidth-1, top, extraAreaWidth-1, bottom-1);
+ painter.restore();
+ }
+
+ painter.drawText(markWidth, top, extraAreaWidth - markWidth - 4, fm.height(), Qt::AlignRight, number);
+// painter.drawText(markWidth + 4, top + fm.ascent() + fm.leading(), number);
+
+
+ }
+ block = nextBlock;
+ top = bottom;
+ bottom = top + (int)blockBoundingRect(block).height();
+ ++blockNumber;
+ }
+
+}
+
+void TextEdit::timerEvent(QTimerEvent *e)
+{
+ if (e->timerId() == autoScrollTimer.timerId()) {
+ const QPoint globalPos = QCursor::pos();
+ const QPoint pos = m_extraArea->mapFromGlobal(globalPos);
+ QRect visible = m_extraArea->rect();
+ verticalScrollBar()->triggerAction( pos.y() < visible.center().y() ?
+ QAbstractSlider::SliderSingleStepSub
+ : QAbstractSlider::SliderSingleStepAdd);
+ QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+ extraAreaMouseEvent(&ev);
+ int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height();
+ if (delta < 7)
+ delta = 7;
+ int timeout = 4900 / (delta * delta);
+ autoScrollTimer.start(timeout, this);
+
+ }
+ QPlainTextEdit::timerEvent(e);
+}
+
+
+void TextEdit::mouseMoveEvent(QMouseEvent *e)
+{
+ if (e->buttons() == 0) {
+ QTextBlock collapsedBlock = collapsedBlockAt(e->pos());
+ viewport()->setCursor(collapsedBlock.isValid() ? Qt::PointingHandCursor : Qt::IBeamCursor);
+ } else {
+ QPlainTextEdit::mouseMoveEvent(e);
+ }
+}
+
+void TextEdit::mousePressEvent(QMouseEvent *e)
+{
+ if (e->button() == Qt::LeftButton) {
+ QTextBlock collapsedBlock = collapsedBlockAt(e->pos());
+ if (collapsedBlock.isValid()) {
+ toggleBlockVisible(collapsedBlock);
+ viewport()->setCursor(Qt::IBeamCursor);
+ }
+ }
+ QPlainTextEdit::mousePressEvent(e);
+}
+
+void TextEdit::extraAreaMouseEvent(QMouseEvent *e)
+{
+ QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
+ cursor.setPosition(cursor.block().position());
+
+ int markWidth;
+ extraAreaWidth(&markWidth);
+
+ if (e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
+ bool hand = (e->pos().x() <= markWidth
+ || (TextBlockUserData::canCollapse(cursor.block())
+ && collapseBox(cursor.block()).contains(e->pos())));
+ if (hand != (m_extraArea->cursor().shape() == Qt::PointingHandCursor))
+ m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor);
+ return;
+ }
+
+
+ if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
+ if (e->button() == Qt::LeftButton) {
+ if (TextBlockUserData::canCollapse(cursor.block())
+ && collapseBox(cursor.block()).contains(e->pos())) {
+ setTextCursor(cursor);
+ toggleBlockVisible(cursor.block());
+ } else if (e->pos().x() > markWidth) {
+ QTextCursor selection = cursor;
+ extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
+ selection.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
+ setTextCursor(selection);
+ } else {
+ extraAreaToggleBreakpointBlockNumber = cursor.blockNumber();
+ }
+ }
+ } else if (extraAreaSelectionAnchorBlockNumber >= 0) {
+ QTextCursor selection = cursor;
+ if (e->type() == QEvent::MouseMove) {
+ QTextBlock anchorBlock = document()->findBlockByNumber(extraAreaSelectionAnchorBlockNumber);
+ if (extraAreaSelectionAnchorBlockNumber == cursor.blockNumber()) {
+ selection.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
+ } else {
+ selection.setPosition(anchorBlock.position() +
+ (cursor.blockNumber() <= extraAreaSelectionAnchorBlockNumber ?
+ anchorBlock.length() - (anchorBlock.next().isValid()?0:1) : 0));
+ selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor);
+ }
+
+ if (e->pos().y() >= 0 && e->pos().y() <= m_extraArea->height())
+ autoScrollTimer.stop();
+ else if (!autoScrollTimer.isActive())
+ autoScrollTimer.start(100, this);
+
+ } else {
+ autoScrollTimer.stop();
+ extraAreaSelectionAnchorBlockNumber = -1;
+ return;
+ }
+ setTextCursor(selection);
+ } else if (extraAreaToggleBreakpointBlockNumber >= 0) {
+ if (e->type() == QEvent::MouseButtonRelease && e->button() == Qt::LeftButton) {
+ if (cursor.blockNumber() == extraAreaToggleBreakpointBlockNumber) {
+ bool b = false;
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(cursor.block()))
+ b = userData->breakpoint();
+ setBreakpoint_helper(extraAreaToggleBreakpointBlockNumber, !b);
+ }
+ extraAreaToggleBreakpointBlockNumber = -1;
+ }
+ }
+}
+
+void TextEdit::slotCursorPositionChanged()
+{
+
+ QList<QTextEdit::ExtraSelection> extraSelections;
+
+ if (true || !isReadOnly()){ // the line selection
+
+ QTextEdit::ExtraSelection sel;
+
+ QTextCursor cursor = textCursor();
+ const QColor fg = palette().color(QPalette::Highlight);
+ const QColor bg = palette().color(QPalette::Base);
+ QColor col;
+ const qreal ratio = 0.15;
+ col.setRedF(fg.redF() * ratio + bg.redF() * (1 - ratio));
+ col.setGreenF(fg.greenF() * ratio + bg.greenF() * (1 - ratio));
+ col.setBlueF(fg.blueF() * ratio + bg.blueF() * (1 - ratio));
+ sel.format.setBackground(col);
+ sel.format.setProperty(QTextFormat::FullWidthSelection, true);
+ sel.cursor = cursor;
+ sel.cursor.clearSelection();
+ extraSelections.append(sel);
+
+ m_parenthesesMatchingTimer->start(50);
+ }
+
+ setExtraSelections(extraSelections);
+}
+
+void TextEdit::slotToggleBookmark()
+{
+ QTextBlock block = textCursor().block();
+ bool b = false;
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ b = userData->bookmark();
+ setBookmark_helper(block.blockNumber(), !b);
+}
+
+void TextEdit::slotToggleBreakpoint()
+{
+ QTextBlock block = textCursor().block();
+ bool b = false;
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ b = userData->breakpoint();
+ setBreakpoint_helper(block.blockNumber(), !b);
+}
+
+void TextEdit::slotTogglePendingBreakpoint()
+{
+ QTextBlock block = textCursor().block();
+ bool b = false;
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ b = userData->pendingBreakpoint();
+ setPendingBreakpoint_helper(block.blockNumber(), !b);
+}
+
+void TextEdit::slotSetExecutionBlock()
+{
+ setExecutionBlockNumber(textCursor().block().blockNumber());
+}
+
+
+void TextBlockUserData::setCollapse(const QTextBlock& block, bool visible)
+{
+ QTextBlock info = block;
+ if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->collapse() == CollapseAfter)
+ ;
+ else if (block.next().userData()
+ && static_cast<TextBlockUserData*>(block.next().userData())->collapse()
+ == TextBlockUserData::CollapseThis)
+ info = block.next();
+ else
+ return;
+ int pos = static_cast<TextBlockUserData*>(info.userData())->collapseAtPos();
+ if (pos < 0)
+ return;
+ QTextCursor cursor(info);
+ cursor.setPosition(cursor.position() + pos);
+ if (matchCursorForward(&cursor) != Match) {
+ if (visible) {
+ // no match, at least unfold!
+ QTextBlock b = block.next();
+ while (b.isValid() && !b.isVisible()) {
+ b.setVisible(true);
+ b = b.next();
+ }
+ }
+ return;
+ }
+
+ QTextBlock b = block.next();
+ while (b < cursor.block()) {
+ b.setVisible(visible);
+ b = b.next();
+ }
+
+ Q_ASSERT(cursor.block() == b);
+ int relPos = cursor.position() - b.position();
+ QString s = b.text().mid(relPos).trimmed();
+ bool collapseIncludesClosure = (s.isEmpty() || s == QLatin1String(";"));
+ if (collapseIncludesClosure)
+ b.setVisible(visible);
+ static_cast<TextBlockUserData*>(info.userData())->setCollapseIncludesClosure(collapseIncludesClosure);
+}
+
+void TextEdit::toggleBlockVisible(const QTextBlock &block)
+{
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+ Q_ASSERT(documentLayout);
+
+ if (!TextBlockUserData::canCollapse(block))
+ return;
+
+ bool visible = block.next().isVisible();
+ TextBlockUserData::setCollapse(block, !visible);
+ documentLayout->requestUpdate();
+}
+
+void TextEdit::slotToggleBlockVisible()
+{
+ toggleBlockVisible(textCursor().block());
+}
+
+
+QChar TextEdit::charAt(int pos) const
+{
+ QTextCursor c = textCursor();
+
+ if (pos < 0)
+ return QChar();
+
+ c.movePosition(QTextCursor::End);
+ if (pos >= c.position())
+ return QChar();
+
+ c.setPosition(pos);
+ c.setPosition(pos + 1, QTextCursor::KeepAnchor);
+
+ return c.selectedText().at(0);
+}
+
+
+void TextEdit::keyPressEvent(QKeyEvent *e)
+{
+ switch (e->key()) {
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ if (m_tabsettings.m_autoIndent) {
+ QTextCursor cursor = textCursor();
+ cursor.insertBlock();
+ const QString leftText = cursor.block().text().left(cursor.position() - cursor.block().position());
+ if (cursor.hasSelection() || leftText.simplified().isEmpty()) {
+ indent(document(), cursor, QChar::Null);
+ }
+ e->accept();
+ setTextCursor(cursor);
+ return;
+ }
+ break;
+ case Qt::Key_Tab:
+ if (m_tabsettings.m_spacesForTabs) {
+ indentSelection();
+ e->accept();
+ return;
+ }
+ break;
+ case Qt::Key_Backtab:
+ if (m_tabsettings.m_indentSelection) {
+ unIndentSelection();
+ e->accept();
+ return;
+ }
+ break;
+ case Qt::Key_Home:
+ if (!(e == QKeySequence::MoveToStartOfDocument)) {
+ handleHomeKey(e->modifiers() == Qt::ShiftModifier);
+ e->accept();
+ return;
+ }
+ break;
+ case Qt::Key_Left:
+ case Qt::Key_Right:
+ if ((e->modifiers() == Qt::NoModifier) && textCursor().hasSelection()) {
+ handleArrowKeys(e->key());
+ e->accept();
+ return;
+ }
+ break;
+ case Qt::Key_BraceLeft:
+ case Qt::Key_BraceRight: {
+ const QString text = e->text();
+ if (!text.isEmpty()) {
+ const QChar c = text.at(0);
+ if (c == QLatin1Char('}') || c == QLatin1Char('{')) {
+ QTextCursor cursor = textCursor();
+ cursor.insertText(QString(c));
+ const QString leftText = cursor.block().text().left(cursor.position() - 1 - cursor.block().position());
+ if (cursor.hasSelection() || leftText.simplified().isEmpty()) {
+ indent(document(), cursor, c);
+ }
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+ Q_ASSERT(documentLayout);
+ documentLayout->requestUpdate(); // a bit drastic
+ e->accept();
+ setTextCursor(cursor);
+ return;
+ }
+ }
+ } break;
+
+ default:
+ break;
+ }
+ QPlainTextEdit::keyPressEvent(e);
+ if (e->key() == Qt::Key_Delete)
+ slotCursorPositionChanged(); // parentheses matching
+}
+
+void TextEdit::unIndentSelection()
+{
+ QTextCursor cursor = textCursor();
+ cursor.beginEditBlock();
+ const QString indent(m_tabsettings.m_indentSize, QLatin1Char(' '));
+
+ const int anchor = cursor.anchor();
+ const int pos = cursor.position();
+ int indents = 0;
+ const bool reversed = (anchor > pos);
+ int startPos = (reversed ? pos : anchor);
+ int endPos = (reversed ? anchor : pos);
+ const QTextBlock startBlock = document()->findBlock(reversed ? pos : anchor);
+ QTextBlock lastBlock = document()->findBlock(reversed ? anchor : pos);
+ if ((lastBlock.position() == pos || lastBlock.position() == anchor) && startBlock != lastBlock)
+ lastBlock = lastBlock.previous();
+
+ QTextBlock curBlock = startBlock;
+ while(curBlock.previous() != lastBlock) {
+ if (curBlock.text().startsWith(indent)) {
+ cursor.setPosition(curBlock.position());
+ cursor.setPosition(curBlock.position() + m_tabsettings.m_indentSize, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+ ++indents;
+ }
+ curBlock = curBlock.next();
+ }
+
+ startPos = startBlock.position();
+ endPos -= m_tabsettings.m_indentSize * (indents);
+ if (endPos < startPos)
+ endPos = startPos;
+ cursor.setPosition(reversed ? endPos : startPos);
+ cursor.setPosition(reversed ? startPos : endPos, QTextCursor::KeepAnchor);
+
+ cursor.endEditBlock();
+ setTextCursor(cursor);
+}
+
+void TextEdit::indentSelection()
+{
+ QTextCursor cursor = textCursor();
+ cursor.beginEditBlock();
+
+ const QString indent(m_tabsettings.m_indentSize, QLatin1Char(' '));
+ if (cursor.hasSelection() && m_tabsettings.m_indentSelection) {
+ int anchor = cursor.anchor();
+ int pos = cursor.position();
+ int indents = 0;
+ const bool reversed = (anchor > pos);
+ const QTextBlock startBlock = document()->findBlock(reversed ? pos : anchor);
+ QTextBlock lastBlock = document()->findBlock(reversed ? anchor : pos);
+ if ((lastBlock.position() == pos || lastBlock.position() == anchor) && startBlock != lastBlock)
+ lastBlock = lastBlock.previous();
+
+ QTextBlock curBlock = startBlock;
+ while(curBlock.previous() != lastBlock) {
+ cursor.setPosition(curBlock.position());
+ cursor.insertText(indent);
+ curBlock = curBlock.next();
+ ++indents;
+ }
+
+ if (reversed) {
+ pos = startBlock.position();
+ anchor += m_tabsettings.m_indentSize * (indents);
+ } else {
+ anchor = startBlock.position();
+ pos += m_tabsettings.m_indentSize * (indents);
+ }
+ cursor.setPosition(anchor);
+ cursor.setPosition(pos, QTextCursor::KeepAnchor);
+ } else {
+ cursor.insertText(indent);
+ }
+ cursor.endEditBlock();
+ setTextCursor(cursor);
+}
+
+void TextEdit::handleHomeKey(bool anchor)
+{
+ QTextCursor cursor = textCursor();
+ QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
+
+ if (anchor)
+ mode = QTextCursor::KeepAnchor;
+
+ const int initpos = cursor.position();
+ int pos = cursor.block().position();
+
+ while (charAt(pos).category() == QChar::Separator_Space) {
+ ++pos;
+ }
+
+ // Go to the start of the block when we're already at the start of the text
+ if (pos == initpos)
+ pos = cursor.block().position();
+
+ cursor.setPosition(pos, mode);
+ setTextCursor(cursor);
+}
+
+void TextEdit::handleArrowKeys(int key)
+{
+ QTextCursor cursor = textCursor();
+ if (key == Qt::Key_Left)
+ cursor.setPosition(cursor.selectionStart());
+ else if (key == Qt::Key_Right)
+ cursor.setPosition(cursor.selectionEnd());
+ setTextCursor(cursor);
+}
+
+
+void TextEdit::format()
+{
+ QTextCursor cursor = textCursor();
+ if (!cursor.hasSelection())
+ return;
+
+ const int start = cursor.selectionStart();
+ int end = cursor.selectionEnd();
+
+ const QTextBlock endblock = document()->findBlock(end - 1);
+ indent(document(), cursor, QChar::Null);
+
+ cursor.setPosition(start);
+ end = endblock.position() + endblock.length();
+ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, end-start);
+ setTextCursor(cursor);
+}
+
+void TextEdit::setTabSettings(const TabSettings &ts)
+{
+ m_tabsettings = ts;
+}
+
+void TextEdit::indentBlock(QTextDocument *, QTextBlock, QChar)
+{
+}
+
+void TextEdit::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
+{
+ if (cursor.hasSelection()) {
+ QTextBlock block = doc->findBlock(cursor.selectionStart());
+ const QTextBlock end = doc->findBlock(cursor.selectionEnd()).next();
+ do {
+ indentBlock(doc, block, typedChar);
+ block = block.next();
+ } while (block.isValid() && block != end);
+ } else {
+ indentBlock(doc, cursor.block(), typedChar);
+ }
+}
+
+
+QList<int> TextEdit::bookmarks() const
+{
+ QList<int> list;
+ QTextBlock block = document()->begin();
+ int blockNumber = 0;
+ while (block.isValid()) {
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ if (userData->bookmark())
+ list += blockNumber;
+ block = block.next();
+ ++blockNumber;
+ }
+ return list;
+}
+
+QList<int> TextEdit::breakpoints() const
+{
+ QList<int> list;
+ QTextBlock block = document()->begin();
+ int blockNumber = 0;
+ while (block.isValid()) {
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ if (userData->breakpoint())
+ list += blockNumber;
+ block = block.next();
+ ++blockNumber;
+ }
+ return list;
+}
+
+void TextEdit::setBookmark_helper(int blockNumber, bool b, bool update)
+{
+ QTextDocument *doc = document();
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+ Q_ASSERT(documentLayout);
+ QTextBlock block = doc->findBlockByNumber(blockNumber);
+ if (block.isValid()) {
+ TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block);
+ bool wasSet = userData && userData->bookmark();
+ if (b != wasSet) {
+ if (!userData)
+ userData = TextEditDocumentLayout::userData(block);
+ userData->setBookmark(b);
+ int diff = b ? 1 : -1;
+ documentLayout->markCount += diff;
+ if (userData->breakpoint())
+ documentLayout->doubleMarkCount += diff;
+ }
+ }
+ if (update)
+ documentLayout->requestUpdate();
+}
+
+void TextEdit::setBreakpoint_helper(int blockNumber, bool b, bool update)
+{
+ QTextDocument *doc = document();
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+ Q_ASSERT(documentLayout);
+ QTextBlock block = doc->findBlockByNumber(blockNumber);
+
+ if (block.isValid()) {
+ TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block);
+ bool wasSet = userData && userData->breakpoint();
+ if (b != wasSet) {
+ if (!userData)
+ userData = TextEditDocumentLayout::userData(block);
+ setPendingBreakpoint_helper(blockNumber, false, false);
+ userData->setBreakpoint(b);
+ int diff = b ? 1 : -1;
+ documentLayout->markCount += diff;
+ if (userData->bookmark())
+ documentLayout->doubleMarkCount += diff;
+ emit breakpointToggled(blockNumber + baseLine, b);
+ }
+ }
+ if (update)
+ documentLayout->requestUpdate();
+}
+
+void TextEdit::setPendingBreakpoint_helper(int blockNumber, bool b, bool update)
+{
+ QTextDocument *doc = document();
+ TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+ Q_ASSERT(documentLayout);
+ QTextBlock block = doc->findBlockByNumber(blockNumber);
+
+ if (block.isValid()) {
+ TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block);
+ bool wasSet = userData && userData->pendingBreakpoint();
+ if (b != wasSet) {
+ if (!userData)
+ userData = TextEditDocumentLayout::userData(block);
+ setBreakpoint_helper(blockNumber, false, false);
+ userData->setPendingBreakpoint(b);
+ int diff = b ? 1 : -1;
+ documentLayout->markCount += diff;
+ if (userData->bookmark())
+ documentLayout->doubleMarkCount += diff;
+ }
+ }
+ if (update)
+ documentLayout->requestUpdate();
+}
+
+void TextEdit::setBookmarks(const QList<int> &blockNumbers)
+{
+ Q_ASSERT(qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()));
+ QTextBlock block = document()->begin();
+ while (block.isValid()) {
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ userData->setBookmark(false);
+ block = block.next();
+ }
+ foreach (int blockNumber, blockNumbers) {
+ setBookmark_helper(blockNumber, true, blockNumber == blockNumbers.last());
+ }
+}
+
+
+void TextEdit::setBreakpoints(const QList<int> &blockNumbers)
+{
+ Q_ASSERT(qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()));
+ QTextBlock block = document()->begin();
+ while (block.isValid()) {
+ if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+ userData->setBreakpoint(false);
+ block = block.next();
+ }
+ foreach (int blockNumber, blockNumbers) {
+ setBreakpoint_helper(blockNumber, true, blockNumber == blockNumbers.last());
+ }
+}
+
+
+void TextEdit::markBlocksAsChanged(QList<int> blockNumbers) {
+ QTextBlock block = document()->begin();
+ while (block.isValid()) {
+ if (block.revision() < 0)
+ block.setRevision(-block.revision() - 1);
+ block = block.next();
+ }
+ foreach(int blockNumber, blockNumbers) {
+ QTextBlock block = document()->findBlockByNumber(blockNumber);
+ if (block.isValid())
+ block.setRevision(-block.revision() - 1);
+ }
+}
+
+
+
+TextBlockUserData::MatchType TextBlockUserData::checkOpenParenthesis(QTextCursor *cursor, QChar c)
+{
+ if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+ return NoMatch;
+
+ Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+ Parenthesis openParen, closedParen;
+ QTextBlock closedParenParag = cursor->block();
+
+ const int cursorPos = cursor->position() - closedParenParag.position();
+ int i = 0;
+ int ignore = 0;
+ bool foundOpen = false;
+ for (;;) {
+ if (!foundOpen) {
+ if (i >= parenList.count())
+ return NoMatch;
+ openParen = parenList.at(i);
+ if (openParen.pos != cursorPos) {
+ ++i;
+ continue;
+ } else {
+ foundOpen = true;
+ ++i;
+ }
+ }
+
+ if (i >= parenList.count()) {
+ for (;;) {
+ closedParenParag = closedParenParag.next();
+ if (!closedParenParag.isValid())
+ return NoMatch;
+ if (TextEditDocumentLayout::hasParentheses(closedParenParag)) {
+ parenList = TextEditDocumentLayout::parentheses(closedParenParag);
+ break;
+ }
+ }
+ i = 0;
+ }
+
+ closedParen = parenList.at(i);
+ if (closedParen.type == Parenthesis::Opened) {
+ ignore++;
+ ++i;
+ continue;
+ } else {
+ if (ignore > 0) {
+ ignore--;
+ ++i;
+ continue;
+ }
+
+ cursor->clearSelection();
+ cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);
+
+ if (c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}')
+ || c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')')
+ || c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']')
+ )
+ return Mismatch;
+
+ return Match;
+ }
+ }
+}
+
+TextBlockUserData::MatchType TextBlockUserData::checkClosedParenthesis(QTextCursor *cursor, QChar c)
+{
+
+ if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+ return NoMatch;
+
+ Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+ Parenthesis openParen, closedParen;
+ QTextBlock openParenParag = cursor->block();
+
+ const int cursorPos = cursor->position() - openParenParag.position();
+ int i = parenList.count() - 1;
+ int ignore = 0;
+ bool foundClosed = false;
+ for (;;) {
+ if (!foundClosed) {
+ if (i < 0)
+ return NoMatch;
+ closedParen = parenList.at(i);
+ if (closedParen.pos != cursorPos - 1) {
+ --i;
+ continue;
+ } else {
+ foundClosed = true;
+ --i;
+ }
+ }
+
+ if (i < 0) {
+ for (;;) {
+ openParenParag = openParenParag.previous();
+ if (!openParenParag.isValid())
+ return NoMatch;
+
+ if (TextEditDocumentLayout::hasParentheses(openParenParag)) {
+ parenList = TextEditDocumentLayout::parentheses(openParenParag);
+ break;
+ }
+ }
+ i = parenList.count() - 1;
+ }
+
+ openParen = parenList.at(i);
+ if (openParen.type == Parenthesis::Closed) {
+ ignore++;
+ --i;
+ continue;
+ } else {
+ if (ignore > 0) {
+ ignore--;
+ --i;
+ continue;
+ }
+
+ cursor->clearSelection();
+ cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);
+
+ if ( c == '}' && openParen.chr != '{' ||
+ c == ')' && openParen.chr != '(' ||
+ c == ']' && openParen.chr != '[' )
+ return Mismatch;
+
+ return Match;
+ }
+ }
+}
+TextBlockUserData::MatchType TextBlockUserData::matchCursorBackward(QTextCursor *cursor)
+{
+ cursor->clearSelection();
+ const QTextBlock block = cursor->block();
+
+ if (!TextEditDocumentLayout::hasParentheses(block))
+ return NoMatch;
+
+ const int relPos = cursor->position() - block.position();
+
+ Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
+ const Parentheses::const_iterator cend = parentheses.constEnd();
+ for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
+ const Parenthesis &paren = *it;
+ if (paren.pos == relPos - 1
+ && paren.type == Parenthesis::Closed) {
+ return checkClosedParenthesis(cursor, paren.chr);
+ }
+ }
+ return NoMatch;
+}
+
+TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *cursor)
+{
+ cursor->clearSelection();
+ const QTextBlock block = cursor->block();
+
+ if (!TextEditDocumentLayout::hasParentheses(block))
+ return NoMatch;
+
+ const int relPos = cursor->position() - block.position();
+
+ Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
+ const Parentheses::const_iterator cend = parentheses.constEnd();
+ for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
+ const Parenthesis &paren = *it;
+ if (paren.pos == relPos
+ && paren.type == Parenthesis::Opened) {
+ return checkOpenParenthesis(cursor, paren.chr);
+ }
+ }
+ return NoMatch;
+}
+
+void TextEdit::matchParentheses()
+{
+ if (isReadOnly())
+ return;
+
+ QTextCursor backwardMatch = textCursor();
+ QTextCursor forwardMatch = textCursor();
+ const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
+ const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);
+
+ if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch)
+ return;
+
+ QList<QTextEdit::ExtraSelection> extraSelections = this->extraSelections();
+
+ if (backwardMatch.hasSelection()) {
+ QTextEdit::ExtraSelection sel;
+ if (backwardMatchType == TextBlockUserData::Mismatch) {
+ sel.cursor = backwardMatch;
+ sel.format = m_mismatchFormat;
+ } else {
+
+ if (m_formatRange) {
+ sel.cursor = backwardMatch;
+ sel.format = m_rangeFormat;
+ extraSelections.append(sel);
+ }
+
+ sel.cursor = backwardMatch;
+ sel.format = m_matchFormat;
+
+ sel.cursor.setPosition(backwardMatch.selectionStart());
+ sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+ extraSelections.append(sel);
+
+ sel.cursor.setPosition(backwardMatch.selectionEnd());
+ sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
+ }
+ extraSelections.append(sel);
+ }
+
+ if (forwardMatch.hasSelection()) {
+ QTextEdit::ExtraSelection sel;
+ if (forwardMatchType == TextBlockUserData::Mismatch) {
+ sel.cursor = forwardMatch;
+ sel.format = m_mismatchFormat;
+ } else {
+
+ if (m_formatRange) {
+ sel.cursor = forwardMatch;
+ sel.format = m_rangeFormat;
+ extraSelections.append(sel);
+ }
+
+ sel.cursor = forwardMatch;
+ sel.format = m_matchFormat;
+
+ sel.cursor.setPosition(forwardMatch.selectionStart());
+ sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+ extraSelections.append(sel);
+
+ sel.cursor.setPosition(forwardMatch.selectionEnd());
+ sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
+ }
+ extraSelections.append(sel);
+ }
+ setExtraSelections(extraSelections);
+}
+
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef TEXTEDIT_H
+#define TEXTEDIT_H
+
+#include <QtGui/QPlainTextEdit>
+#include "tabsettings.h"
+#include <QtGui/QTextCharFormat>
+#include <QtGui/QTextBlockUserData>
+#include <QtGui/QIcon>
+#include <QtCore/QBasicTimer>
+#include <QtCore/QDebug>
+
+
+struct Parenthesis;
+typedef QVector<Parenthesis> Parentheses;
+struct Parenthesis
+{
+ enum Type { Opened, Closed };
+
+ inline Parenthesis() : type(Opened), pos(-1) {}
+ inline Parenthesis(Type t, QChar c, int position)
+ : type(t), chr(c), pos(position) {}
+ Type type;
+ QChar chr;
+ int pos;
+ static int collapseAtPos(const Parentheses &parentheses);
+};
+
+class TextBlockUserData : public QTextBlockUserData {
+public:
+
+ enum Collapse { CollapseThis, CollapseAfter, NoCollapse };
+
+ inline TextBlockUserData()
+ :m_breakpoint(false), m_pendingBreakpoint(false), m_bookmark(false),
+ m_collapseIncludesClosure(false),
+ m_collapse(NoCollapse) {}
+ inline bool breakpoint() const { return m_breakpoint; }
+ inline bool pendingBreakpoint() const { return m_pendingBreakpoint; }
+ inline bool bookmark() const { return m_bookmark; }
+ inline Collapse collapse() const { return m_collapse; }
+ inline bool collapseIncludesClosure() const { return m_collapseIncludesClosure; }
+ inline void setBookmark(bool b) { m_bookmark = b; }
+ inline void setBreakpoint(bool b){ m_breakpoint = b; }
+ inline void setPendingBreakpoint(bool b){ m_pendingBreakpoint = b; }
+ inline void setCollapse(Collapse c) { m_collapse = c; }
+ inline void setCollapseIncludesClosure(bool b) { m_collapseIncludesClosure = b; }
+
+ inline void setParentheses(const Parentheses &parentheses) { m_parentheses = parentheses; }
+ inline void clearParentheses() { m_parentheses.clear(); }
+ inline const Parentheses &parentheses() const { return m_parentheses; }
+
+ inline bool hasParentheses() const { return !m_parentheses.isEmpty(); }
+
+ inline static bool canCollapse(const QTextBlock& block) {
+ return (block.userData()
+ && static_cast<TextBlockUserData*>(block.userData())->collapse()
+ == CollapseAfter)
+ || (block.next().userData()
+ && static_cast<TextBlockUserData*>(block.next().userData())->collapse()
+ == TextBlockUserData::CollapseThis);
+ }
+
+ static void setCollapse(const QTextBlock& block, bool visible);
+
+ bool hasClosingCollapse() const;
+ int collapseAtPos() const;
+
+ enum MatchType { NoMatch, Match, Mismatch };
+ static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c);
+ static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c);
+ static MatchType matchCursorBackward(QTextCursor *cursor);
+ static MatchType matchCursorForward(QTextCursor *cursor);
+
+
+
+private:
+ bool m_breakpoint;
+ bool m_pendingBreakpoint;
+ bool m_bookmark;
+ bool m_collapseIncludesClosure;
+ Collapse m_collapse;
+ Parentheses m_parentheses;
+};
+
+class TextEditDocumentLayout : public QPlainTextDocumentLayout
+{
+ Q_OBJECT
+
+public:
+
+ TextEditDocumentLayout(QTextDocument *doc):QPlainTextDocumentLayout(doc) {
+ lastSaveRevision = markCount = doubleMarkCount = 0;
+ m_executionBlockNumber = -1;
+ }
+ ~TextEditDocumentLayout(){}
+
+ QRectF blockBoundingRect(const QTextBlock &block) const;
+
+ static void setParentheses(const QTextBlock &block, const Parentheses &parentheses);
+ static void clearParentheses(const QTextBlock &block) { setParentheses(block, Parentheses());}
+ static Parentheses parentheses(const QTextBlock &block);
+ static bool hasParentheses(const QTextBlock &block);
+
+ static TextBlockUserData *testUserData(const QTextBlock &block) {
+ return static_cast<TextBlockUserData*>(block.userData());
+ }
+ static TextBlockUserData *userData(const QTextBlock &block) {
+ TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData());
+ if (!data && block.isValid())
+ const_cast<QTextBlock &>(block).setUserData((data = new TextBlockUserData));
+ return data;
+ }
+
+ inline void setExecutionBlockNumber(int blockNumber) {
+ m_executionBlockNumber = blockNumber;
+ requestUpdate();
+ }
+
+ inline int executionBlockNumber() const { return m_executionBlockNumber; }
+
+ int lastSaveRevision;
+ int markCount;
+ int doubleMarkCount;
+
+ int m_executionBlockNumber;
+};
+
+class TextEdit : public QPlainTextEdit
+{
+ Q_OBJECT
+public:
+ explicit TextEdit(QWidget *parent = 0);
+ ~TextEdit();
+
+ QWidget *extraArea() const;
+ virtual int extraAreaWidth(int *markWidthPtr = 0) const;
+ virtual void extraAreaPaintEvent(QPaintEvent *);
+ virtual void extraAreaMouseEvent(QMouseEvent *);
+
+
+ inline const TabSettings &tabSettings() const { return m_tabsettings; }
+
+ QList<int> bookmarks() const;
+ void setBookmarks(const QList<int> &blockNumbers);
+ QList<int> breakpoints() const;
+ void setBreakpoints(const QList<int> &blockNumbers);
+ QList<int> pendingBreakpoints() const;
+ void setPendingBreakpoints(const QList<int> &blockNumbers);
+
+
+ void markBlocksAsChanged(QList<int> blockNumbers);
+
+ int executionBlockNumber() const;
+ void setExecutionBlockNumber(int blockNumber);
+
+ int baseLineNumber() const { return baseLine; }
+ void setBaseLineNumber(int lineNumber) { baseLine = lineNumber; }
+ int cursorLineNumber() const { return textCursor().blockNumber() + baseLine; }
+
+public Q_SLOTS:
+ void setTabSettings(const TabSettings &);
+ virtual void format();
+
+Q_SIGNALS:
+ void breakpointToggled(int lineNumber, bool set);
+
+protected:
+
+ bool viewportEvent(QEvent *event);
+ QChar charAt(int pos) const;
+
+ void resizeEvent(QResizeEvent *);
+ void paintEvent(QPaintEvent *);
+ void keyPressEvent(QKeyEvent *);
+ void timerEvent(QTimerEvent *);
+ void mouseMoveEvent(QMouseEvent *);
+ void mousePressEvent(QMouseEvent *);
+
+ // Indent a text block based on previous line. Default does nothing
+ virtual void indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar);
+ // Indent at cursor. Calls indentBlock for selection or current line.
+ virtual void indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar);
+
+
+protected Q_SLOTS:
+ virtual void slotUpdateExtraAreaWidth();
+ virtual void slotModificationChanged(bool);
+ virtual void slotUpdateRequest(const QRect &r, int dy);
+ virtual void slotCursorPositionChanged();
+
+public slots:
+ void slotToggleBookmark();
+ void slotToggleBreakpoint();
+ void slotTogglePendingBreakpoint();
+ void slotToggleBlockVisible();
+ void slotSetExecutionBlock();
+private:
+
+ void setBookmark_helper(int blockNumber, bool b = true, bool update = true);
+ void setBreakpoint_helper(int blockNumber, bool b = true, bool update = true);
+ void setPendingBreakpoint_helper(int blockNumber, bool b = true, bool update = true);
+
+ void indentSelection();
+ void handleHomeKey(bool anchor);
+ void handleArrowKeys(int key);
+ void unIndentSelection();
+
+ void toggleBlockVisible(const QTextBlock &block);
+ QRect collapseBox(const QTextBlock &block);
+
+ QTextBlock collapsedBlockAt(const QPoint &pos, QString *text = 0, QRect *box = 0) const;
+
+ // parentheses matcher
+private slots:
+ void matchParentheses();
+private:
+ bool m_formatRange;
+ QTextCharFormat m_matchFormat;
+ QTextCharFormat m_mismatchFormat;
+ QTextCharFormat m_rangeFormat;
+ QTimer *m_parenthesesMatchingTimer;
+ // end parentheses matcher
+
+
+ QWidget *m_extraArea;
+ TabSettings m_tabsettings;
+ QIcon executionIcon;
+ QIcon bookmarkIcon;
+ QIcon breakpointIcon;
+ QIcon breakpointPendingIcon;
+
+ int extraAreaSelectionAnchorBlockNumber;
+ int extraAreaToggleBreakpointBlockNumber;
+
+ QBasicTimer autoScrollTimer;
+
+ int baseLine;
+};
+
+
+#endif // CPPEDIT_H