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)
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)
QTextEdit
. Summerfield, in his PyQt book, shows how to make one in Chapter 13 usingQTextEdit
as a base class. – During