在 QTextBrowser 中跟踪文本插入

Track text insertion in QTextBrowser

我正在制作一个应用程序并使用 QTextBrowser 来显示消息。它应该解析 ascii 颜色,所以我的 class (say MessageBoard) 继承自 QTextBrowser。我可以替换ascii色码,插入前根据ascii码设置MessageBoard的文字颜色

但是有很多方法可以将文本插入 QTextBrowser所以 MessageBoard 应该能够准确检测到文本插入的位置以及它的长度.

问题是,QTextBrowser(通过 QTextEdit)仅提供 textChanged 信号,但无法获取更改发生的位置。

所以是没有办法得到它还是我遗漏了什么?

我已经解决了问题,但这是我遇到的问题(参见 main.cpp)。 MessageBoard.h

#ifndef MESSAGEBOARD_H
#define MESSAGEBOARD_H

#include <QTextBrowser>

#define AC_BLACK        "\u001b[30m"
#define AC_RED          "\u001b[31m"
#define AC_GREEN        "\u001b[32m"
#define AC_YELLOW       "\u001b[33m"
#define AC_BLUE         "\u001b[34m"
#define AC_MAGENTA      "\u001b[35m"
#define AC_CYAN         "\u001b[36m"
#define AC_WHITE        "\u001b[37m"
#define AC_RESET        "\u001b[0m"

using AsciiStringPos = std::pair<int /*index*/,int /*length*/>;

class MessageBoard : public QTextBrowser
{
public:
    MessageBoard(QWidget *parent = nullptr);
    void appendText(const QByteArray &text);
    ~MessageBoard();
private:
    std::pair<AsciiStringPos,QColor> find_ascii(const QByteArray &text, int starts_from);
private:
    std::map<QByteArray, QColor> m_colors;
};
#endif // MESSAGEBOARD_H

MessageBoard.cpp

#include "MessageBoard.h"
#include <QRegularExpression>
#include <climits>

MessageBoard::MessageBoard(QWidget *parent)
    : QTextBrowser(parent),
      m_colors({
{QByteArray(AC_BLACK) ,     Qt::black},
{QByteArray(AC_RED) ,       Qt::red},
{QByteArray(AC_GREEN) ,     Qt::green},
{QByteArray(AC_YELLOW) ,    Qt::yellow},
{QByteArray(AC_BLUE) ,      Qt::blue},
{QByteArray(AC_MAGENTA) ,   Qt::magenta},
{QByteArray(AC_CYAN) ,      Qt::cyan},
{QByteArray(AC_WHITE) ,     Qt::white}
               })
{
    m_colors.insert({QByteArray(AC_RESET) , textColor()});
}

void MessageBoard::appendText(const QByteArray &text)
{
    int index = 0;
    QTextCursor text_cursor = textCursor();
    text_cursor.movePosition(QTextCursor::End);
    auto res = find_ascii(text,0);
    while(res.first.first != -1)        //color string's index
    {
        text_cursor.insertText(text.mid(index,res.first.first - index));//append text before the color
        QTextCharFormat format;
        format.setForeground(res.second);   //set color to charformat
        text_cursor.setCharFormat(format);  //set charformat
        index = res.first.first         //color string started from
                + res.first.second;     //color string length
        res = find_ascii(text,index);   //find next color
    }
    text_cursor.insertText(text.mid(index));
}

std::pair<AsciiStringPos, QColor> MessageBoard::find_ascii(const QByteArray &text, int starts_from)
{
    QByteArray first_color;
    int min_index = INT_MAX;
    for(const auto &p : m_colors)
    {
        int index = text.indexOf(p.first,starts_from);
        if(index != -1 && min_index > index)
        {
            min_index = index;
            first_color = p.first;
        }
    }
    if(first_color.isNull())
        return {{-1,0},m_colors[QByteArray(AC_RESET)]};
    else
        return {{min_index,first_color.length()},m_colors[first_color]};
}

MessageBoard::~MessageBoard()
{
}

main.cpp

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MessageBoard w;
    //appendText is manually created, so I can parse text before inserting.
    w.appendText(AC_GREEN "This is written with " AC_RED " Ascii " AC_GREEN " escaped words." AC_RESET);
    //append, can't do the same because I don't know the location where it was inserted.
    w.append(AC_MAGENTA "This won't be written in magenta.");
    w.appendText(AC_CYAN "This will be written in cyan" AC_RESET);
    w.zoomIn(5);
    w.show();
    return a.exec();
}

Output Image

如果我很好地理解你的要求,我想你可能想使用这个信号https://doc.qt.io/qt-5/qtextdocument.html#contentsChange

您将通过此 https://doc.qt.io/qt-5/qtextedit.html#document-prop

访问 QTextDocument

我已经解决了。我将 QDocumentcontentsChange 信号与 MessageBoard2textInserted 插槽相连。每当插入任何文本时,我都会复制它们并将其从文档中删除,然后解析其 ascii 颜色代码并根据代码设置颜色。然后我插入文本,更改文本颜色,插入文本,我通过一个循环递归地完成它。这是我的代码

MessageBoard2.h

#ifndef MESSAGEBOARD2_H
#define MESSAGEBOARD2_H

#include <QTextBrowser>
#define AC_BLACK        "\u001b[30m"
#define AC_RED          "\u001b[31m"
#define AC_GREEN        "\u001b[32m"
#define AC_YELLOW       "\u001b[33m"
#define AC_BLUE         "\u001b[34m"
#define AC_MAGENTA      "\u001b[35m"
#define AC_CYAN         "\u001b[36m"
#define AC_WHITE        "\u001b[37m"
#define AC_RESET        "\u001b[0m"

class MessageBoard2 : public QTextBrowser
{
private:
    class SearchResults{
    private:
        struct result_t{
            std::size_t index;
            std::size_t length;
            QColor color;
        };
        std::vector<result_t> vec;
        std::size_t iterator;
    public:
        SearchResults() : iterator(0){}
        bool hasMatch() const {return !vec.empty();}
        bool hasNext() const {return iterator < vec.size();}
        const result_t &next() {return vec[iterator++];}
        friend class ::MessageBoard2;
    };
public:
    MessageBoard2(QWidget *parent = nullptr);
    ~MessageBoard2();
    SearchResults find_ascii(const QString &text, int starts_from);
private slots:
    void textInserted(int pos, int sub, int add);
    void parseAndInsert(const QString &text);
private:
    bool m_should_react;    //prevent recursive calls
    QTextDocument *m_document;
    std::map<QString, QColor> m_colors;
};

#endif // MESSAGEBOARD2_H

MessageBoard2.cpp

#include "MessageBoard2.h"
#include <QRegularExpressionMatch>
#include <QTextBlock>

MessageBoard2::MessageBoard2(QWidget *parent) :
    QTextBrowser(parent),
    m_colors({
    {QStringLiteral(AC_BLACK) ,     Qt::black},
    {QStringLiteral(AC_RED) ,       Qt::red},
    {QStringLiteral(AC_GREEN) ,     Qt::green},
    {QStringLiteral(AC_YELLOW) ,    Qt::yellow},
    {QStringLiteral(AC_BLUE) ,      Qt::blue},
    {QStringLiteral(AC_MAGENTA) ,   Qt::magenta},
    {QStringLiteral(AC_CYAN) ,      Qt::cyan},
    {QStringLiteral(AC_WHITE) ,     Qt::white}
           }),
    m_should_react(true)
{
    m_colors.insert({QStringLiteral(AC_RESET),textColor()});
    m_document = document();
    connect(m_document,&QTextDocument::contentsChange,this,&MessageBoard2::textInserted);
}

MessageBoard2::~MessageBoard2()
{

}

void MessageBoard2::textInserted(int pos, int sub, int add)
{
    if(m_should_react && add > 0)
    {
        QTextCursor text_cursor = textCursor();
        text_cursor.setPosition(pos);
        text_cursor.setPosition(pos+add,QTextCursor::KeepAnchor);
        QString text = text_cursor.selectedText();
        m_should_react = false;
        text_cursor.removeSelectedText();
        setTextCursor(text_cursor);
        parseAndInsert(text);
        m_should_react = true;
    }
}

void MessageBoard2::parseAndInsert(const QString &text)
{
    int index = 0;
    QTextCursor text_cursor = textCursor();
    text_cursor.movePosition(QTextCursor::End);
    SearchResults results = find_ascii(text,0);
    while(results.hasNext())        //color string's index
    {
        const SearchResults::result_t &result = results.next();
        text_cursor.insertText(text.mid(index,result.index - index));//append text before the color
        QTextCharFormat format;
        format.setForeground(result.color);
        text_cursor.setCharFormat(format);
        index = result.index         //color string started from
                + result.length;     //color string length
    }
    text_cursor.insertText(text.mid(index));
}

MessageBoard2::SearchResults MessageBoard2::find_ascii(const QString &text, int starts_from)
{
    QRegularExpressionMatchIterator itr = QRegularExpression("\u001b\[\d+m").globalMatch(text);
    SearchResults results;
    while(itr.hasNext())
    {
        QRegularExpressionMatch match = itr.next();
        auto it = m_colors.find(match.captured());
        if(it != m_colors.end())
        {
            SearchResults::result_t result;
            result.index = match.capturedStart();
            result.length = match.capturedLength();
            result.color = it->second;
            results.vec.push_back(result);
        }
        std::cout << std::endl;
    }
    return results;
}

main.cpp

#include "MessageBoard2.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MessageBoard2 w;
    //execpt setPlainText function, I can parse the strings and set color accordingly.
    w.textCursor().insertText(AC_GREEN "This is written with " AC_RED " Ascii " AC_GREEN " escaped words." AC_RESET);
    w.insertPlainText(AC_MAGENTA "This will be written in magenta.");
    w.append(AC_CYAN "This will be written in cyan" AC_RESET);
    w.zoomIn(5);
    w.show();
    return a.exec();
}

Output Image