TextChanged triggered with syntaxhighlighter->rehighlight()



  • In a project I'm currently working on (IDE for custom interpreted language) I need to use the textChanged() signal of a QTextEdit widget. My custom QSyntaxHighlighter is attached to the QTextEdit and doing his job just fine, but textChanged() is also emitted after using QSyntaxHighlighter->rehighlight(). Why? And how can I differentiate between user triggered textChanged() and QSyntaxHighlighter triggered textChanged()? Do I have to compare QTextEdit->toPlainText() everytime textChanged() is emitted?

    In my case the user can type code into the textEdit and hit "start" to run his code. If an error has been found, I highlight the error in the textEdit. If the user now changes the code, the error shouldn't be highlighted anymore.
    But if I simply do
    @void textChanged() { highlighter->resetErrorHighlighting(); }@

    The highlighting of the error itself calls this function! But even worse, if I do
    @highlighter->resetErrorHighlighting(); highlighter->rehighlight();@

    on textChanged(), it results in an endless loop and 100% CPU.
    Of course, resetErrorHighlighting rehighlights only if it hasn't been reset before, so that only highlights the issue (pun intended :P )

    Im using Qt 4.8 on linux and will be using Qt 5.0 on windows on my port.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    You can use QObject::sender() to know who emitted the signal



  • Thanks for your response!
    @QObject::sender()->metaObject()->className()@

    always gives me "QPlainTextEdit" as expected.


  • Lifetime Qt Champion

    You could use blockSignals() on your line edit before calling the various highlighter functions



  • That works, but why does a SyntaxHighlighter change the text of a QPlainTextEdit? That doesn't make any sense.


  • Lifetime Qt Champion

    On the contrary it does, the syntax highlighter will modify the formatting of your document to show the highlighting which counts in the modification that triggers textChanged



  • It modifies the formatting, but not the text, so a signal called "anythingChanged()" would have made more sense here than "textChanged()", I suppose.



  • Sorry for the doublepost, but now I'm getting another problem.
    The destructor of QSyntaxHighlighter seems to trigger the textChanged() signal, but the QPlainTextEdit has already been destroyed (by "delete ui" in the MainWindow destructor) and it results in a call to __cxa_pure_virtual which terminates the program!
    Is this a bug?
    I know I can circumvent it by destroying them in the right order, but shouldn't the QObject system already handle such things correctly for me?


  • Lifetime Qt Champion

    This might indeed be a bug since the ownership of the highlighter should have been taken or do you set its parent later ?



  • The parent is QPlainTextEditor::document and it doesn't even get to highlightBlock(text). My workaround is
    @ ui->codeEdit->blockSignals(true);
    delete highlighter;
    delete ui;@

    which works 100%. Simplest solution would be not to trigger textChanged() in the destructor or not by the QSyntaxHighlighter at all.


  • Lifetime Qt Champion

    The way I see it:

    highlighter is destroyed

    document must be "cleaned" from highlighting

    documentChanged is emitted



  • Here, a complete stacktrace:
    The MainWindow deletes the QSyntaxHighlighter, which triggers textChanged(), which calls QSyntaxHighlighter::rehighlight(), which seems to be pure virtual as being destroyed.
    @0 raise /lib64/libc.so.6
    1 abort /lib64/libc.so.6
    2 __gnu_cxx::__verbose_terminate_handler()
    3 ??
    4 std::terminate()
    5 __cxa_pure_virtual
    6 ?? /usr/lib64/libQtGui.so.4
    7 ?? /usr/lib64/libQtGui.so.4
    8 QSyntaxHighlighter::rehighlight()
    9 SteveHighlighter::resetHighlight
    10 MainWindow::textChanged mainwindow.cpp 187
    11 MainWindow::qt_static_metacall
    12 QMetaObject::activate(QObject*, QMetaObject const*, int, void**)
    13 QMetaObject::activate(QObject*, QMetaObject const*, int, void**)
    14 ??
    15 QTextControl::qt_metacall(QMetaObject::Call, int, void**)
    16 QMetaObject::activate(QObject*, QMetaObject const*, int, void**)
    17 ??
    18 QSyntaxHighlighter::setDocument(QTextDocument*)
    19 QSyntaxHighlighter::~QSyntaxHighlighter()
    20 SteveHighlighter::~SteveHighlighter stevehighlighter.h
    21 SteveHighlighter::~SteveHighlighter stevehighlighter.h
    22 MainWindow::~MainWindow mainwindow.cpp
    23 main@


  • Lifetime Qt Champion

    Just thought about something... Why do you need to call rehighlight after resetErrorHighlighting ? Shouldn't that cleanup also do the highlighting ?

    Also, could you share the code of the highlighter ? I just tried with a minimal highlighter and everything goes well



  • bq. Just thought about something… Why do you need to call rehighlight after resetErrorHighlighting ? Shouldn’t that cleanup also do the highlighting ?

    Yes, and it does it with rehighlight :-)

    @#include "stevehighlighter.h"

    SteveHighlighter::SteveHighlighter(QPlainTextEdit *editor)
    : QSyntaxHighlighter(editor->document()), parent(editor)
    {
    QTextCharFormat format;

     format.setForeground(QColor(0, 128, 0));
     format.setFontWeight(QFont::Bold);
     setFormat(TOK_KEYWORD, format);
    
     format.setForeground(QColor(192, 16, 112));
     format.setFontWeight(QFont::Bold);
     setFormat(TOK_CONDITION, format);
    
     format.setForeground(QColor(128, 0, 0));
     format.setFontWeight(QFont::Bold);
     setFormat(TOK_INSTRUCTION, format);
    

    }

    void SteveHighlighter::setFormat(Token what, const QTextCharFormat &format)
    {
    this->format[what] = format;
    rehighlight();
    }

    void SteveHighlighter::resetHighlight()
    {
    rehighlight();
    }@

    Header:
    @#ifndef STEVEHIGHLIGHTER_H
    #define STEVEHIGHLIGHTER_H

    #include <QPlainTextEdit>
    #include <QSyntaxHighlighter>

    enum Token {
    TOK_KEYWORD = 0,
    TOK_CONDITION = 1,
    TOK_INSTRUCTION = 2
    };

    class SteveHighlighter : public QSyntaxHighlighter
    {
    Q_OBJECT

    public:
    SteveHighlighter(QPlainTextEdit *editor);
    void highlightBlock(const QString &text) override {};
    void highlight(int line, const QTextCharFormat &format, const QString &what = "") {};
    void resetHighlight();
    void setFormat(Token what, const QTextCharFormat &format);

    private:
    int highlight_line;
    QString highlight_str;
    QPlainTextEdit *parent;
    QTextCharFormat format[TOK_INSTRUCTION + 1];
    };
    #endif // STEVEHIGHLIGHTER_H@

    I removed some irrelevant parts, but I also noticed after removal of the constructor code, it no longer crashes. I didn't expect that.


  • Lifetime Qt Champion

    The thing is, you are creating an infinite loop calling rehighlight in the slot triggered by textChanged, you should rather do everything in one pass (doesn't mean that you can't split the highlighting part in several functions)
    or delay the highlighting a bit.

    One thing to try to change: pass the text edit directly as parent (third constructor)



  • bq. The thing is, you are creating an infinite loop calling rehighlight in the slot triggered by textChanged, you should rather do everything in one pass (doesn’t mean that you can’t split the highlighting part in several functions) or delay the highlighting a bit.

    But that's not the cause of the crash.

    bq. One thing to try to change: pass the text edit directly as parent (third constructor)

    Tried that. Doesn't work, QPlainTextEdit != QTextEdit. QPlainTextEdit gets implicitly converted to QObject.


  • Lifetime Qt Champion

    Indeed... But why are you keeping a pointer to the parent ? You don't have any destructor and IIRC the default generated should delete all the member so deleting the parent variable which hold a pointer to the QPlainTextEdit which is the parent (indirectly) of the highlighter might be a problem...

    Sorry I mixed the two classes



  • I'm 100% sure a default constructor doesn't delete any objects a member pointer points to, only the pointer itself. A manual-specified destructor always runs before destroying the non-static members.


  • Lifetime Qt Champion

    You're right ! The object pointed to are not destroyed by the default constructor.

    There must be something else... What did you removed from the constructor that avoided the crash ?



  • The three
    @ QTextCharFormat format;
    format.setForeground(QColor(0, 128, 0));
    format.setFontWeight(QFont::Bold);
    setFormat(TOK_KEYWORD, format);@

    blocks. Without them, it works. Might be because there's a similiar function setFormat but with different arguments in QSyntaxHighlighter. But then, it should also crash without these lines.
    I could narrow it down further to rehighlight() in setFormat, but I really don't know why.


  • Lifetime Qt Champion

    Strange... Can't reproduce it on OS X

    By the way, I would recommend using a QHash, you'll be more flexible if you add new Tokens


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.