/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtSCriptTools module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qscriptedit_p.h" #include "qscriptsyntaxhighlighter_p.h" #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QScriptEditExtraArea : public QWidget { public: QScriptEditExtraArea(QScriptEdit *edit) : QWidget(edit) { setMouseTracking(true); } QSize sizeHint() const { return QSize(editor()->extraAreaWidth(), 0); } protected: void paintEvent(QPaintEvent *event) { editor()->extraAreaPaintEvent(event); } void mousePressEvent(QMouseEvent *event) { editor()->extraAreaMouseEvent(event); } void mouseMoveEvent(QMouseEvent *event) { editor()->extraAreaMouseEvent(event); } void mouseReleaseEvent(QMouseEvent *event) { editor()->extraAreaMouseEvent(event); } bool event(QEvent *event) { if (editor()->extraAreaEvent(event)) return true; return QWidget::event(event); } private: QScriptEdit *editor() const { return qobject_cast(parent()); } }; QScriptEdit::QScriptEdit(QWidget *parent) : QPlainTextEdit(parent) { m_baseLineNumber = 1; m_executionLineNumber = -1; m_extraArea = new QScriptEditExtraArea(this); QObject::connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateExtraAreaWidth())); QObject::connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateExtraArea(QRect,int))); QObject::connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); updateExtraAreaWidth(); #ifndef QT_NO_SYNTAXHIGHLIGHTER (void) new QScriptSyntaxHighlighter(document()); #endif } QScriptEdit::~QScriptEdit() { } int QScriptEdit::baseLineNumber() const { return m_baseLineNumber; } void QScriptEdit::setBaseLineNumber(int base) { m_baseLineNumber = base; m_extraArea->update(); } int QScriptEdit::executionLineNumber() const { return m_executionLineNumber; } void QScriptEdit::setExecutionLineNumber(int lineNumber, bool error) { m_executionLineNumber = lineNumber; m_executionLineNumberHasError = error; m_extraArea->update(); updateExtraSelections(); gotoLine(lineNumber); } void QScriptEdit::setExecutableLineNumbers(const QSet &lineNumbers) { m_executableLineNumbers = lineNumbers; } bool QScriptEdit::isExecutableLine(int lineNumber) const { #if 0 // ### enable me once we have information about the script again return m_executableLineNumbers.contains(lineNumber); #else Q_UNUSED(lineNumber); return true; #endif } int QScriptEdit::currentLineNumber() const { return textCursor().blockNumber() + m_baseLineNumber; } void QScriptEdit::gotoLine(int lineNumber) { #ifndef QT_NO_SYNTAXHIGHLIGHTER int blockNumber = lineNumber - m_baseLineNumber; const QTextBlock &block = document()->findBlockByNumber(blockNumber); if (block.isValid()) { setTextCursor(QTextCursor(block)); centerCursor(); } #else Q_UNUSED(lineNumber); #endif } void QScriptEdit::setBreakpoint(int lineNumber) { m_breakpoints[lineNumber] = BreakpointData(); m_extraArea->update(); } void QScriptEdit::setBreakpointEnabled(int lineNumber, bool enable) { m_breakpoints[lineNumber].enabled = enable; m_extraArea->update(); } void QScriptEdit::deleteBreakpoint(int lineNumber) { m_breakpoints.remove(lineNumber); m_extraArea->update(); } void QScriptEdit::paintEvent(QPaintEvent *e) { QPlainTextEdit::paintEvent(e); } void QScriptEdit::resizeEvent(QResizeEvent *e) { QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); int x = isLeftToRight() ? cr.left() : cr.left() + cr.width() - extraAreaWidth(); m_extraArea->setGeometry(QRect(x, cr.top(), extraAreaWidth(), cr.height())); } void QScriptEdit::updateExtraAreaWidth() { if (isLeftToRight()) setViewportMargins(extraAreaWidth(), 0, 0, 0); else setViewportMargins(0, 0, extraAreaWidth(), 0); } void QScriptEdit::updateExtraArea(const QRect &rect, int dy) { if (dy) m_extraArea->scroll(0, dy); else m_extraArea->update(0, rect.y(), m_extraArea->width(), rect.height()); if (rect.contains(viewport()->rect())) updateExtraAreaWidth(); } void QScriptEdit::highlightCurrentLine() { updateExtraSelections(); } void QScriptEdit::updateExtraSelections() { QList extraSelections; { QTextEdit::ExtraSelection selection; QColor lineColor = QColor(Qt::yellow).lighter(160); selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); extraSelections.append(selection); } if (m_executionLineNumber != -1) { QTextEdit::ExtraSelection selection; QColor lineColor; if (m_executionLineNumberHasError) lineColor = QColor(Qt::red); else lineColor = QColor(Qt::green).lighter(160); selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); #ifndef QT_NO_SYNTAXHIGHLIGHTER int blockNumber = m_executionLineNumber - m_baseLineNumber; selection.cursor = QTextCursor(document()->findBlockByNumber(blockNumber)); #endif selection.cursor.clearSelection(); extraSelections.append(selection); } setExtraSelections(extraSelections); } int QScriptEdit::extraAreaWidth() const { int space = 0; const QFontMetrics fm(fontMetrics()); int digits = 1; int max = qMax(1, blockCount() + m_baseLineNumber); while (max >= 10) { max /= 10; ++digits; } space += fm.width(QLatin1Char('9')) * digits; int markWidth = fm.lineSpacing(); space += markWidth; space += 4; return space; } void QScriptEdit::extraAreaPaintEvent(QPaintEvent *e) { QRect rect = e->rect(); QPalette pal = palette(); pal.setCurrentColorGroup(QPalette::Active); QPainter painter(m_extraArea); painter.fillRect(rect, Qt::lightGray); const QFontMetrics fm(fontMetrics()); int markWidth = fm.lineSpacing(); int extraAreaWidth = m_extraArea->width(); 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(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(rect.intersected(QRect(rect.x(), rect.y(), markWidth, rect.height())), gradient2); painter.setPen(QPen(pal.color(QPalette::Background), 2)); if (isLeftToRight()) painter.drawLine(rect.x() + extraAreaWidth-1, rect.top(), rect.x() + extraAreaWidth-1, rect.bottom()); else painter.drawLine(rect.x(), rect.top(), rect.x(), rect.bottom()); painter.setRenderHint(QPainter::Antialiasing); #ifndef QT_NO_SYNTAXHIGHLIGHTER QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); qreal top = blockBoundingGeometry(block).translated(contentOffset()).top(); qreal bottom = top + blockBoundingRect(block).height(); QString imagesPath = QString::fromLatin1(":/qt/scripttools/debugging/images"); QString imageExt; // SVGs don't work on all platforms, even when QT_NO_SVG is not defined, so disable SVG usage for now. // #ifndef QT_NO_SVG #if 0 imageExt = QString::fromLatin1("svg"); #else imageExt = QString::fromLatin1("png"); #endif while (block.isValid() && top <= rect.bottom()) { if (block.isVisible() && bottom >= rect.top()) { int lineNumber = blockNumber + m_baseLineNumber; if (m_breakpoints.contains(lineNumber)) { int radius = fm.lineSpacing() - 1; QRect r(rect.x(), (int)top, radius, radius); QIcon icon(m_breakpoints[lineNumber].enabled ? QString::fromLatin1("%0/breakpoint.%1").arg(imagesPath).arg(imageExt) : QString::fromLatin1("%0/d_breakpoint.%1").arg(imagesPath).arg(imageExt)); icon.paint(&painter, r, Qt::AlignCenter); } if (m_executionLineNumber == lineNumber) { int radius = fm.lineSpacing() - 1; QRect r(rect.x(), (int)top, radius, radius); QIcon icon(QString::fromLatin1("%0/location.%1").arg(imagesPath).arg(imageExt)); icon.paint(&painter, r, Qt::AlignCenter); } if (!isExecutableLine(lineNumber)) painter.setPen(pal.color(QPalette::Mid)); else painter.setPen(QColor(Qt::darkCyan)); QString number = QString::number(lineNumber); painter.drawText(rect.x() + markWidth, (int)top, rect.x() + extraAreaWidth - markWidth - 4, fm.height(), Qt::AlignRight, number); } block = block.next(); top = bottom; bottom = top + blockBoundingRect(block).height(); ++blockNumber; } #endif } void QScriptEdit::extraAreaMouseEvent(QMouseEvent *e) { QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y())); #ifndef QT_NO_SYNTAXHIGHLIGHTER cursor.setPosition(cursor.block().position()); #endif QFontMetrics fm(font()); int markWidth = fm.lineSpacing(); if (e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking bool hand = (e->pos().x() <= markWidth); int lineNumber = cursor.blockNumber() + m_baseLineNumber; hand = hand && isExecutableLine(lineNumber); #ifndef QT_NO_CURSOR if (hand != (m_extraArea->cursor().shape() == Qt::PointingHandCursor)) m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor); #endif } if (e->type() == QEvent::MouseButtonPress) { if (e->button() == Qt::LeftButton) { int lineNumber = cursor.blockNumber() + m_baseLineNumber; bool executable = isExecutableLine(lineNumber); if ((e->pos().x() <= markWidth) && executable) m_extraAreaToggleBlockNumber = cursor.blockNumber(); else m_extraAreaToggleBlockNumber = -1; } } else if (e->type() == QEvent::MouseButtonRelease) { if (e->button() == Qt::LeftButton) { if ((m_extraAreaToggleBlockNumber != -1) && (e->pos().x() <= markWidth)) { int lineNumber = m_extraAreaToggleBlockNumber + m_baseLineNumber; bool on = !m_breakpoints.contains(lineNumber); emit breakpointToggleRequest(lineNumber, on); } } else if (e->button() == Qt::RightButton) { int lineNumber = cursor.blockNumber() + m_baseLineNumber; if (!isExecutableLine(lineNumber)) return; bool has = m_breakpoints.contains(lineNumber); QMenu *popup = new QMenu(); QAction *toggleAct = new QAction(tr("Toggle Breakpoint"), popup); popup->addAction(toggleAct); QAction *disableAct = new QAction(tr("Disable Breakpoint"), popup); QAction *enableAct = new QAction(tr("Enable Breakpoint"), popup); QWidget *conditionWidget = new QWidget(); { QHBoxLayout *hbox = new QHBoxLayout(conditionWidget); hbox->addWidget(new QLabel(tr("Breakpoint Condition:"))); hbox->addWidget(new QLineEdit()); } // QWidgetAction *conditionAct = new QWidgetAction(popup); // conditionAct->setDefaultWidget(conditionWidget); if (has) { popup->addSeparator(); popup->addAction(m_breakpoints[lineNumber].enabled ? disableAct : enableAct); // popup->addAction(conditionAct); } QAction *ret = popup->exec(e->globalPos()); if (ret) { if (ret == toggleAct) { emit breakpointToggleRequest(lineNumber, !has); } else if (ret == disableAct) { emit breakpointEnableRequest(lineNumber, false); } else if (ret == enableAct) { emit breakpointEnableRequest(lineNumber, true); }// else if (ret == conditionAct) { //} } popup->deleteLater(); } } } bool QScriptEdit::extraAreaEvent(QEvent *e) { if (e->type() == QEvent::ToolTip) { // ### show the breakpoint's condition, if any return true; } return false; } QT_END_NAMESPACE