From 52686dd40c05dac3df1f4b15baaff6bac9ee684e Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Sat, 30 Apr 2022 14:26:01 -0500 Subject: [PATCH] Added example workaround for placeholder text. Added an example PyQt6 application with a workaround to attempt to fix the placeholder text color issues, which further supports the idea that it is a Qt bug, and not a stylesheet issue. Using `QtGui.QPalette.ColorRole.PlaceholderText`, we are able to modify the placeholder text color in Qt5, but not in Qt6. Closes #41. Closes #44. --- README.md | 9 +- example/advanced-dock.py | 4 +- example/placeholder_text.py | 215 ++++++++++++++++++++++++++++++++++++ example/standard_icons.py | 8 +- example/widgets.py | 8 +- 5 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 example/placeholder_text.py diff --git a/README.md b/README.md index 147df77..723fe94 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This stylesheet aims to be similar across all platforms, and provide a nice UI f - [PyQt6 Installation](#pyqt6-installation) - [Features](#features) - [Extending Stylesheets](#extending-stylesheets) +- [Known Issues](#known-issues) - [Debugging](#debugging) - [Development Guide](#development-guide) - [Configuring](#configuring) @@ -411,12 +412,18 @@ The limitations of stylesheets include: For an example of using QCommonStyle to override standard icons in a PyQt application, see [standard_icons.py](/example/standard_icons.py). An extensive reference can be found [here](https://doc.qt.io/qt-5/style-reference.html). A reference of QStyle, and the default styles Qt provides can be found [here](https://doc.qt.io/qt-5/qstyle.html). +# Known Issues + +Some issues cannot be fixed with stylesheets alone, or there are bugs in Qt itself that prevent these issues from being fixed. + +- Placeholder Text color: for the widgets `QTextEdit`, `QPlainTextEdit`, and `QLineEdit`, you can set placeholder text for when no text is present. In Qt5, this is correctly grayed out when the placeholder text is present, which is not respected in Qt6 (as of Qt version 6.3.0). An example of a workaround [placeholder_text.py](/example/placeholder_text.py), which only works currently for Qt5. Using the native stylesheet shows it uses hard-coded colors for Qt6, so this is almost certainly a Qt bug. + # Debugging Have an issue with the styles? Here's a few suggestions, prior to filing a bug report: - Modified the application font? Make sure you do **before** setting the application stylesheet. -- Modified the application style? Make sure you do **before** you creating a `QApplication instance`. +- Modified the application style? Make sure you do **after** you creating a `QApplication instance` but **before** you show the window or add widgets. # Development Guide diff --git a/example/advanced-dock.py b/example/advanced-dock.py index ae1ed0b..876af6b 100644 --- a/example/advanced-dock.py +++ b/example/advanced-dock.py @@ -127,11 +127,11 @@ def main(): os.environ['QT_SCALE_FACTOR'] = str(args.scale) else: os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' + app = QtWidgets.QApplication(sys.argv[:1] + unknown) if args.style != 'native': style = QtWidgets.QStyleFactory.create(args.style) - QtWidgets.QApplication.setStyle(style) + app.setStyle(style) - app = QtWidgets.QApplication(sys.argv[:1] + unknown) window = QtWidgets.QMainWindow() # use the default font size diff --git a/example/placeholder_text.py b/example/placeholder_text.py new file mode 100644 index 0000000..8a99d22 --- /dev/null +++ b/example/placeholder_text.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python +# +# The MIT License (MIT) +# +# Copyright (c) <2013-2014> +# Modified by Alex Huszagh +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +''' + placeholder_text + ================ + + Example showing how to style the placeholder text for QLineEdit, + QTextEdit, and QPlainTextEdit, since in Qt6 is can be styled as + the default text color. This seems to be an issue with palettes for + Qt6 in `QPalette::PlaceholderText`, since both the stylesheets + and palette edits correctly affect styles in Qt5, but not Qt6. +''' + +import argparse +import os +import sys + +example_dir = os.path.dirname(os.path.realpath(__file__)) +home = os.path.dirname(example_dir) +dist = os.path.join(home, 'dist') + +# Create our arguments. +parser = argparse.ArgumentParser(description='Configurations for the Qt5 application.') +parser.add_argument( + '--stylesheet', + help='''stylesheet name''', + default='native' +) +# Know working styles include: +# 1. Fusion +# 2. Windows +parser.add_argument( + '--style', + help='''application style, which is different than the stylesheet''', + default='native' +) +parser.add_argument( + '--font-size', + help='''font size for the application''', + type=float, + default=-1 +) +parser.add_argument( + '--font-family', + help='''the font family''' +) +parser.add_argument( + '--scale', + help='''scale factor for the UI''', + type=float, + default=1, +) +parser.add_argument( + '--pyqt6', + help='''use PyQt6 rather than PyQt5.''', + action='store_true' +) +parser.add_argument( + '--use-x11', + help='''force the use of x11 on compatible systems.''', + action='store_true' +) +parser.add_argument( + '--set-palette', + help='''set the placeholder text palette.''', + action='store_true' +) + +args, unknown = parser.parse_known_args() +if args.pyqt6: + from PyQt6 import QtCore, QtGui, QtWidgets + QtCore.QDir.addSearchPath(args.stylesheet, f'{dist}/pyqt6/{args.stylesheet}/') + resource_format = f'{args.stylesheet}:' +else: + sys.path.insert(0, home) + from PyQt5 import QtCore, QtGui, QtWidgets + import breeze_resources + resource_format = f':/{args.stylesheet}/' +stylesheet = f'{resource_format}stylesheet.qss' + +# Compat definitions, between Qt5 and Qt6. +if args.pyqt6: + AlignHCenter = QtCore.Qt.AlignmentFlag.AlignHCenter + ReadOnly = QtCore.QFile.OpenModeFlag.ReadOnly + Text = QtCore.QFile.OpenModeFlag.Text + PlaceholderText = QtGui.QPalette.ColorRole.PlaceholderText + WindowText = QtGui.QPalette.ColorRole.WindowText +else: + AlignHCenter = QtCore.Qt.AlignHCenter + ReadOnly = QtCore.QFile.ReadOnly + Text = QtCore.QFile.Text + PlaceholderText = QtGui.QPalette.PlaceholderText + WindowText = QtGui.QPalette.WindowText + +PLACEHOLDER = QtGui.QColor(78, 79, 79, 100) + +def set_palette(widget, role, color): + '''Set the palette for the placeholder text. This only works in Qt5.''' + + palette = widget.palette(); + palette.setColor(role, color) + widget.setPalette(palette); + +def set_placeholder_palette(widget): + set_palette(widget, PlaceholderText, PLACEHOLDER) + + +class Ui: + '''Main class for the user interface.''' + + def setup(self, MainWindow): + MainWindow.setObjectName('MainWindow') + MainWindow.resize(1068, 824) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName('centralwidget') + self.layout = QtWidgets.QVBoxLayout(self.centralwidget) + self.layout.setObjectName('layout') + self.layout.setAlignment(AlignHCenter) + MainWindow.setCentralWidget(self.centralwidget) + + self.textEdit = QtWidgets.QTextEdit(self.centralwidget) + self.textEdit.setObjectName('textEdit') + self.textEdit.setPlaceholderText('Placeholder Text') + self.layout.addWidget(self.textEdit) + + self.plainTextEdit = QtWidgets.QPlainTextEdit(self.centralwidget) + self.plainTextEdit.setObjectName('plainTextEdit') + self.plainTextEdit.setPlaceholderText('Placeholder Text') + self.layout.addWidget(self.plainTextEdit) + + self.lineEdit = QtWidgets.QLineEdit(self.centralwidget) + self.lineEdit.setObjectName('lineEdit') + self.lineEdit.setPlaceholderText('Placeholder Text') + self.layout.addWidget(self.lineEdit) + + # Set the palettes. + if args.set_palette: + set_placeholder_palette(self.textEdit) + set_placeholder_palette(self.plainTextEdit) + set_placeholder_palette(self.lineEdit) + + def style_text(self, widget, text): + if text: + set_text_palette(widget) + else: + set_notext_palette(widget) + + +def main(): + 'Application entry point' + + if args.scale != 1: + os.environ['QT_SCALE_FACTOR'] = str(args.scale) + else: + os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' + + app = QtWidgets.QApplication(sys.argv[:1] + unknown) + if args.style != 'native': + style = QtWidgets.QStyleFactory.create(args.style) + app.setStyle(style) + + window = QtWidgets.QMainWindow() + + # setup ui + ui = Ui() + ui.setup(window) + window.setWindowTitle('Stylized Placeholder Text.') + + # use the default font size + font = app.font() + if args.font_size > 0: + font.setPointSizeF(args.font_size) + if args.font_family: + font.setFamily(args.font_family) + app.setFont(font) + + # setup stylesheet + if args.stylesheet != 'native': + file = QtCore.QFile(stylesheet) + file.open(ReadOnly | Text) + stream = QtCore.QTextStream(file) + app.setStyleSheet(stream.readAll()) + + # run + window.show() + if args.pyqt6: + return app.exec() + else: + return app.exec_() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/example/standard_icons.py b/example/standard_icons.py index dc4126f..05df718 100644 --- a/example/standard_icons.py +++ b/example/standard_icons.py @@ -31,7 +31,6 @@ ''' import argparse -import logging import os import sys @@ -444,12 +443,12 @@ def main(): os.environ['QT_SCALE_FACTOR'] = str(args.scale) else: os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' - logging.basicConfig(level=logging.DEBUG) app = QtWidgets.QApplication(sys.argv[:1] + unknown) window = QtWidgets.QMainWindow() - style = QtWidgets.QStyleFactory.create(args.style) - app.setStyle(ApplicationStyle(style)) + if args.style != 'native': + style = QtWidgets.QStyleFactory.create(args.style) + app.setStyle(ApplicationStyle(style)) # use the default font size font = app.font() @@ -459,7 +458,6 @@ def main(): font.setFamily(args.font_family) app.setFont(font) - # setup ui ui = Ui() ui.setup(window) diff --git a/example/widgets.py b/example/widgets.py index 1dafc79..86542a5 100644 --- a/example/widgets.py +++ b/example/widgets.py @@ -31,7 +31,6 @@ ''' import argparse -import logging import os import sys @@ -604,11 +603,12 @@ def main(): os.environ['QT_SCALE_FACTOR'] = str(args.scale) else: os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' + + app = QtWidgets.QApplication(sys.argv[:1] + unknown) if args.style != 'native': style = QtWidgets.QStyleFactory.create(args.style) - QtWidgets.QApplication.setStyle(style) - logging.basicConfig(level=logging.DEBUG) - app = QtWidgets.QApplication(sys.argv[:1] + unknown) + app.setStyle(style) + window = QtWidgets.QMainWindow() # use the default font size