From 66c275e337ce4305be0160bf7e4fffd1c30e2918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 17 Feb 2018 22:24:41 +0100 Subject: [PATCH] GUI: Added code editor for quick inspection of bugs --- gui/codeeditor.cpp | 205 ++++++++++++++++++++++++++++++++++++++++++++ gui/codeeditor.h | 89 +++++++++++++++++++ gui/gui.pro | 2 + gui/resultsview.cpp | 15 ++++ gui/resultsview.ui | 24 ++++++ 5 files changed, 335 insertions(+) create mode 100644 gui/codeeditor.cpp create mode 100644 gui/codeeditor.h diff --git a/gui/codeeditor.cpp b/gui/codeeditor.cpp new file mode 100644 index 000000000..51eaeb7cb --- /dev/null +++ b/gui/codeeditor.cpp @@ -0,0 +1,205 @@ +#include + +#include "codeeditor.h" + + +Highlighter::Highlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) +{ + HighlightingRule rule; + + keywordFormat.setForeground(Qt::darkBlue); + keywordFormat.setFontWeight(QFont::Bold); + QStringList keywordPatterns; + keywordPatterns << "\\bchar\\b" << "\\bclass\\b" << "\\bconst\\b" + << "\\bdouble\\b" << "\\benum\\b" << "\\bexplicit\\b" + << "\\bfriend\\b" << "\\binline\\b" << "\\bint\\b" + << "\\blong\\b" << "\\bnamespace\\b" << "\\boperator\\b" + << "\\bprivate\\b" << "\\bprotected\\b" << "\\bpublic\\b" + << "\\bshort\\b" << "\\bsignals\\b" << "\\bsigned\\b" + << "\\bslots\\b" << "\\bstatic\\b" << "\\bstruct\\b" + << "\\btemplate\\b" << "\\btypedef\\b" << "\\btypename\\b" + << "\\bunion\\b" << "\\bunsigned\\b" << "\\bvirtual\\b" + << "\\bvoid\\b" << "\\bvolatile\\b" << "\\bbool\\b"; + foreach (const QString &pattern, keywordPatterns) { + rule.pattern = QRegularExpression(pattern); + rule.format = keywordFormat; + highlightingRules.append(rule); + } + + classFormat.setFontWeight(QFont::Bold); + classFormat.setForeground(Qt::darkMagenta); + rule.pattern = QRegularExpression("\\bQ[A-Za-z]+\\b"); + rule.format = classFormat; + highlightingRules.append(rule); + + quotationFormat.setForeground(Qt::darkGreen); + rule.pattern = QRegularExpression("\".*\""); + rule.format = quotationFormat; + highlightingRules.append(rule); + + functionFormat.setFontItalic(true); + functionFormat.setForeground(Qt::blue); + rule.pattern = QRegularExpression("\\b[A-Za-z0-9_]+(?=\\()"); + rule.format = functionFormat; + highlightingRules.append(rule); + + singleLineCommentFormat.setForeground(Qt::gray); + rule.pattern = QRegularExpression("//[^\n]*"); + rule.format = singleLineCommentFormat; + highlightingRules.append(rule); + + multiLineCommentFormat.setForeground(Qt::gray); + + commentStartExpression = QRegularExpression("/\\*"); + commentEndExpression = QRegularExpression("\\*/"); +} + +void Highlighter::highlightBlock(const QString &text) +{ + foreach (const HighlightingRule &rule, highlightingRules) { + QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } + } + + setCurrentBlockState(0); + + int startIndex = 0; + if (previousBlockState() != 1) + startIndex = text.indexOf(commentStartExpression); + + while (startIndex >= 0) { + QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); + int endIndex = match.capturedStart(); + int commentLength = 0; + if (endIndex == -1) { + setCurrentBlockState(1); + commentLength = text.length() - startIndex; + } else { + commentLength = endIndex - startIndex + + match.capturedLength(); + } + setFormat(startIndex, commentLength, multiLineCommentFormat); + startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); + } +} + + +CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent) +{ + lineNumberArea = new LineNumberArea(this); + highlighter = new Highlighter(this->document()); + mErrorPosition = -1; + + 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); +} + +static int getPos(const QString &fileData, int lineNumber) +{ + for (int pos = 0, line = 1; pos < fileData.size(); ++pos) { + if (fileData[pos] != '\n') + continue; + ++line; + if (line == lineNumber) + return pos + 1; + } + return fileData.size(); +} + +void CodeEditor::setErrorLine(int errorLine) +{ + mErrorPosition = getPos(toPlainText(), errorLine); + QTextCursor tc = textCursor(); + tc.setPosition(mErrorPosition); + setTextCursor(tc); + centerCursor(); +} + +int CodeEditor::lineNumberAreaWidth() +{ + int digits = 1; + int max = qMax(1, blockCount()); + while (max >= 10) { + max /= 10; + ++digits; + } + + int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; + return space; +} + +void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */) +{ + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + +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::resizeEvent(QResizeEvent *event) +{ + QPlainTextEdit::resizeEvent(event); + QRect cr = contentsRect(); + lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + +void CodeEditor::highlightCurrentLine() +{ + QTextCursor tc = textCursor(); + tc.setPosition(mErrorPosition); + setTextCursor(tc); + + QList extraSelections; + + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(255,220,220); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + + setExtraSelections(extraSelections); +} + +void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) +{ + QPainter painter(lineNumberArea); + painter.fillRect(event->rect(), QColor(240,240,240)); + + 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(Qt::black); + painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), + Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} diff --git a/gui/codeeditor.h b/gui/codeeditor.h new file mode 100644 index 000000000..41d555016 --- /dev/null +++ b/gui/codeeditor.h @@ -0,0 +1,89 @@ +#ifndef CODEEDITOR_H +#define CODEEDITOR_H + +#include +#include +#include +#include + +class QPaintEvent; +class QResizeEvent; +class QSize; +class QWidget; + +class LineNumberArea; + + +class Highlighter : public QSyntaxHighlighter { + Q_OBJECT + +public: + explicit Highlighter(QTextDocument *parent); + +protected: + void highlightBlock(const QString &text) override; + +private: + struct HighlightingRule { + QRegularExpression pattern; + QTextCharFormat format; + }; + QVector highlightingRules; + + QRegularExpression commentStartExpression; + QRegularExpression commentEndExpression; + + QTextCharFormat keywordFormat; + QTextCharFormat classFormat; + QTextCharFormat singleLineCommentFormat; + QTextCharFormat multiLineCommentFormat; + QTextCharFormat quotationFormat; + QTextCharFormat functionFormat; +}; + +class CodeEditor : public QPlainTextEdit { + Q_OBJECT + +public: + explicit CodeEditor(QWidget *parent); + + void lineNumberAreaPaintEvent(QPaintEvent *event); + int lineNumberAreaWidth(); + + void setErrorLine(int errorLine); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private slots: + void updateLineNumberAreaWidth(int newBlockCount); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect &, int); + +private: + QWidget *lineNumberArea; + Highlighter *highlighter; + int mErrorPosition; +}; + + +class LineNumberArea : public QWidget { +public: + explicit LineNumberArea(CodeEditor *editor) : QWidget(editor) { + codeEditor = editor; + } + + QSize sizeHint() const override { + return QSize(codeEditor->lineNumberAreaWidth(), 0); + } + +protected: + void paintEvent(QPaintEvent *event) override { + codeEditor->lineNumberAreaPaintEvent(event); + } + +private: + CodeEditor *codeEditor; +}; + +#endif // CODEEDITOR_H diff --git a/gui/gui.pro b/gui/gui.pro index 8c921f521..0c72ef1f6 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -89,6 +89,7 @@ HEADERS += aboutdialog.h \ applicationlist.h \ checkstatistics.h \ checkthread.h \ + codeeditor.h \ common.h \ csvreport.h \ erroritem.h \ @@ -123,6 +124,7 @@ SOURCES += aboutdialog.cpp \ applicationlist.cpp \ checkstatistics.cpp \ checkthread.cpp \ + codeeditor.cpp \ common.cpp \ csvreport.cpp \ erroritem.cpp \ diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index b3abd79a0..00ad6177f 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -365,6 +365,8 @@ void ResultsView::updateDetails(const QModelIndex &index) QStandardItemModel *model = qobject_cast(mUI.mTree->model()); QStandardItem *item = model->itemFromIndex(index); + mUI.mCode->setPlainText(QString()); + if (!item) { mUI.mDetails->setText(QString()); return; @@ -395,6 +397,19 @@ void ResultsView::updateDetails(const QModelIndex &index) if (mUI.mTree->showIdColumn()) formattedMsg.prepend(tr("Id") + ": " + data["id"].toString() + "\n"); mUI.mDetails->setText(formattedMsg); + + const int lineNumber = data["line"].toInt(); + + QString filepath = data["file"].toString(); + if (!QFileInfo(filepath).exists() && QFileInfo(mUI.mTree->getCheckDirectory() + '/' + filepath).exists()) + filepath = mUI.mTree->getCheckDirectory() + '/' + filepath; + + QFile file(filepath); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + mUI.mCode->setPlainText(in.readAll()); + mUI.mCode->setErrorLine(lineNumber); + } } void ResultsView::log(const QString &str) diff --git a/gui/resultsview.ui b/gui/resultsview.ui index 3c5a9e15c..fd3a455b0 100644 --- a/gui/resultsview.ui +++ b/gui/resultsview.ui @@ -118,6 +118,12 @@ + + + 0 + 1 + + false @@ -126,6 +132,19 @@ + + + + + 0 + 4 + + + + false + + + @@ -139,6 +158,11 @@ QTreeView
resultstree.h
+ + CodeEditor + QPlainTextEdit +
codeeditor.h
+
mTree