3

How to set the color of caret blinking cursor in QLineEdit? I have a question, which is how to change the color of caret blinking cursor in QLineEdit.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
liulangya
  • 31
  • 3

2 Answers2

0

Here's a solution I came up with using QLineEdit. It uses the paintEvent to generate a custom caret. I have thought about this for about 2 years and, after stumbling on this thread, decided to try to find a solution.

My solution makes an assumption that you're assigning a font to the field. In this widget, I am using the Hasklig Nerd Font. Download here.

Maybe there is a less convoluted way to go about this, but this works for me. If you change the font, you may need to re-evaluate some of the math to get the caret step feeling good.

from PySide2.QtCore import Qt, QRect
from PySide2.QtGui import QPainter, QColor, QFont, QFontMetricsF
from PySide2.QtWidgets import QWidget, QLineEdit, QCommonStyle, QStyle, QStyleOption


_WIDGET_HEIGHT: int = 24


class CaretStyle(QCommonStyle):

    def __init__(self, width: int = None):
        """
        This allows us to set the width of the default widget input caret.

        Args:
            width (int): The width of the input caret in pixels.
        """
        super().__init__()
        self._width = 5 if width is None else width

    def pixelMetric(self, pixel_metric: QStyle, option: QStyleOption = None, widget: QWidget = None):
        if pixel_metric == QStyle.PM_TextCursorWidth:
            return self._width
        else:
            return QCommonStyle.pixelMetric(self, pixel_metric, option, widget)


def remap_value(value: float, old: tuple, new: tuple) -> float:
    """Range remaps a value using old and new min/max."""
    old_min, old_max = old
    new_min, new_max = new
    return (new_max - new_min) * (value - old_min) / (old_max - old_min) + new_min


class StringInput(QLineEdit):
    _caret_pos_x: float = 0.0
    _caret_pos_y: float = 1 + _WIDGET_HEIGHT * 0.2
    _font_metric: QFontMetricsF = None

    def __init__(self, *args, **kwargs):
        """
        QLineEdit with custom input field caret.
        """
        super().__init__(parent=kwargs.get("parent", None))
        self.setObjectName("ParmInput")

        self.setFixedHeight(24)
        self.setFocusPolicy(Qt.StrongFocus)

        # hide the default caret by scaling it to 0
        self.setStyle(CaretStyle(width=0))

        # accessing font details is important for accurate
        # caret positioning
        font = QFont("Hasklug NF", 9, QFont.Thin)
        font.setHintingPreference(QFont.HintingPreference.PreferNoHinting)
        self._font_metric = QFontMetricsF(font)
        self.setFont(font)

        # align the text within the field
        self.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        # we need this so we can update the caret in the field as the user
        # does stuff.
        self.cursorPositionChanged.connect(self._update_caret_pos_x)

    def _update_caret_pos_x_x(self):
        """Calculate the position of the caret based on cursor position."""
        text: str = self.text()
        step: float = self._font_metric.averageCharWidth() - 1

        # if the field is empty set the character to teh step value.
        # this puts the caret where the first char would be. this also
        # prevents divide by 0 if no text is in the field.
        if len(text) < 1:
            self._caret_pos_x = step
            return

        cursor_pos: int = self.cursorPosition()
        text_width: int = self._font_metric.width(text)
        char_count: len(text)

        # remap the cursor position based on the current text in the field.
        self._caret_pos_x = remap_value(cursor_pos, 0, 1, 0, text_width) / char_count + step

    def get_caret_pos_x(self) -> float:
        """Queries the current position of the caret."""
        return self._caret_pos_x

    def focusInEvent(self, event) -> None:
        """We update the caret position upon focus, based on where the user clicked."""
        self._update_caret_pos_x()

    def focusOutEvent(self, event) -> None:
        """Clear the current text selection if the field loses focus."""
        self.setSelection(0, 0)

    def paintEvent(self, event):
        """This creates a custom caret for the input field."""
        super().paintEvent(event)

        # easy out if the widget doesnt have focus
        if not self.hasFocus():
            return

        width: float = 2.0
        height: float = _WIDGET_HEIGHT/1.5

        painter: QPainter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(Qt.NoPen)

        caret = QRect(self.get_caret_pos_x(), self._caret_pos_y, width, height)
        painter.setBrush(QColor(231, 167, 38, 150))
        painter.drawRect(caret)

        painter.end()
Seth
  • 11
  • 3
-1

There is one way you can give a try. Theoretically it looks like it should work (not tested. Look for compile time errors.)

First get the image of icon you want to set. Then load the image to get QPixmap object.

QPixmap myPixmap;
myPixmap.load("<<YOURIMAGEPATH>>/cursoricon.png");

Now set the color to QPixmap using https://doc.qt.io/qt-5/qpixmap.html#fill

myPixmap.fill(QColor(0,255,0));

Now create a QCursor object using QPixmap object https://doc.qt.io/qt-5/qcursor.html#QCursor-3

QCursor cursor = QCursor(myPixmap);

Then set the cursor to your line edit object.

pLineEdit->setCursor(cursor);

Actually the setCursor function, first I thought it works only for mouse cursor. But the documentation says it holds good for editor widgets also. https://doc.qt.io/qt-5/qwidget.html#cursor-prop

An editor widget might use an I-beam cursor

Pavan Chandaka
  • 11,671
  • 5
  • 26
  • 34