Search code examples
c++qtqt5qplaintextedit

QPlainTextEdit with multiple colours on a line


tl;dr:

QPlainTextEdit::appendPlainText(QString) appends a newline to my text widget. QPlainTextEdit::insertPlainText(QString) doesn't seem affected by setCurrentCharFormat().

Is there a way to append text while listening to the current QTextCharFormat without inserting a newline

Details

I have a "terminal" style widget which gets text from stdout from a child process and displays it in a QPlainTextEdit.

Before I had colour content, I could simply do this:

void ProcessWidget::appendText(QString text)
{
    m_textedit->appendPlainText(text);
}

Colours appear in the text using the escape character '\033' followed by a colour. I can detect the colour and set the pallette appropriately:

void ProcessWidget::appendText(QString text)
{
    Qt::GlobalColor colour = GetColour(text);

    QTextCharFormat tf = m_textedit->currentCharFormat();
    tf.setForeground(QBrush(colour));
    m_textedit->setCurrentCharFormat(tf);

    m_textedit->appendPlainText(text);
}

This works if there is only one colour per line, but if my colour changes half-way through each line, then I need to be a bit crazier:

std::map<QString, Qt::GlobalColor>   m_colours;
QPlainTextEdit*                      m_textedit;
...

void ProcessWidget::AppendText(QString text)
{
    while(true)
    {
        int iColour = text.indexOf('\033');

        if (iColour == -1)
            break;

        QString pretext = text.mid(0, iColour);

        if (!pretext.isEmpty())
        {
            m_textedit->appendPlainText(pretext);
        }

        text.remove(0, iColour);

        for (auto pair : m_colours)
        {
            if ( text.startsWith(pair.first) )
            {
                QTextCharFormat tf = m_textedit->currentCharFormat();
                tf.setForeground(QBrush(pair.second));
                m_textedit->setCurrentCharFormat(tf);
                text.remove(0, pair.first.size());
                break;
            }
        }
    }

    if (!text.isEmpty())
    {
        m_textedit->appendPlainText(text);
    }
}

However, because I use appendPlainText(), each new colour found gives me a new line.

I've tried replacing appendPlainText() with:

m_textedit->moveCursor (QTextCursor::End);
m_textedit->insertPlainText(text);
m_textedit->moveCursor (QTextCursor::End);

then adding '\n' at the end. But in that case, I don't get any colours anymore. I've also tried appendHtml() but that doesn't seem to make a difference.


Solution

  • For these cases the simplest thing is to use HTML and insert the tags: <font color = "..."> </ font>

    Example:

    #include <QApplication>
    #include <QDateTime>
    #include <QPlainTextEdit>
    #include <QTimer>
    #include <QVBoxLayout>
    #include <QWidget>
    
    const std::map<QString, QColor> m_colours {{"red", QColor(Qt::red)},
                                               {"blue", QColor(Qt::blue)},
                                               {"green", QColor(Qt::green)}
                                              };
    
    class ProcessWidget: public QWidget{
        Q_OBJECT
    public:
        ProcessWidget(QWidget *parent=nullptr):
            QWidget(parent),
            lay{this}
        {
            m_textedit.setReadOnly(true);
            lay.addWidget(&m_textedit);
        }
    public slots:
        void appendText(const QString & text){
            QString html{text};
            int j = 0;
            bool start = true;
            QString textColor;
    
            while ((j = html.indexOf(QChar('\033'), j)) != -1) {
                html.remove(j, 1);
                QColor color;
                for(auto & pair : m_colours){
                    if(html.mid(j).startsWith(pair.first)){
                        color = pair.second;
                        html.remove(j, pair.first.length());
                    }
                }
                if(start){
                    textColor = QString("<font color=\"%1\">").arg(color.name());
                    start = false;
                }
                else
                    textColor = QString("</font><font color=\"%1\">").arg(color.name());
                html.insert(j, textColor);
                j += 1+textColor.length();
            }
            html.append("</font>");
            m_textedit.appendHtml(html);
        }
    private:
        QPlainTextEdit m_textedit;
        QVBoxLayout lay;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        ProcessWidget w;
        QTimer timer;
        QObject::connect(&timer, &QTimer::timeout, [&w](){
            QString text = QString("\033redDateTime: %1 \033blueDate:%2 \033greenTime:%3")
                    .arg(QDateTime::currentDateTime().toString())
                    .arg(QDate().currentDate().toString())
                    .arg(QTime::currentTime().toString());
            w.appendText(text);
        });
        timer.start(1000);
        w.show();
        return a.exec();
    }
    
    #include "main.moc"
    

    enter image description here