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

时间:2016-10-30 18:22:32

标签: c++ linux c++11 qt5

Gtk3丰富text widget机制(基于GtkTextBuffer& GtkTextView)同时包含"begin-user-action""end-user-actions"信号,可以方便地快速处理用户输入(并将其与 application 生成的对缓冲区或视图的更改区分开来。)

但看起来Qt5中没有类似的东西。例如,我的不完全理解是,QTextEdit::insertHtmlQTextDocument::contentsChangeQTextDocument::contentsChanged不会将与用户输入(键盘或粘贴等)相关的更改与由应用

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

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

(对于好奇的人:我正在重新设计并使用C& GTK重新实现我的MELT monitor与C ++ 11& Qt5暂时称为Basixmo;所有都是免费的GPL软件,但我还没有编写Qt5的东西)

例如

我有一个窗口,其中包含一个按钮say和一个QTextEdit作为其中心窗口小部件。当我单击按钮时,文档中插入了"hello"字符串,我称之为应用程序更改(您可以想象该按钮被与用户无关的内容替换,例如一些网络输入)。当我在文本编辑器中键入一些按键时,还会从该用户操作更改中插入一些字符串。我想区分两者。

#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的虚拟成员函数,等等),特别是因为我不确定知道 all 影响QTextEdit实例的可能事件。

顺便说一句,对我的问题的一个过于宽泛的概括可能是:如何设计&amp;编写与emacs一样可编写脚本的编辑器,在C ++ 11中使用真正的 Qt5编码风格和小部件(当然,通过嵌入一些可编写脚本的解释器来代替Guile)。

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

2 个答案:

答案 0 :(得分:3)

在您的特定情况下,您感兴趣的信号将发送到属于QTextDocument的{​​{1}}对象。
您可以在lambda中使用QTextEdit来阻止发出这些信号:

QSignalBlocker

这样,当应用程序更改文档的上下文时,您将不再收到#include <QSignalBlocker> // ... tb->addAction("say", [=]{ const QSignalBlocker blocker{ted->document()}; ted->insertPlainText("hello"); }); 信号。

有关详细信息,请参阅official documentation

请注意,contentsChange上的信号不会被禁止 它们可能绑定到QTextEdit并传播(如果您对这些细节感兴趣,可以查看该类的代码)。
如果要区分用户更改和应用程序更改,现在可以扩展类并添加要从lambda中发出的自定义信号(例如QTextDocument)。
否则,直接打电话给所有人。
做什么主要取决于具体问题以及软件的设计方式。

作为附注并在评论中提到,更多细节可以更好地解释其工作原理 internalContentChange(我说)转发 QTextEdit请求到内部insertPlainText。因此,后者发出一个信号(再次,让我说)QTextDocument捕获并传播。 这就是为什么上述技巧通过抑制内部类别的信号在特定情况下很好地适用的原因。换句话说,QTextEdit都有自己的信号,并作为其组件发出的某些信号(主要是QTextEdit)的重发射器。

为了清晰起见,我试图保持简单,但您可以通过查看存在的类here(公共存储库)来跟踪请求和信号的流程。
特别是,hereQTextDocument组件将QTextEdit调用转发给内部QTextEditControl组件的位置,其本身就是insertPlainText和{{3} }到内部光标......等等 这里需要对此进行全面分析。

答案 1 :(得分:0)

@skypjack's answer略有不同的方法可能是标记应用程序更改而不是阻止它们:

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

请注意,此实现是一个基本示例,应在实际用例中进行调整。例如(如注释中所指出的),多线程应用程序中可能存在问题。