2

I am making an application and using QTextBrowser to show messages. It should parse ascii colors, so my class (say MessageBoard) is inheriting from QTextBrowser. I can replace ascii color code and set MessageBoard's text color according to the ascii code before insertion.

But there are many ways of inserting text into the QTextBrowser, so MessageBoard should be able to detect exactly where text is inserted and what is its length.

The problem is, QTextBrowser (through QTextEdit) provides only textChanged signal but there is no way to get where changes happened.

So is there no way to get it or I missing something?

I've solved the problem but this was the issue I was having, (See 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

Keshav Sahu
  • 98
  • 2
  • 8

2 Answers2

2

I've solved it. I connected the QDocument's contentsChange signal with MessageBoard2s textInserted slot. Whenever any text is inserted, I copy them and remove it from the document, then I parse its ascii color codes and set the colors according to the code. And then I insert text, change text color, insert text, I do it recursively through a loop. Here are my codes

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

Keshav Sahu
  • 98
  • 2
  • 8
0

If I understand your request well, I guess you may want to use this signal https://doc.qt.io/qt-5/qtextdocument.html#contentsChange

You will get access to QTextDocument with this https://doc.qt.io/qt-5/qtextedit.html#document-prop

  • Used contentsChange and now my problem is solved. Only setPlainText function is not signaling contentsChange, but it's fine as I need it to read some process's output and error. See my answer. – Keshav Sahu Dec 19 '21 at 11:32