1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtSCriptTools module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qscriptedit_p.h"
41#include "qscriptsyntaxhighlighter_p.h"
42
43#include <QtGui/qpainter.h>
44#include <QtGui/qicon.h>
45#include <QtWidgets/qboxlayout.h>
46#include <QtWidgets/qlabel.h>
47#include <QtWidgets/qlineedit.h>
48#include <QtWidgets/qmenu.h>
49#include <QtWidgets/qaction.h>
50#include <QtWidgets/qwidgetaction.h>
51#include <QtCore/qdebug.h>
52
53QT_BEGIN_NAMESPACE
54
55class QScriptEditExtraArea : public QWidget
56{
57public:
58 QScriptEditExtraArea(QScriptEdit *edit)
59 : QWidget(edit)
60 {
61 setMouseTracking(true);
62 }
63
64 QSize sizeHint() const {
65 return QSize(editor()->extraAreaWidth(), 0);
66 }
67
68protected:
69 void paintEvent(QPaintEvent *event)
70 {
71 editor()->extraAreaPaintEvent(e: event);
72 }
73 void mousePressEvent(QMouseEvent *event)
74 {
75 editor()->extraAreaMouseEvent(e: event);
76 }
77 void mouseMoveEvent(QMouseEvent *event)
78 {
79 editor()->extraAreaMouseEvent(e: event);
80 }
81 void mouseReleaseEvent(QMouseEvent *event)
82 {
83 editor()->extraAreaMouseEvent(e: event);
84 }
85 bool event(QEvent *event)
86 {
87 if (editor()->extraAreaEvent(e: event))
88 return true;
89 return QWidget::event(event);
90 }
91
92private:
93 QScriptEdit *editor() const
94 {
95 return qobject_cast<QScriptEdit*>(object: parent());
96 }
97};
98
99
100
101QScriptEdit::QScriptEdit(QWidget *parent)
102 : QPlainTextEdit(parent)
103{
104 m_baseLineNumber = 1;
105 m_executionLineNumber = -1;
106
107 m_extraArea = new QScriptEditExtraArea(this);
108
109 QObject::connect(sender: this, SIGNAL(blockCountChanged(int)),
110 receiver: this, SLOT(updateExtraAreaWidth()));
111 QObject::connect(sender: this, SIGNAL(updateRequest(QRect,int)),
112 receiver: this, SLOT(updateExtraArea(QRect,int)));
113 QObject::connect(sender: this, SIGNAL(cursorPositionChanged()),
114 receiver: this, SLOT(highlightCurrentLine()));
115
116 updateExtraAreaWidth();
117
118#ifndef QT_NO_SYNTAXHIGHLIGHTER
119 (void) new QScriptSyntaxHighlighter(document());
120#endif
121}
122
123QScriptEdit::~QScriptEdit()
124{
125}
126
127int QScriptEdit::baseLineNumber() const
128{
129 return m_baseLineNumber;
130}
131
132void QScriptEdit::setBaseLineNumber(int base)
133{
134 m_baseLineNumber = base;
135 m_extraArea->update();
136}
137
138int QScriptEdit::executionLineNumber() const
139{
140 return m_executionLineNumber;
141}
142
143void QScriptEdit::setExecutionLineNumber(int lineNumber, bool error)
144{
145 m_executionLineNumber = lineNumber;
146 m_executionLineNumberHasError = error;
147 m_extraArea->update();
148 updateExtraSelections();
149 gotoLine(lineNumber);
150}
151
152void QScriptEdit::setExecutableLineNumbers(const QSet<int> &lineNumbers)
153{
154 m_executableLineNumbers = lineNumbers;
155}
156
157bool QScriptEdit::isExecutableLine(int lineNumber) const
158{
159#if 0 // ### enable me once we have information about the script again
160 return m_executableLineNumbers.contains(lineNumber);
161#else
162 Q_UNUSED(lineNumber);
163 return true;
164#endif
165}
166
167int QScriptEdit::currentLineNumber() const
168{
169 return textCursor().blockNumber() + m_baseLineNumber;
170}
171
172void QScriptEdit::gotoLine(int lineNumber)
173{
174#ifndef QT_NO_SYNTAXHIGHLIGHTER
175 int blockNumber = lineNumber - m_baseLineNumber;
176 const QTextBlock &block = document()->findBlockByNumber(blockNumber);
177 if (block.isValid()) {
178 setTextCursor(QTextCursor(block));
179 centerCursor();
180 }
181#else
182 Q_UNUSED(lineNumber);
183#endif
184}
185
186void QScriptEdit::setBreakpoint(int lineNumber)
187{
188 m_breakpoints[lineNumber] = BreakpointData();
189 m_extraArea->update();
190}
191
192void QScriptEdit::setBreakpointEnabled(int lineNumber, bool enable)
193{
194 m_breakpoints[lineNumber].enabled = enable;
195 m_extraArea->update();
196}
197
198void QScriptEdit::deleteBreakpoint(int lineNumber)
199{
200 m_breakpoints.remove(akey: lineNumber);
201 m_extraArea->update();
202}
203
204void QScriptEdit::paintEvent(QPaintEvent *e)
205{
206 QPlainTextEdit::paintEvent(e);
207}
208
209void QScriptEdit::resizeEvent(QResizeEvent *e)
210{
211 QPlainTextEdit::resizeEvent(e);
212
213 QRect cr = contentsRect();
214 int x = isLeftToRight() ? cr.left() : cr.left() + cr.width() - extraAreaWidth();
215 m_extraArea->setGeometry(QRect(x, cr.top(), extraAreaWidth(), cr.height()));
216}
217
218void QScriptEdit::updateExtraAreaWidth()
219{
220 if (isLeftToRight())
221 setViewportMargins(left: extraAreaWidth(), top: 0, right: 0, bottom: 0);
222 else
223 setViewportMargins(left: 0, top: 0, right: extraAreaWidth(), bottom: 0);
224}
225
226void QScriptEdit::updateExtraArea(const QRect &rect, int dy)
227{
228 if (dy)
229 m_extraArea->scroll(dx: 0, dy);
230 else
231 m_extraArea->update(ax: 0, ay: rect.y(), aw: m_extraArea->width(), ah: rect.height());
232
233 if (rect.contains(r: viewport()->rect()))
234 updateExtraAreaWidth();
235}
236
237void QScriptEdit::highlightCurrentLine()
238{
239 updateExtraSelections();
240}
241
242void QScriptEdit::updateExtraSelections()
243{
244 QList<QTextEdit::ExtraSelection> extraSelections;
245
246 {
247 QTextEdit::ExtraSelection selection;
248 QColor lineColor = QColor(Qt::yellow).lighter(f: 160);
249 selection.format.setBackground(lineColor);
250 selection.format.setProperty(propertyId: QTextFormat::FullWidthSelection, value: true);
251 selection.cursor = textCursor();
252 selection.cursor.clearSelection();
253 extraSelections.append(t: selection);
254 }
255 if (m_executionLineNumber != -1) {
256 QTextEdit::ExtraSelection selection;
257 QColor lineColor;
258 if (m_executionLineNumberHasError)
259 lineColor = QColor(Qt::red);
260 else
261 lineColor = QColor(Qt::green).lighter(f: 160);
262 selection.format.setBackground(lineColor);
263 selection.format.setProperty(propertyId: QTextFormat::FullWidthSelection, value: true);
264#ifndef QT_NO_SYNTAXHIGHLIGHTER
265 int blockNumber = m_executionLineNumber - m_baseLineNumber;
266 selection.cursor = QTextCursor(document()->findBlockByNumber(blockNumber));
267#endif
268 selection.cursor.clearSelection();
269 extraSelections.append(t: selection);
270 }
271
272 setExtraSelections(extraSelections);
273}
274
275int QScriptEdit::extraAreaWidth() const
276{
277 int space = 0;
278 const QFontMetrics fm(fontMetrics());
279
280 int digits = 1;
281 int max = qMax(a: 1, b: blockCount() + m_baseLineNumber);
282 while (max >= 10) {
283 max /= 10;
284 ++digits;
285 }
286 space += fm.horizontalAdvance(QLatin1Char('9')) * digits;
287
288 int markWidth = fm.lineSpacing();
289 space += markWidth;
290
291 space += 4;
292
293 return space;
294}
295
296void QScriptEdit::extraAreaPaintEvent(QPaintEvent *e)
297{
298 QRect rect = e->rect();
299 QPalette pal = palette();
300 pal.setCurrentColorGroup(QPalette::Active);
301 QPainter painter(m_extraArea);
302 painter.fillRect(r: rect, c: Qt::lightGray);
303 const QFontMetrics fm(fontMetrics());
304
305 int markWidth = fm.lineSpacing();
306 int extraAreaWidth = m_extraArea->width();
307
308 QLinearGradient gradient(QPointF(extraAreaWidth - 10, 0), QPointF(extraAreaWidth, 0));
309 gradient.setColorAt(pos: 0, color: pal.color(cr: QPalette::Window));
310 gradient.setColorAt(pos: 1, color: pal.color(cr: QPalette::Base));
311 painter.fillRect(rect, gradient);
312
313 QLinearGradient gradient2(QPointF(0, 0), QPointF(markWidth, 0));
314 gradient2.setColorAt(pos: 0, color: pal.color(cr: QPalette::Dark));
315 gradient2.setColorAt(pos: 1, color: pal.color(cr: QPalette::Window));
316 painter.fillRect(rect.intersected(other: QRect(rect.x(), rect.y(), markWidth, rect.height())), gradient2);
317
318 painter.setPen(QPen(pal.color(cr: QPalette::Window), 2));
319 if (isLeftToRight())
320 painter.drawLine(x1: rect.x() + extraAreaWidth-1, y1: rect.top(), x2: rect.x() + extraAreaWidth-1, y2: rect.bottom());
321 else
322 painter.drawLine(x1: rect.x(), y1: rect.top(), x2: rect.x(), y2: rect.bottom());
323 painter.setRenderHint(hint: QPainter::Antialiasing);
324
325#ifndef QT_NO_SYNTAXHIGHLIGHTER
326 QTextBlock block = firstVisibleBlock();
327 int blockNumber = block.blockNumber();
328 qreal top = blockBoundingGeometry(block).translated(p: contentOffset()).top();
329 qreal bottom = top + blockBoundingRect(block).height();
330
331 QString imagesPath = QString::fromLatin1(str: ":/qt/scripttools/debugging/images");
332 QString imageExt;
333// SVGs don't work on all platforms, even when QT_NO_SVG is not defined, so disable SVG usage for now.
334// #ifndef QT_NO_SVG
335#if 0
336 imageExt = QString::fromLatin1("svg");
337#else
338 imageExt = QString::fromLatin1(str: "png");
339#endif
340
341 while (block.isValid() && top <= rect.bottom()) {
342 if (block.isVisible() && bottom >= rect.top()) {
343
344 int lineNumber = blockNumber + m_baseLineNumber;
345 if (m_breakpoints.contains(akey: lineNumber)) {
346 int radius = fm.lineSpacing() - 1;
347 QRect r(rect.x(), (int)top, radius, radius);
348 QIcon icon(m_breakpoints[lineNumber].enabled
349 ? QString::fromLatin1(str: "%0/breakpoint.%1").arg(a: imagesPath).arg(a: imageExt)
350 : QString::fromLatin1(str: "%0/d_breakpoint.%1").arg(a: imagesPath).arg(a: imageExt));
351 icon.paint(painter: &painter, rect: r, alignment: Qt::AlignCenter);
352 }
353 if (m_executionLineNumber == lineNumber) {
354 int radius = fm.lineSpacing() - 1;
355 QRect r(rect.x(), (int)top, radius, radius);
356 QIcon icon(QString::fromLatin1(str: "%0/location.%1").arg(a: imagesPath).arg(a: imageExt));
357 icon.paint(painter: &painter, rect: r, alignment: Qt::AlignCenter);
358 }
359
360 if (!isExecutableLine(lineNumber))
361 painter.setPen(pal.color(cr: QPalette::Mid));
362 else
363 painter.setPen(QColor(Qt::darkCyan));
364 QString number = QString::number(lineNumber);
365 painter.drawText(x: rect.x() + markWidth, y: (int)top, w: rect.x() + extraAreaWidth - markWidth - 4,
366 h: fm.height(), flags: Qt::AlignRight, str: number);
367 }
368
369 block = block.next();
370 top = bottom;
371 bottom = top + blockBoundingRect(block).height();
372 ++blockNumber;
373 }
374#endif
375}
376
377void QScriptEdit::extraAreaMouseEvent(QMouseEvent *e)
378{
379 QTextCursor cursor = cursorForPosition(pos: QPoint(0, e->pos().y()));
380#ifndef QT_NO_SYNTAXHIGHLIGHTER
381 cursor.setPosition(pos: cursor.block().position());
382#endif
383
384 QFontMetrics fm(font());
385 int markWidth = fm.lineSpacing();
386
387 if (e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
388 bool hand = (e->pos().x() <= markWidth);
389 int lineNumber = cursor.blockNumber() + m_baseLineNumber;
390 hand = hand && isExecutableLine(lineNumber);
391#ifndef QT_NO_CURSOR
392 if (hand != (m_extraArea->cursor().shape() == Qt::PointingHandCursor))
393 m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor);
394#endif
395 }
396
397 if (e->type() == QEvent::MouseButtonPress) {
398 if (e->button() == Qt::LeftButton) {
399 int lineNumber = cursor.blockNumber() + m_baseLineNumber;
400 bool executable = isExecutableLine(lineNumber);
401 if ((e->pos().x() <= markWidth) && executable)
402 m_extraAreaToggleBlockNumber = cursor.blockNumber();
403 else
404 m_extraAreaToggleBlockNumber = -1;
405 }
406 } else if (e->type() == QEvent::MouseButtonRelease) {
407 if (e->button() == Qt::LeftButton) {
408 if ((m_extraAreaToggleBlockNumber != -1) && (e->pos().x() <= markWidth)) {
409 int lineNumber = m_extraAreaToggleBlockNumber + m_baseLineNumber;
410 bool on = !m_breakpoints.contains(akey: lineNumber);
411 emit breakpointToggleRequest(lineNumber, on);
412 }
413 } else if (e->button() == Qt::RightButton) {
414 int lineNumber = cursor.blockNumber() + m_baseLineNumber;
415 if (!isExecutableLine(lineNumber))
416 return;
417 bool has = m_breakpoints.contains(akey: lineNumber);
418 QMenu *popup = new QMenu();
419 QAction *toggleAct = new QAction(tr(s: "Toggle Breakpoint"), popup);
420 popup->addAction(action: toggleAct);
421 QAction *disableAct = new QAction(tr(s: "Disable Breakpoint"), popup);
422 QAction *enableAct = new QAction(tr(s: "Enable Breakpoint"), popup);
423 QWidget *conditionWidget = new QWidget();
424 {
425 QHBoxLayout *hbox = new QHBoxLayout(conditionWidget);
426 hbox->addWidget(new QLabel(tr(s: "Breakpoint Condition:")));
427 hbox->addWidget(new QLineEdit());
428 }
429// QWidgetAction *conditionAct = new QWidgetAction(popup);
430// conditionAct->setDefaultWidget(conditionWidget);
431 if (has) {
432 popup->addSeparator();
433 popup->addAction(action: m_breakpoints[lineNumber].enabled ? disableAct : enableAct);
434// popup->addAction(conditionAct);
435 }
436 QAction *ret = popup->exec(pos: e->globalPos());
437 if (ret) {
438 if (ret == toggleAct) {
439 emit breakpointToggleRequest(lineNumber, on: !has);
440 } else if (ret == disableAct) {
441 emit breakpointEnableRequest(lineNumber, enable: false);
442 } else if (ret == enableAct) {
443 emit breakpointEnableRequest(lineNumber, enable: true);
444 }// else if (ret == conditionAct) {
445 //}
446 }
447 popup->deleteLater();
448 }
449 }
450}
451
452bool QScriptEdit::extraAreaEvent(QEvent *e)
453{
454 if (e->type() == QEvent::ToolTip) {
455 // ### show the breakpoint's condition, if any
456 return true;
457 }
458 return false;
459}
460
461QT_END_NAMESPACE
462

source code of qtscript/src/scripttools/debugging/qscriptedit.cpp