Qt 5.8 QTextEdit Text Cursor Color Won't Change
Asked Answered
S

2

2

I am trying to make the text cursor on a QTextEdit red (rgb(255,0,0)). Despite my best efforts, it continues to blink white.

From what I've found, the Style Sheet "color" property is supposed to change the color of the cursor. Not sure what's wrong.

My Code:

    textEntry = new QTextEdit();
    textEntry->setFont(QFont("Electrolize", 9, 1));
    textEntry->setMinimumHeight(25);
    textEntry->setMaximumHeight(25);
    textEntry->setLineWrapMode(QTextEdit::NoWrap);
    textEntry->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    textEntry->setStyleSheet("color: rgb(255, 0, 0);"
                             "border: 1px solid rgb(255, 0, 0);");

Edit: I encourage a full read of Scheff's answer. It's awesome. I noticed that the cursor created with his solution didn't blink, though, so I wanted to share a blinking version derived from Scheff's code with my (inexperienced) addition.

TextEdit.h

#ifndef TEXTEDIT_H
#define TEXTEDIT_H

#include <QTextEdit>
#include <QTimer>

class TextEdit : public TextEdit
{
    Q_OBJECT
public:
    explicit TextEdit(QWidget *parent = nullptr);

private:
    QTimer *timer;
    QPainter *pPainter;
    bool bCursorVisible;

protected:
    virtual void paintEvent(QPaintEvent *pEvent) override;

signals:
    sendUpdate();

public slots:
    void timerSlot();
};

#endif // TEXTEDIT_H

TextEdit.cpp

#include "textedit.h"

#include <QPainter>
#include <QColor>
#include <QTimer>

TextEdit::TextEdit(QWidget *parent) : QTextEdit(parent) {
    bCursorVisible = true;

    timer = new QTimer(this);
    timer->start(500);
    connect(this, SIGNAL(sendUpdate()), this, SLOT(update()));
    connect(timer, SIGNAL(timeout()), this, SLOT(timerSlot()));
}

void TextEdit::paintEvent(QPaintEvent *event)
{
  // use paintEvent() of base class to do the main work
  QTextEdit::paintEvent(event);
  // draw cursor (if widget has focus)
  if (hasFocus()) {
    if(bCursorVisible) {
        const QRect qRect = cursorRect(textCursor());
        QPainter qPainter(viewport());
        qPainter.fillRect(qRect, QColor(255, 0, 0, 255));
    } else {
        const QRect qRect = cursorRect(textCursor());
        QPainter qPainter(viewport());
        qPainter.fillRect(qRect, QColor(0, 0, 0, 255));
    }
  }
}

void TextEdit::timerSlot() {
    if(bCursorVisible) {
        bCursorVisible = false;
    } else {
        bCursorVisible = true;
    }

    emit sendUpdate();
}
Saad answered 27/4, 2019 at 6:6 Comment(4)
Where did you read the Style Sheet "color" property is supposed to change the color of the cursor? All I found in the Qt doc. Qt Style Sheets Reference color was The color used to render text.Outhe
@Scheff That's what I'm hearing from other Q&A's around the web. #26616901 forum.qt.io/topic/20769/… There are others, but, like you, I couldn't find a reference to this in Qt Style SheetsSaad
Hmmm. 1.) I would consider the Qt doc. as original source concerning Qt. 2.) This might be subject of style engines. Hence, I wouldn't wonder if this may work on one system but not on another. Though, I found similar hints as well e.g. this: (Solved)Change text cursor color in QTextEdit. This brought me to another idea: Have your tried QTextEdit::setTextColor() alternatively?Outhe
Per 1.) Have you found any reference to the Text Cursor Color in the Qt doc.? Per 2.) I agree 100%, I'm on Win7 if anyone is wondering. I tried setTextColor(), but that had no effect on the Text Cursor (by itself or combined with style sheet color property). I'm at a loss here. Feeling like maybe it's QPallette related? The cursor is currently white on my black background.. so something is working to keep it from being black on black.Saad
T
7

There was some conversation with OP beforehand, as I had serious doubts whether the color property of the QTextEdit is responsible for the color of text cursor as well.

All I found in the Qt Style Sheets Reference:

The color used to render text.

This property is supported by all widgets that respect the QWidget::palette.

If this property is not set, the default is whatever is set for in the widget's palette for the QWidget::foregroundRole (typically black).

Out of curiosity, I fiddled a little bit with colors of QTextEdit.

  1. I could reproduce what OP described:
    Changing the text color of QTextEdit (e.g. with QTextEdit::setTextColor()) has an effect on inserted text typed afterwards but it didn't change the text cursor color (at least, on the platforms where I tested).

  2. While fiddling I realized another fact that encouraged me to write this answer:
    IMHO, the text cursor ignores any color setting. Instead, it inverts the pixels under the drawn text cursor bar.
    Have a look at QPainter::RasterOp_NotSource to see what I mean.

My sample application testQTextEditCursorColor.cc:

#include <QtWidgets>

class ColorButton: public QPushButton {
  private:
    QColor _qColor;

  public:
    explicit ColorButton(
      const QString &text, const QColor &qColor = Qt::black,
      QWidget *pQParent = nullptr):
      QPushButton(text, pQParent)
    {
      setColor(qColor);
    }
    virtual ~ColorButton() = default;
    ColorButton(const ColorButton&) = delete;
    ColorButton& operator=(const ColorButton&) = delete;

    const QColor& color() const { return _qColor; }
    void setColor(const QColor &qColor)
    {
      _qColor = qColor;
      QFontMetrics qFontMetrics(font());
      const int h = qFontMetrics.height();
      QPixmap qPixmap(h, h);
      qPixmap.fill(_qColor);
      setIcon(qPixmap);
    }

    QColor chooseColor()
    {
      setColor(QColorDialog::getColor(_qColor, this, text()));
      return _qColor;
    }
};

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  qDebug() << app.style();
  // setup GUI
  QMainWindow qWin;
  qWin.resize(250, 100);
  qWin.setWindowTitle("Test Set Cursor Color");
  QTextEdit qTextEdit;
  qWin.setCentralWidget(&qTextEdit);
  QToolBar qToolBar;
  ColorButton qBtnColor("Text Color", qTextEdit.palette().color(QPalette::Text));
  qToolBar.addWidget(&qBtnColor);
  ColorButton qBtnColorBg("Background", qTextEdit.palette().color(QPalette::Base));
  qToolBar.addWidget(&qBtnColorBg);
  qWin.addToolBar(&qToolBar);
  qWin.show();
  // install signal handlers
  QObject::connect(&qBtnColor, &QPushButton::clicked,
    [&]() { qTextEdit.setTextColor(qBtnColor.chooseColor()); });
  QObject::connect(&qBtnColorBg, &QPushButton::clicked,
    [&]() {
      QPalette qPal = qTextEdit.palette();
      qPal.setColor(QPalette::Base, qBtnColorBg.chooseColor());
      qTextEdit.setPalette(qPal);
    });
  // runtime loop
  return app.exec();
}

and the corresponding Qt project file testQTextEditCursorColor.pro:

SOURCES = testQTextEditCursorColor.cc

QT += widgets

Compiled and tested in cygwin64 on Windows 10:

$ qmake-qt5 testQTextEditCursorColor.pro

$ make && ./testQTextEditCursorColor
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQTextEditCursorColor.o testQTextEditCursorColor.cc
g++  -o testQTextEditCursorColor.exe testQTextEditCursorColor.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 
Qt Version: 5.9.4
QFusionStyle(0x6000e10c0, name = "fusion")

Snapshot in X11 (white background) Snapshot in X11 (black background)

So, black makes a white cursor, white makes a black cursor (independent of any color setting). Assuming my above statement is correct, cyan background (#00ffff) should make red cursor (#ff0000):

Snapshot in X11 (cyan background)

For a comparison, I wrote a CMake script CMakeLists.txt:

project(QTextEditCursorColor)

cmake_minimum_required(VERSION 3.10.0)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Qt5Widgets CONFIG REQUIRED)

include_directories("${CMAKE_SOURCE_DIR}")

add_executable(testQTextEditCursorColor testQTextEditCursorColor.cc)

target_link_libraries(testQTextEditCursorColor Qt5::Widgets)

and compiled and tested in VS2017 again:

Qt Version: 5.11.2
QWindowsVistaStyle(0x1c1ed936690, name = "windowsvista")

(Please note, the different style engine.)

Snapshot in Windows 10 (white background) Snapshot in Windows 10 (black background)

The rendering in the Windows GDI makes it obvious that glyph pixels are inverted as well (but I noticed the same in the X11 test above):

Cursor in Windows 10 (5× magnified)


The above in mind, it becomes obvious that it's a bad idea to use middle gray as background color. The bitwise NOT of e.g. #808080 is #7f7f7f and there is little contrast between these two colors. (I don't provide a snapshot because I was not able to recognize the right time to hit the Print key for a snapshot with text cursor drawn.)


OP referred to another Q&A: SO: Qt 5.3 QPlainTextEdit Change the QTextCursor color. Though, this answer was accepted and upvoted, it didn't help to change the cursor color on my side in any other way as described above. These are the modifications, I tried on my sample:

  • replacing QTextEdit by QPlainTextEdit
  • changing text cursor width with qTextEdit.setCursorWidth()
  • used style sheets instead of modifying the colors in palette

including using the exposed code in linked answer "literally".


After some conversation with thuga (the author of the accepted answer to SO: Qt 5.3 QPlainTextEdit Change the QTextCursor color, it appeared that there is a bug report for Qt 5.8 concerning this:

Qt 5.8 no longer allows QPlainTextEdit's cursor color to be set

which is marked as Unresolved at the time of writing. (Currently, Qt5.12 is the most recent version.)


After having long explained why it cannot work out-of-the-box, finally a sample how OPs intention can be achieved with a custom-painted cursor:

#include <QtWidgets>

class TextEdit: public QTextEdit {
  protected:
    virtual void paintEvent(QPaintEvent *pEvent) override;
};

void TextEdit::paintEvent(QPaintEvent *pQEvent)
{
  // use paintEvent() of base class to do the main work
  QTextEdit::paintEvent(pQEvent);
  // draw cursor (if widget has focus)
  if (hasFocus()) {
    const QRect qRect = cursorRect(textCursor());
    QPainter qPainter(viewport());
    qPainter.fillRect(qRect, textColor());
  }
}

class ColorButton: public QPushButton {
  private:
    QColor _qColor;

  public:
    explicit ColorButton(
      const QString &text, const QColor &qColor = Qt::black,
      QWidget *pQParent = nullptr):
      QPushButton(text, pQParent)
    {
      setColor(qColor);
    }
    virtual ~ColorButton() = default;
    ColorButton(const ColorButton&) = delete;
    ColorButton& operator=(const ColorButton&) = delete;

    const QColor& color() const { return _qColor; }
    void setColor(const QColor &qColor)
    {
      _qColor = qColor;
      QFontMetrics qFontMetrics(font());
      const int h = qFontMetrics.height();
      QPixmap qPixmap(h, h);
      qPixmap.fill(_qColor);
      setIcon(qPixmap);
    }

    QColor chooseColor()
    {
      setColor(QColorDialog::getColor(_qColor, this, text()));
      return _qColor;
    }
};

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  qDebug() << app.style();
  // setup GUI
  QMainWindow qWin;
  qWin.resize(250, 100);
  qWin.setWindowTitle("Test Set Cursor Color");
  TextEdit qTextEdit;
  qWin.setCentralWidget(&qTextEdit);
  qTextEdit.setCursorWidth(QFontMetrics(qTextEdit.font()).averageCharWidth());
  QToolBar qToolBar;
  ColorButton qBtnColor("Text Color",
    qTextEdit.palette().color(QPalette::Text));
  qToolBar.addWidget(&qBtnColor);
  ColorButton qBtnColorBg("Background",
    qTextEdit.palette().color(QPalette::Base));
  qToolBar.addWidget(&qBtnColorBg);
  qWin.addToolBar(&qToolBar);
  qWin.show();
  // install signal handlers
  QObject::connect(&qBtnColor, &QPushButton::clicked,
    [&]() { qTextEdit.setTextColor(qBtnColor.chooseColor()); });
  QObject::connect(&qBtnColorBg, &QPushButton::clicked,
    [&]() {
      QPalette qPal = qTextEdit.palette();
      qPal.setColor(QPalette::Base, qBtnColorBg.chooseColor());
      qTextEdit.setPalette(qPal);
    });
  // runtime loop
  return app.exec();
}

The QTextEdit is replaced by the derived TextEdit with an overridden paintEvent().

The QTextEdit::paintEvent() is called in TextEdit::paintEvent() to do the main work. Afterwards the cursor is (re-)painted with a rectangle in the textColor. (This simply over-paints the already rendered built-in text cursor.)

Snapshot in cygwin with X11 Snapshot in Windows 10 with GDI

Note:

A smalls trap is the usage of QPainter in TextEdit::paintEvent(). Because QTextEdit is derived from QAbstractScrollArea, QPainter qPainter(this); would be wrong. Instead, QPainter qPainter(viewport()); has to be used. This is mentioned in the Qt doc. for QAbstractScrollArea::paintEvent():

Note: If you open a painter, make sure to open it on the viewport().

Technician answered 28/4, 2019 at 11:6 Comment(5)
I'd love to see that sample. I'd like a solution, if possible.Saad
You're a champion. Thanks so much!Saad
How to do the same in python 3 pyqt5?Slavin
@VictorVosMottorthanksMonica I added another answer. (SO is not a code writing service. Nevertheless, I admit that Python might be more popular than C++ and found it worth (the challenge of) porting my sample code to Python.) ;-)Outhe
Thanks, I know that "SO is not a code writing service", but I guess it is very useful to add the pyqt5 version because it is widely used library.Slavin
T
1

Upon request, a Python3 / PyQt5 port of the sample program in my other answer:

#!/usr/bin/python3

import sys
from PyQt5.QtCore import QT_VERSION_STR, QRect
from PyQt5.QtWidgets import QApplication, QMainWindow, QToolBar
from PyQt5.QtGui import QPainter, QIcon, QPixmap, QFontMetrics, QPalette
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtWidgets import QPushButton, QColorDialog


class TextEdit(QTextEdit):

    def __init__(self, parent=None):
        QTextEdit.__init__(self, parent)

    def paintEvent(self, event):
        # use paintEvent() of base class to do the main work
        QTextEdit.paintEvent(self, event)
        # draw cursor (if widget has focus)
        if self.hasFocus():
            rect = self.cursorRect(self.textCursor())
            painter = QPainter(self.viewport())
            painter.fillRect(rect, self.textColor())


class ColorButton(QPushButton):

    def __init__(self, text, custom_color, parent=None):
        QPushButton.__init__(self, text, parent)
        self.setColor(custom_color)

    def color(self):
        return self.custom_color

    def setColor(self, custom_color):
        self.custom_color = custom_color
        font_metrics = QFontMetrics(self.font())
        h = font_metrics.height()
        pixmap = QPixmap(h, h)
        pixmap.fill(self.custom_color)
        self.setIcon(QIcon(pixmap))

    def chooseColor(self):
        self.setColor(QColorDialog().getColor(self.custom_color))
        return self.custom_color


if __name__ == '__main__':
    print("Qt Version: {}".format(QT_VERSION_STR))
    app = QApplication(sys.argv)
    print(app.style())
    # build GUI
    win = QMainWindow()
    win.resize(250, 100)
    win.setWindowTitle("Test Set Cursor Color")
    text_edit = TextEdit()
    text_edit.setCursorWidth(QFontMetrics(text_edit.font()).averageCharWidth())
    win.setCentralWidget(text_edit)
    tool_bar = QToolBar()
    btn_color = ColorButton(
        "Text Color", text_edit.palette().color(QPalette.Text))
    tool_bar.addWidget(btn_color)
    btn_color_bg = ColorButton(
        "Background", text_edit.palette().color(QPalette.Base))
    tool_bar.addWidget(btn_color_bg)
    win.addToolBar(tool_bar)
    win.show()
    # install signal handlers
    btn_color.clicked.connect(
        lambda state: text_edit.setTextColor(btn_color.chooseColor()))

    def on_click(state):
        palette = text_edit.palette()
        palette.setColor(QPalette.Base, btn_color_bg.chooseColor())
        text_edit.setPalette(palette)

    btn_color_bg.clicked.connect(on_click)
    # runtime loop
    sys.exit(app.exec_())

Output:

Qt Version: 5.9.3
<PyQt5.QtWidgets.QCommonStyle object at 0x6ffffd8dc18>

Snapshot of testQTextEditCursorColor.py

Technician answered 14/7, 2020 at 8:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.