/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtSCriptTools module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.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 the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qscriptedit_p.h" | |
#include "qscriptsyntaxhighlighter_p.h" | |
#include <QtGui/qpainter.h> | |
#include <QtGui/qicon.h> | |
#include <QtGui/qboxlayout.h> | |
#include <QtGui/qlabel.h> | |
#include <QtGui/qlineedit.h> | |
#include <QtGui/qmenu.h> | |
#include <QtGui/qaction.h> | |
#include <QtGui/qwidgetaction.h> | |
#include <QtCore/qdebug.h> | |
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<QScriptEdit*>(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<int> &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) | |
{ | |
int blockNumber = lineNumber - m_baseLineNumber; | |
const QTextBlock &block = document()->findBlockByNumber(blockNumber); | |
if (block.isValid()) { | |
setTextCursor(QTextCursor(block)); | |
centerCursor(); | |
} | |
} | |
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<QTextEdit::ExtraSelection> 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); | |
int blockNumber = m_executionLineNumber - m_baseLineNumber; | |
selection.cursor = QTextCursor(document()->findBlockByNumber(blockNumber)); | |
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); | |
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; | |
} | |
} | |
void QScriptEdit::extraAreaMouseEvent(QMouseEvent *e) | |
{ | |
QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y())); | |
cursor.setPosition(cursor.block().position()); | |
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 |