How can I change color of part of the text in QLineEdit?
Asked Answered
I

4

26

I want to add some syntax highlighting to text being written in QLineEdit, but it does not support rich text formatting, I can not change QlineEdit to something else, so I should find how to set color of text in this widget.

Is there a way to do this?

Inpatient answered 19/1, 2013 at 18:19 Comment(1)
It seems a shame they don't provide a native app for this, a kind of single line analog to QTextEdit. Summerfield, in his PyQt book, shows how to make one in Chapter 13 using QTextEdit as a base class.During
I
29

Just found a neat trick for that.

static void setLineEditTextFormat(QLineEdit* lineEdit, const QList<QTextLayout::FormatRange>& formats)
{
    if(!lineEdit)
        return;

    QList<QInputMethodEvent::Attribute> attributes;
    foreach(const QTextLayout::FormatRange& fr, formats)
    {
        QInputMethodEvent::AttributeType type = QInputMethodEvent::TextFormat;
        int start = fr.start - lineEdit->cursorPosition();
        int length = fr.length;
        QVariant value = fr.format;
        attributes.append(QInputMethodEvent::Attribute(type, start, length, value));
    }
    QInputMethodEvent event(QString(), attributes);
    QCoreApplication::sendEvent(lineEdit, &event);
}

static void clearLineEditTextFormat(QLineEdit* lineEdit)
{
    setLineEditTextFormat(lineEdit, QList<QTextLayout::FormatRange>());
}

// Usage example:
QLineEdit* lineEdit = new QLineEdit;
lineEdit->setText(tr("Task Tracker - Entry"));

QList<QTextLayout::FormatRange> formats;

QTextCharFormat f;

f.setFontWeight(QFont::Bold);
QTextLayout::FormatRange fr_task;
fr_task.start = 0;
fr_task.length = 4;
fr_task.format = f;

f.setFontItalic(true);
f.setBackground(Qt::darkYellow);
f.setForeground(Qt::white);
QTextLayout::FormatRange fr_tracker;
fr_tracker.start = 5;
fr_tracker.length = 7;
fr_tracker.format = f;

formats.append(fr_task);
formats.append(fr_tracker);

setLineEditTextFormat(lineEdit, formats);
Inpatient answered 20/1, 2013 at 11:26 Comment(1)
Somehow this does work only once for me. Setting it dynamically does nothing.Tibetan
S
2

You can change the color with the use of style sheets.

 QLineEdit* myLineEdit = new QLineEdit("Whatever");

 //for whatever case you want to change the color
 if(syntax_needs_to_highlighted)
      myLineEdit->setStyleSheet("QLineEdit#myLineEdit{color:blue}"); 

You may want to consider using QTextBrowser for this case.

Superfetation answered 20/1, 2013 at 4:36 Comment(0)
T
1

You can change color of texts like this:

QLineEdit *line = new QLineEdit();
line->setText("this is a test");
line->setStyleSheet("foreground-color: blue;");

If it won't work, replace the last line with the following:

line->setStyleSheet("color: blue;");
Thenna answered 23/1, 2013 at 16:27 Comment(1)
neither worked needed to use "color: blue;" (FWIW using PySide)Frey
B
0

I was able to accomplish this by overlaying a QLabel on top of a QLineEdit then making the text color of the line edit white. When the textEdited signal is emitted, use it to update the text of the QLabel. The QLabel accepts rich text so you can process the text in the QLineEdit and replace key words with the HTML needed to display the text in the way you want it. I'm sure you could modify the code to change the text color of the current selection.

class LabelEditPair(QLineEdit):
    """
    QLineEdit that changes the color of the word 'blue' to blue and
    the changes the font weight of the word 'bold' to bold.
    """
    def __init__(self):
        super().__init__()

        self.label = QLabel("starting out")
        self.label.setParent(self)
        self.label.move(3, 0)
        self.label.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.setStyleSheet("QLineEdit{color: white}")

        self.textEdited.connect(self.text_edited)

    def resizeEvent(self, event):
        self.label.setFixedHeight(self.height())
        self.label.setFixedWidth(self.width())
        super().resizeEvent(event)

    def text_edited(self, text):
        text = text.replace("blue", "<span style='color: blue'>blue</span>")
        text = text.replace("bold", "<span style='font-weight: bold'>bold</span>")
        self.label.setText(text)

enter image description here

EDIT

The previous example doesn't work well in instances where the text overflows from the QLineEdit widget. Here is a more comprehensive widget that uses the same idea but instead of making a subclass of QLineEdit the widget is a subclass of QFrame with a QLineEdit and two QLabels, one before the cursor and one after. The widget replaces regex matches with HTML to change the style of those characters.

class LabelEditPair(QFrame):
    """
    QLineEdit that changes the color of the word 'blue' to blue and
    the changes the font weight of the word 'bold' to bold.
    """
    def __init__(self, initial_text: str):
        super().__init__()

        self.stylized_regex: List[Tuple[str, str]] = []

        self.setFixedHeight(22)
        self.setObjectName("frame")
        self.setStyleSheet("QFrame#frame{background-color: white; border: 1px solid gray}"
                           "QFrame:hover#frame{border: 1px solid black}"
                           "QFrame:selected#frame{border: 1px solid blue}")
        self.setFrameStyle(QFrame.Box)
        self.line_edit = QLineEdit(initial_text)
        self.line_edit.setParent(self)
        self.line_edit.move(0, 2)
        self.line_edit.setStyleSheet("border: 0px solid white; background-color:transparent")

        self.line_edit.setFixedWidth(2**16)

        self.left_label = QLabel()
        self.left_label.setParent(self.line_edit)
        self.left_label.move(1, 1)
        self.left_label.setAlignment(Qt.AlignRight)
        self.left_label.setAttribute(Qt.WA_TransparentForMouseEvents)

        self.right_label = QLabel()
        self.right_label. setParent(self.line_edit)
        self.right_label.move(5, 1)
        self.right_label.setAlignment(Qt.AlignLeft)
        self.right_label.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.right_label.setFixedWidth(2**16)
        self.offset = 0

        self.line_edit.textEdited.connect(self.text_edited)
        self.line_edit.cursorPositionChanged.connect(self.apply_shift)
        self.line_edit.selectionChanged.connect(self.set_text_to_update)
        self.update_text_needed = True

        self.placeholder = ""
        self.color = (0, 0, 0)

    def text(self):
        return self.line_edit.text()

    def setReadOnly(self, read_only: bool):
        self.line_edit.setReadOnly(read_only)
        self.line_edit.setAttribute(Qt.WA_TransparentForMouseEvents, read_only)
        self.line_edit.end(False)

    def set_placeholder_text(self, text: str):
        self.placeholder = text

    def set_text_color(self, color: Tuple[int, int, int]):
        self.color = color

    def set_text_to_update(self):
        self.update_text_needed = True

    def text_edited(self, text: str):
        if len(text) == 0:
            self.left_label.setText(self.placeholder)
            self.left_label.setStyleSheet("color: gray")
            return
        self.left_label.setStyleSheet(f"color: rbg{self.color}")
        new = self.line_edit.cursorPosition()
        left_text = text[:new]
        right_text = text[new:]
        self.left_label.setText(left_text)
        self.right_label.setText(right_text)
        for style, regex in self.stylized_regex:
            matches = findall(regex, left_text)
            for match in matches:
                left_text = left_text.replace(match, f"<span style='{style}'>{match}</span>")
                self.left_label.setText(left_text)
            matches_right = findall(regex, right_text)
            for match in matches_right:
                right_text = right_text.replace(match, f"<span style='{style}'>{match}</span>")
                self.right_label.setText(right_text)
        self.update_text_needed = False

    def add_style_for_regex(self, style: str, regex: str):
        self.stylized_regex.append((style, regex))

    def apply_shift(self, old=None, new=None):
        text = self.line_edit.text()
        rect = self.line_edit.cursorRect()
        x_pos = rect.x()
        if x_pos + self.offset > self.width() - 8 and new == old + 1:
            self.offset = -1*(x_pos - (self.width()-8))
        elif new + 1 == old and x_pos + self.offset < self.width() * 1/2:
            self.offset += 5
        self.offset = min(0, self.offset)
        if len(text) == 0:
            self.offset = 0
        self.line_edit.move(self.offset, 2)
        self.left_label.setFixedWidth(x_pos + 4)
        self.right_label.move(x_pos + 5, 1)
        if self.update_text_needed:
            self.text_edited(text=text)
        self.update_text_needed = True
example usage
self.color_edit = LabelEditPair("")
self.color_edit.add_style_for_regex("color: blue", "(?:HI|HELLO)")
main_layout.addWidget(self.color_edit)

enter image description here

Bourdon answered 17/11, 2021 at 23:42 Comment(2)
But the tag is about c++, not python.Puttier
well it's still Qt... just do the same thing in c++Bourdon

© 2022 - 2024 — McMap. All rights reserved.