使用QTextEdit检测用户input(并将其与应用程序更改区分开来)

Gtk3丰富的文本控件机制(基于GtkTextBuffer和GtkTextView )既有“开始用户操作”和“最终用户操作”信号,可以方便地快速处理用户input(并将其与应用程序生成的更改区分开来或视图)。

但是看起来在Qt5中没有类似的东西。 例如,我不完全的理解是, QTextEdit :: insertHtml或QTextDocument :: contentsChange或QTextDocument :: contentsChanged没有分开与用户input(键盘或粘贴等)相关的变化与应用程序完成的变化。

我想到的是一些面向语法的编辑器。

我可能误解了Qt5富文本编辑器的支持。

(对于好奇的人们,我正在用C&GTK重新devise和重新实现MELT监视器 ,把它变成C ++ 11和Qt5,暂时称为Basixmo ,都是GPL免费软件,但是我还没有编码Qt5的东西)

我有一个buttonsay和一个QTextEdit作为其中心部件的窗口。 当我点击button时,文档中会插入一个"hello"string,我称之为应用程序更改(您可以想象button被与用户无关的内容replace,例如某些networkinginput)。 当我在文本编辑器中键入一些按键时,还会从该用户操作更改中插入一些string。 我想区分两者。

 #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: 

但是当我运行上面的代码时, contentsChange信号(连接到我的lambda函数输出到std::clog )会触发用户操作更改和应用程序更改。

我不介意在QTextEditQTextDocumentQTextCursor级别工作,但是我希望从应用程序更改中分离用户操作更改(键入QTextEdit小部件,或粘贴鼠标单击和菜单等) 。 我想避免在小部件事件级别工作(例如,在我自己的类中重新定义QWidget::keyPressEvent的虚拟成员函数,等等),特别是因为我不确定是否知道影响QTextEdit实例的所有可能的事件。

顺便说一句,我的问题的泛泛而谈可能是:如何devise和编写一个编辑器为脚本化的emacs是使用真正的 Qt5编码风格和部件在C + + 11(当然通过embedded一些脚本化的解释器Guile)。

PS。 如果那么重要,我的桌面系统运行Debian / testing / x86-64。 Qt是5.6.1版本,我的代码是由GCC 6.2编译的; 编译命令在最后一个广泛的评论。

在您的具体情况下,您感兴趣的信号将QTextDocumentQTextEdit一部分QTextDocument对象上。
您可以在您的lambda中使用QSignalBlocker来防止这些信号被发射:

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

这样,当应用程序更改文档的上下文时,将不会再收到contentsChange信号。

有关更多详细信息,请参阅官方文档

请注意QTextEdit上的信号不被禁止。
它们可能被绑定到QTextDocument并传播(如果你对这些细节感兴趣,你可以看到这个类的代码)。
如果您想区分用户更改和应用程序更改,现在可以扩展该类并添加要在您的lambda中发出的自定义信号(例如internalContentChange )。
否则,直接调用一个方法,这就是全部。
该做什么主要取决于具体问题以及如何设计软件。


作为一个方面的说明,并在评论中提到,更多的细节,以更好地解释为什么它的作品。
QTextEdit (让我说) insertPlainText请求转发给内部的QTextDocument 。 结果,后者发出了一个信号(再次,让我说) QTextEdit 捕获和传播
这就是为什么上述技巧通过禁止内部类的信号在特定情况下适用的原因。 QTextEditQTextEdit都有自己的信号,并作为其组件发出的一些信号(主要是QTextDocument )的重新发射器。

为了清楚起见,我试图保持简单,但是可以通过查看这里存在的类(公共存储库)来跟踪请求和信号的流程。
尤其是, QTextEdit组件将insertPlainText调用转发给内部的QTextEditControl组件,也就是QWidgetTextControl , 并将调用转发给内部游标…等等。
这里的路径太长,需要充分分析。

对@ skypjack的答案稍微不同的做法可能是标记应用程序更改而不是阻止它们:

 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; } ); 

application_change上的开关可能被封装在一个储物柜中:

 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"); } ); 

或者通过工厂函数在包装lambda中生成:

 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"); } ) ); 

请注意,这个实现是一个基本的例子,应该在实际使用情况下进行调整。 例如(如评论中指出的那样),多线程应用程序中可能存在问题。