# ifndef EDITOR_HXX
# define EDITOR_HXX
/// from qt http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
# include <QPlainTextEdit>
# include <QPainter>
# include <QTextBlock>
# include <QResizeEvent>
# include <QSyntaxHighlighter>
# include <QRegularExpression>
# include <QRegularExpressionMatch>
# include <cassert>
# include <iostream>
class Highlighter : public QSyntaxHighlighter {
Q_OBJECT
Q_SIGNALS :
void include ( QString ) ;
public :
Highlighter ( QTextDocument * parent ) : QSyntaxHighlighter ( parent ) {
QString commands = " auth|ca-certificate|call|case|default|check|clear-cookies|click|clicktype|client-certificate|do|download|echo|execute|exists|exit|expect|fail|for|function|if|else|while|ignore|unignore|ignoreto|include|label|load|not|offline-storage-path|open|screenshot|set|setvalue|sleep|testcase|testsuite|timeout|unset|upload " ;
_expressions < < Expression ( " ^ *( " + commands + " ) \\ b " ) . weight ( QFont : : Bold ) . fg ( Qt : : darkBlue )
< < Expression ( " ^ *#.*$ " ) . weight ( QFont : : Bold ) . fg ( Qt : : black ) ;
}
protected :
void highlightBlock ( const QString & text ) {
static QRegularExpression inc ( " ^ *include +([^ ].*. \\ .wt) " ) ;
QRegularExpressionMatch m ( inc . match ( text ) ) ;
if ( m . hasMatch ( ) ) {
QTextCharFormat fmt ;
if ( QFile ( m . captured ( 1 ) ) . exists ( ) ) {
fmt . setForeground ( Qt : : darkGreen ) ;
fmt . setFontWeight ( QFont : : Bold ) ;
} else {
fmt . setForeground ( Qt : : darkRed ) ;
fmt . setFontStrikeOut ( true ) ;
}
setFormat ( m . capturedStart ( 1 ) , m . capturedLength ( 1 ) , fmt ) ;
include ( m . captured ( 1 ) ) ;
}
for ( auto e : _expressions ) {
auto m2 ( e . re . match ( text ) ) ;
for ( int i ( 0 ) ; i < = m2 . lastCapturedIndex ( ) ; + + i ) {
setFormat ( m2 . capturedStart ( i ) , m2 . capturedLength ( i ) , e . fmt ) ;
}
}
}
private :
struct Expression {
Expression ( QString s ) : re ( s ) { }
Expression ( QString s , QTextCharFormat f ) : re ( s ) , fmt ( f ) { }
Expression & weight ( int w ) { fmt . setFontWeight ( w ) ; return * this ; }
Expression & strike ( bool s = true ) { fmt . setFontStrikeOut ( s ) ; return * this ; }
Expression & fg ( const QBrush & b ) { fmt . setForeground ( b ) ; return * this ; }
QRegularExpression re ;
QTextCharFormat fmt ;
} ;
QList < Expression > _expressions ;
} ;
class CodeEditor ;
class LineNumberArea : public QWidget {
public :
LineNumberArea ( CodeEditor * editor ) ;
QSize sizeHint ( ) const override ;
protected :
void paintEvent ( QPaintEvent * event ) override ;
private :
CodeEditor * codeEditor ;
} ;
class CodeEditor : public QPlainTextEdit {
Q_OBJECT ;
Q_SIGNALS :
void include ( QString ) ;
void link ( QString ) ;
public :
CodeEditor ( QWidget * parent = 0 ) : QPlainTextEdit ( parent ) {
Highlighter * highlighter ( new Highlighter ( document ( ) ) ) ;
lineNumberArea = new LineNumberArea ( this ) ;
assert ( connect ( this , SIGNAL ( blockCountChanged ( int ) ) , SLOT ( updateLineNumberAreaWidth ( int ) ) ) ) ;
assert ( connect ( this , SIGNAL ( updateRequest ( QRect , int ) ) , SLOT ( updateLineNumberArea ( QRect , int ) ) ) ) ;
assert ( connect ( this , SIGNAL ( cursorPositionChanged ( ) ) , SLOT ( highlightCurrentLine ( ) ) ) ) ;
assert ( connect ( highlighter , SIGNAL ( include ( QString ) ) , SIGNAL ( include ( QString ) ) ) ) ;
updateLineNumberAreaWidth ( 0 ) ;
highlightCurrentLine ( ) ;
}
void gotoLine ( int line ) {
QTextCursor cursor ( document ( ) - > findBlockByNumber ( line - 1 ) ) ;
setTextCursor ( cursor ) ;
highlightCurrentLine ( ) ;
}
void lineNumberAreaPaintEvent ( QPaintEvent * event ) {
QPainter painter ( lineNumberArea ) ;
painter . fillRect ( event - > rect ( ) , Qt : : lightGray ) ;
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 ;
}
}
int 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 mousePressEvent ( QMouseEvent * e ) {
clickedAnchor = ( e - > button ( ) & Qt : : LeftButton )
? document ( ) - > findBlock ( cursorForPosition ( e - > pos ( ) ) . position ( ) ) . text ( )
: QString ( ) ;
QPlainTextEdit : : mousePressEvent ( e ) ;
}
void mouseReleaseEvent ( QMouseEvent * e ) {
if ( e - > button ( ) & Qt : : LeftButton & & ! clickedAnchor . isEmpty ( )
& & document ( ) - > findBlock ( cursorForPosition ( e - > pos ( ) ) . position ( ) ) . text ( ) = = clickedAnchor ) {
static QRegularExpression inc ( " ^ *include +([^ ].*. \\ .wt) " ) ;
QRegularExpressionMatch m ( inc . match ( clickedAnchor ) ) ;
if ( m . hasMatch ( ) & & QFile ( m . captured ( 1 ) ) . exists ( ) ) {
link ( m . captured ( 1 ) ) ;
}
}
QPlainTextEdit : : mouseReleaseEvent ( e ) ;
}
protected :
void resizeEvent ( QResizeEvent * e ) override {
QPlainTextEdit : : resizeEvent ( e ) ;
QRect cr = contentsRect ( ) ;
lineNumberArea - > setGeometry ( QRect ( cr . left ( ) , cr . top ( ) , lineNumberAreaWidth ( ) , cr . height ( ) ) ) ;
}
private Q_SLOTS :
void updateLineNumberAreaWidth ( int newBlockCount ) {
setViewportMargins ( lineNumberAreaWidth ( ) , 0 , 0 , 0 ) ;
}
void highlightCurrentLine ( ) {
QList < QTextEdit : : ExtraSelection > extraSelections ;
if ( ! isReadOnly ( ) ) {
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 ) ;
}
setExtraSelections ( extraSelections ) ;
}
void 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 ) ;
}
private :
QWidget * lineNumberArea ;
QString clickedAnchor ;
} ;
inline LineNumberArea : : LineNumberArea ( CodeEditor * editor ) : QWidget ( editor ) {
codeEditor = editor ;
}
inline QSize LineNumberArea : : sizeHint ( ) const {
return QSize ( codeEditor - > lineNumberAreaWidth ( ) , 0 ) ;
}
inline void LineNumberArea : : paintEvent ( QPaintEvent * event ) {
codeEditor - > lineNumberAreaPaintEvent ( event ) ;
}
# endif