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 https://mcmap.net/q/520449/-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();
}
:)