4
votes

Gtk3 rich text widget machinery (based upon GtkTextBuffer & GtkTextView) has both "begin-user-action" and "end-user-actions" signals, which permits quickly to process user input conveniently (and distinguish it from application generated changes to the buffer or the view).

But it looks like there is no similar stuff in Qt5. For example, my incomplete understanding is that QTextEdit::insertHtml or QTextDocument::contentsChange or QTextDocument::contentsChanged does not separate changes related to user input (either keyboard, or pasting, etc...) from those done by the application.

What I have in mind is some syntax oriented editor.

I'm probably misunderstanding Qt5 rich text editor support.

(For curious people: I am redesigning & reimplementing my MELT monitor with C & GTK into something with C++11 & Qt5 tentatively called Basixmo; all is GPL free software, but I have not coded yet the Qt5 thing)

example

I have a window with a button say and a QTextEdit as its central widget. When I click the button, a "hello" string is inserted in the document, and I call that an application change (you could imagine that the button is replaced by something unrelated to the user, e.g. some network input). When I type some keypresses into the text-editor, some string is also inserted from that user-action change. I want to distinguish both.

#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QToolBar>
#include <fstream>
#include <iostream>
int main(int argc, char**argv)
{
  QApplication app(argc, argv);
  auto win = new QMainWindow;
  auto tb = win->addToolBar("Basile example");
  auto ted = new QTextEdit;
  win->setCentralWidget(ted);
  tb->addAction("say",[=]{ted->insertPlainText("hello");});
  auto doc = ted->document();
  QObject::connect(doc,&QTextDocument::contentsChange,
                   [=](int pos, int removedchars, int addedchars)
                      { std::clog << "contentChange: pos=" << pos
                                  << " removedchars=" << removedchars
                                  << " addedchars=" << addedchars
                                  << std::endl; });
  win->show();
  app.exec();
  delete win;
}


//// Local Variables:
//// compile-command: "g++ -std=c++11 -Wall -g $(pkg-config --cflags Qt5Core Qt5Widgets Qt5Gui) -fPIC $(pkg-config --libs Qt5Core Qt5Widgets Qt5Gui) -o eqb eqb.cc"
//// End:

But when I run the above code, the contentsChange signal (connected to my lambda function outputting to std::clog) is triggered for both user-action changes and application changes.

I don't care to work at the QTextEdit, or QTextDocument or QTextCursor level, but I want to separate user-action changes (typing into the QTextEdit widget, or pasting with a mouse click & menu, etc...) from application changes. I would like to avoid working at widget events level (e.g. redefine a virtual member function for QWidget::keyPressEvent in my own class, and so on) in particular because I am not sure to know all the possible events affecting a QTextEdit instance.

BTW, a too broad generalization of my question might be : how to design & code an editor as scriptable as emacs is using genuine Qt5 coding style and widgets in C++11 (of course by embedding some scriptable interpreter à la Guile).

PS. If that matters, my desktop system runs Debian/testing/x86-64. Qt is version 5.6.1, my code is compiled by GCC 6.2; the compilation command is in the last wide comment.

2

2 Answers

3
votes

In your specific case, the signals in which you are interested are emitted on the QTextDocument object that is part of the QTextEdit.
You can use a QSignalBlocker within your lambda to prevent those signals to be emitted:

#include <QSignalBlocker>

// ...

tb->addAction("say", [=]{
    const QSignalBlocker blocker{ted->document()};
    ted->insertPlainText("hello");
});

This way, you won't receive anymore the contentsChange signal when the application changes the context of the document.

See the official documentation for further details.

Note that signals on QTextEdit are not inhibited.
They are probably bound to the ones of the QTextDocument and propagated (you can see the code of the class if you are interested in these details).
If you want to differentiate between user changes and application changes, you can now extend the class and add custom signals to be emitted from within your lambda (as an example, internalContentChange).
Otherwise, call directly a method and that's all.
What to do mostly depends on the specific problem and on how the software has been designed.


As a side note and as mentioned in the comments, a few more details to better explain why it works.
The QTextEdit (let me say) forwards the insertPlainText request to the internal QTextDocument. As a consequence, the latter emits a signal that is (again, let me say) caught and propagated by the QTextEdit.
That's why the above described trick applies well in the specific case by inhibiting the signals on the internal class. In other terms, QTextEdit both has its own signals and acts as a re-emitter for some signals emitted by its components (mainly the QTextDocument).

I tried to stay simple for the sake of clarity, but you can follow the flow of the request and the signal by looking at the classes that are present here (the public repository).
In particular, here is where the QTextEdit component forwards the insertPlainText call to the internal QTextEditControl component, that is for itself a QWidgetTextControl and forwards the call to the internal cursor... and so on.
Too long a trail to be fully analyzed here.

0
votes

A slightly different approach to @skypjack's answer may be to tag application changes instead of blocking them:

bool application_change = false;
tb->addAction("say",
    [=, &application_change]{
        application_change = true;
        ted->insertPlainText("hello");
        application_change = false;
    }
);

// ...

QObject::connect(doc, &QTextDocument::contentsChange,
    [=, &application_change](int pos, int removedchars, int addedchars){
        std::clog << "contentChange: pos=" << pos
            << " removedchars=" << removedchars
            << " addedchars=" << addedchars
            << std::endl;
        if(application_change)
            std::clog << "this is an application change" << std::endl;
    }
);

The switch on application_change may be wrapped into a locker:

struct BoolLocker
{
    bool& locked;

    BoolLocker(bool& to_lock_):
        locked(to_lock_)
    {
        locked = true;
    }

    ~BoolLocker(bool& to_lock_)
    {
        locked = false;
    }
};

// ...

bool application_change = false;
tb->addAction("say",
    [=, &application_change]{
        BoolLocker locker(application_change);
        ted->insertPlainText("hello");
    }
);

Or generated in a wrapping lambda through a factory function:

bool application_change = false;

template<typename F>
auto make_application_change(F&& f)
{
    return [&f]{
        application_change = true;
        f();
        application_change = false;
    };
}

// ...

tb->addAction("say",
    make_application_change(
        [=]{
            ted->insertPlainText("hello");
        }
    )
);

Note that this implementation is a basic example which should be adapted in a real use case. For example (as pointed in the comments), there may be issues in a multithread application.