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

detecting user input with QTextEdit (& distinguishing it from application changes)

本文关键字:应用程序 区分开 检测 QTextEdit 用户 输入 使用      更新时间:2023-10-16

Gtk3富文本小部件机制(基于GtkTextBuffer &GtkTextView)同时具有"begin-user-action"answers"end-user-actions"信号,这允许快速方便地处理用户输入(并将其与应用程序生成的缓冲区或视图更改区分开来)。

但是看起来Qt5中没有类似的东西。例如,我的不完全理解是qtexttedit::insertHtml或QTextDocument::contentsChange或QTextDocument::contentsChanged并没有将与用户输入(键盘或粘贴等)相关的更改与应用程序所做的更改分开。

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

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

(对于好奇的人:我正在重新设计&用C &amp重新实现我的MELT监视器;GTK与c++ 11的结合Qt5暂称Basixmo;都是GPL自由软件,但我还没有编码Qt5的东西)

例子

我有一个带有按钮sayQTextEdit作为其中心小部件的窗口。当我点击按钮时,一个"hello"字符串被插入到文档中,我称之为应用程序更改(您可以想象按钮被与用户无关的东西取代,例如一些网络输入)。当我在文本编辑器中键入一些按键时,还会从user-action更改中插入一些字符串。我想把两者区分开来。

#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信号(连接到输出到std::clog的lambda函数)被触发用于用户操作更改和应用程序更改。

我不关心在QTextEdit,或QTextDocumentQTextCursor级别工作,但我想分开用户操作的变化(输入到QTextEdit小部件,或粘贴与鼠标点击&菜单等)从应用程序更改。我想避免在小部件事件级别工作(例如,在我自己的类中重新定义QWidget::keyPressEvent的虚拟成员函数等),特别是因为我不确定知道所有影响QTextEdit实例的可能事件。

顺便说一句,我的问题可能过于笼统地概括为:如何设计&编写一个像emacs一样可脚本化的编辑器是使用真正的 Qt5编码风格和c++ 11中的小部件(当然通过嵌入一些可脚本化的解释器,例如Guile)。

<一口> p。如果这很重要,我的桌面系统运行Debian/testing/x86-64。Qt是5.6.1版本,我的代码是GCC 6.2编译的;编译命令在最后一个宽注释中。

在您的特定情况下,您感兴趣的信号是在作为QTextEdit的一部分的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捕获并传播。这就是为什么上面描述的技巧通过抑制内部类上的信号很好地应用于特定情况的原因。换句话说,QTextEdit既有自己的信号,又对其组成部分(主要是QTextDocument)发出的一些信号起到重发射器的作用。

为了清晰起见,我尽量保持简单,但是您可以通过查看这里提供的类(公共存储库)来跟踪请求和信号的流程。
特别是,这里是QTextEdit组件将insertPlainText调用转发给内部QTextEditControl组件的地方,这本身就是一个QWidgetTextControl,并将调用转发给内部游标…等等。
这条线索太长了,这里无法完全分析

对于@ skyypjack的答案,一个稍微不同的方法可能是标记应用程序更改,而不是阻止它们:

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

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