Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Editable multi-color QLineEdit

Tags:

c++

qt

qlineedit

I know that you can change the color of a line edit, so long as all the text is the same color, but is it possible to assign different colors to characters? That is, some characters are red, and some are black, or simply every character has a different color.

There is a similar question here - How can I change color of part of the text in QLineEdit?, but in my case there is an additional constraint - the QLineEdit must preserve color positions when edited, while new text that is entered assumes some default color. The other question does not have that constraint.

It does have an answer that is somewhat useful, however the colors are detached from the text - as you edit the line edit, whichever symbol that happens to be at a given position assumes that position's colors and formatting. That is, the formatting is not anchored to the text. Here are 2 screenshots to see what I'm talking about:

enter image description hereenter image description here

I will be working on a solution to that shortcoming myself, and when I'm ready, I'll post the results as an answer.

I'll do that either by subscribing to the textEdited() signal, or by directly handling the input events. Each time the text is changed, I'll sync the color positions to it.

In the meantime, if anyone know of a ridiculously easy solution that I've missed, or an easier approach to the problem, please feel free to share it.

like image 471
sashoalm Avatar asked Jun 04 '14 09:06

sashoalm


1 Answers

I finally implemented it, by tracking the cursor position, the last selection start and length, and the last text size. When a textEdited() signal is emitted, I use them to figure out what text has been inserted and/or deleted, and then I replay the insertion and/or deletion in the color array in order to sync it to the text.

You can specify the color to be used for text inserted by the user. If you don't specify it, the system default will be used, which will vary depending on the system theme.

The only problem is that it doesn't support Undo, because I have no idea how to distinguish if a textEdited() signal is caused by an Undo operation or not.


ColorLineEdit.h

#ifndef COLORLINEEDIT_H
#define COLORLINEEDIT_H

#include <QLineEdit>

class ColorLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    explicit ColorLineEdit(QWidget *parent = 0);
    void setCharColors(const QList<QColor> &colors = QList<QColor>());
    void setColorForInsertedText(const QColor &colorForInsertedText) { this->colorForInsertedText = colorForInsertedText; }

signals:

private slots:
    void onSelectionChanged();
    void onTextEdited(const QString &text);

private:
    int lastTextSize;
    QList<QColor> colors;
    QColor colorForInsertedText;
    int lastSelectedTextSize;
    int lastSelectionStart;
};

#endif // COLORLINEEDIT_H

ColorLineEdit.cpp

#include "colorlineedit.h"
#include <QTextLayout>

ColorLineEdit::ColorLineEdit(QWidget *parent) :
    QLineEdit(parent)
{
    connect(this, SIGNAL(selectionChanged()), SLOT(onSelectionChanged()));
    connect(this, SIGNAL(textEdited(QString)), SLOT(onTextEdited(QString)));
    lastSelectedTextSize = 0;
    lastSelectionStart = -1;
    lastTextSize = 0;
}

void ColorLineEdit::setCharColors(const QList<QColor> &colors)
{
    // See http://stackoverflow.com/questions/14417333/how-can-i-change-color-of-part-of-the-text-in-qlineedit.
    QList<QInputMethodEvent::Attribute> attributes;
    int size = colors.size();
    attributes.reserve(size);
    for (int ii = 0; ii < size ; ii++) {
        if (colors[ii].isValid()) {
            QTextCharFormat charFormat;
            charFormat.setForeground(QBrush(colors[ii]));
            const int start = ii - cursorPosition();
            const int length = 1;
            attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, length, charFormat));
        }
    }

    QLineEdit::inputMethodEvent(&QInputMethodEvent(QString(), attributes));

    lastTextSize = text().size();
    this->colors = colors;
}

void ColorLineEdit::onSelectionChanged()
{
    lastSelectionStart = selectionStart();
    lastSelectedTextSize = selectedText().size();
}

void ColorLineEdit::onTextEdited(const QString &text)
{
    if (!lastSelectedTextSize) {
        // We don't have a selection, so it's either
        // an insertion or deletion, but not both.
        int delta = text.size() - lastTextSize;
        if (delta > 0) {
            // User has inserted text.
            int pos = cursorPosition() - delta;
            for (int ii = 0; ii < delta; ii++) {
                colors.insert(pos, colorForInsertedText);
            }
        } else {
            // User has erased text.
            int pos = cursorPosition();
            colors.erase(colors.begin() + pos, colors.begin() + pos - delta);
        }
    } else {
        // There was a selection, so we have both removed
        // and inserted text.
        int pos = lastSelectionStart;
        int removedCount = lastSelectedTextSize;
        int insertedCount = cursorPosition() - pos;
        colors.erase(colors.begin() + pos, colors.begin() + pos + removedCount);
        for (int ii = 0; ii < insertedCount; ii++) {
            colors.insert(pos, colorForInsertedText);
        }
    }

    setCharColors(colors);
}

Sample usage

#include "colorlineedit.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    ColorLineEdit lineEdit;
    QList<QColor> colors;
    colors.append(Qt::red);
    colors.append(Qt::red);
    colors.append(Qt::red);
    colors.append(Qt::red);
    lineEdit.setText("abcd");
    lineEdit.setColorForInsertedText(Qt::blue);
    lineEdit.setCharColors(colors);
    lineEdit.show();

    return a.exec();
}

enter image description hereenter image description here

:)

like image 197
sashoalm Avatar answered Oct 31 '22 17:10

sashoalm