Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt5 Syntax Highlighting in QML [closed]

I am working on a QtQuick 2.0 presentation and I would like to embed some code samples. is it possible easily to create a syntax highlighting QML element.

The objective in question is to apply syntax highlighting to a TextEdit or a TextArea. I know Qt has a QSyntaxHighlighter, but the documentation is not very clear on how exactly to apply it to a TextEdit or a TextArea.

Can you provide examples on how it's done?

like image 428
Itay Grudev Avatar asked Feb 09 '13 20:02

Itay Grudev


People also ask

What is difference between Qt and QML?

QML is the language; its JavaScript runtime is the custom V4 engine, since Qt 5.2; and Qt Quick is the 2D scene graph and the UI framework based on it. These are all part of the Qt Declarative module, while the technology is no longer called Qt Declarative.


5 Answers

Qt Quick's TextEdit item exposes a textDocument property, of type QQuickTextDocument. This is explicitly exposed so you can use QSyntaxHighlighter directly with the document.

QtQuick textEdit documentation for Qt 5.3

like image 154
James Turner Avatar answered Oct 18 '22 04:10

James Turner


I have two answers:

  1. a pure QML answer
  2. a C++ answer involving QSyntaxHighlighter

For a pure QML answer, we can use a TextArea one can set TextArea::textFormat to textFormat: TextEdit.RichText for formatting. We can use TextArea::getText() to get the plain text and set TextArea::text with the rich text. Here's a mock example that:

  • color uppercase identifiers (e.g. Button) as purple
  • color lowercase identifiers (e.g. x y z) as red
  • color numbers (e.g. 123 456) as blue
  • but leave symbols (e.g. = + ;) as black

Here's the snippet:

    TextArea {
        id: output

        property bool processing: false

        text: "<p>x = 123;</p><p>y = 456;</p><p>z = x + y;</p>"
        textFormat: TextEdit.RichText
        selectByMouse: true

        onTextChanged: {
            if (!processing) {
                processing = true;
                let p = cursorPosition;
                let markUp = getText(0, length).replace(
                  /([A-Z][A-Za-z]*|[a-z][A-Za-z]*|[0-9]+|[ \t\n]|['][^']*[']|[^A-Za-z0-9\t\n ])/g,
                    function (f) {
                        console.log("f: ", JSON.stringify(f));
                        if (f.match(/^[A-Z][A-Za-z]*$/))
                            return "<span style='color:#800080'>" + f + "</span>";
                        if (f.match(/^[a-z][A-Za-z]*$/))
                            return "<span style='color:#800000'>" + f + "</span>";
                        else if (f.match(/^[0-9]+$/))
                            return "<span style='color:#0000ff'>" + f + "</span>";
                        else if (f.match(/^[ ]/))
                            return "&nbsp;"
                        else if (f.match(/^[\t\n]/))
                            return f;
                        else if (f.match(/^[']/))
                            return "<span style='color:#008000'>" + f + "</span>";
                        else
                            return f;
                    }
                );
                text = markUp;
                cursorPosition = p;
                processing = false;
            }
        }
    }

To use Qt's QSyntaxHighlighter, you need the following:

  1. In QML, use TextEdit QML type in your application
  2. In C++, define a QSyntaxHighlighter and connect TextEdit QML type to it via the TextEdit::textDocument property
  3. In C++, implement QSyntaxHighlighter::highlightBlock( const QString& text ) in your derived class, calling QSyntaxHighlighter::setFormat() as often as needed to tokenize the text found.

To make things easier, I create a sample app https://github.com/stephenquan/QtSyntaxHighlighterApp which wraps QSyntaxHighlighter and QTextFormat as SyntaxHighlighter and TextFormat QML Types. That way, one can handle onHighlightBlock signal and put the business logic of the syntax highlighter in Javascript instead of C++:

TextEdit {
    id: textEdit
    selectByMouse: true
    text: [
        "import QtQuick 2.12",
        "",
        "Item {",
        "    Rectangle {",
        "        width: 50",
        "        height: 50",
        "        color: '#800000'",
        "    }",
        "}",
    ].join("\n") + "\n"
    font.pointSize: 12
}

SyntaxHighlighter {
    id: syntaxHighlighter
    textDocument: textEdit.textDocument
    onHighlightBlock: {
        let rx = /\/\/.*|[A-Za-z.]+(\s*:)?|\d+(.\d*)?|'[^']*?'|"[^"]*?"/g;
        let m;
        while ( ( m = rx.exec(text) ) !== null ) {
            if (m[0].match(/^\/\/.*/)) {
                setFormat(m.index, m[0].length, commentFormat);
                continue;
            }
            if (m[0].match(/^[a-z][A-Za-z.]*\s*:/)) {
                setFormat(m.index, m[0].match(/^[a-z][A-Za-z.]*/)[0].length, propertyFormat);
                continue;
            }
            if (m[0].match(/^[a-z]/)) {
                let keywords = [ 'import', 'function', 'bool', 'var',
                                'int', 'string', 'let', 'const', 'property',
                                'if', 'continue', 'for', 'break', 'while',
                    ];
                if (keywords.includes(m[0])) {
                    setFormat(m.index, m[0].length, keywordFormat);
                    continue;
                }
                continue;
            }
            if (m[0].match(/^[A-Z]/)) {
                setFormat(m.index, m[0].length, componentFormat);
                continue;
            }
            if (m[0].match(/^\d/)) {
                setFormat(m.index, m[0].length, numberFormat);
                continue;
            }
            if (m[0].match(/^'/)) {
                setFormat(m.index, m[0].length, stringFormat);
                continue;
            }
            if (m[0].match(/^"/)) {
                setFormat(m.index, m[0].length, stringFormat);
                continue;
            }
        }
    }
}

TextCharFormat { id: keywordFormat; foreground: "#808000" }
TextCharFormat { id: componentFormat; foreground: "#aa00aa"; font.pointSize: 12; font.bold: true; font.italic: true }
TextCharFormat { id: numberFormat; foreground: "#0055af" }
TextCharFormat { id: propertyFormat; foreground: "#800000" }
TextCharFormat { id: stringFormat; foreground: "green" }
TextCharFormat { id: commentFormat; foreground: "green" }
like image 37
Stephen Quan Avatar answered Oct 18 '22 06:10

Stephen Quan


There is no obvious way to achieve syntax highlighting in QML.

One could implement one's own declarative item, performing the actual highlighting with QSyntaxHighlighter but then one would have to define its own highlighting rules for language of the source code in question. I would't do that amount of coding for a presentation.

Instead I would display the code in a WebView item with the highlighting already applied as static HTML markup or with the help of a JavaScript highlighting library, for expample highlight.js.

Update 1

If the WebView item is indeed unusable, even the simple Text item with its rudimentary HTML support should be enough to handle the source code highlighting usecase if fed with static HTML.

like image 40
sebasgo Avatar answered Oct 18 '22 06:10

sebasgo


in your app file:

QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQuickTextDocument* doc = childObject<QQuickTextDocument*>(engine, "textEditor", "textDocument");
Q_ASSERT(doc != 0);

// QSyntaxHighlighter derrived class
MySyntaxHighlighter* parser = new MySyntaxHighlighter(doc->textDocument());
// use parser, see QSyntaxHighlighter doc...
int ret = app.exec();
delete parser;
return ret;

The template function to get child objects (returns the first occurence of objectName, so use unique names to identify objects in your qml files) :

template <class T> T childObject(QQmlApplicationEngine& engine,
                                 const QString& objectName,
                                 const QString& propertyName)
{
    QList<QObject*> rootObjects = engine.rootObjects();
    foreach (QObject* object, rootObjects)
    {
        QObject* child = object->findChild<QObject*>(objectName);
        if (child != 0)
        {
            std::string s = propertyName.toStdString();
            QObject* object = child->property(s.c_str()).value<QObject*>();
            Q_ASSERT(object != 0);
            T prop = dynamic_cast<T>(object);
            Q_ASSERT(prop != 0);
            return prop;
        }
    }
    return (T) 0;
}

in your qml file use a TextEdit (inside a Flickable or whatever you want) with the objectName property correctly set:

.... 
TextEdit {
    id: edit
    objectName: "textEditor"
    width: flick.width
    height: flick.height
    focus: true
    font.family: "Courier New"
    font.pointSize: 12
    wrapMode: TextEdit.NoWrap
    onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
}
....

like image 22
Bertrand Avatar answered Oct 18 '22 05:10

Bertrand


Take a look at QSyntaxHighlighter.

If you need a QML Item doing syntax highlighting, you can simply create your own by extending QDeclarativeItem and using the utility above.

like image 24
TheHuge_ Avatar answered Oct 18 '22 04:10

TheHuge_