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.
main
Alex Huszagh 2022-04-30 14:26:01 -05:00
parent 744571ebae
commit 52686dd40c
5 changed files with 232 additions and 12 deletions

View File

@ -16,6 +16,7 @@ This stylesheet aims to be similar across all platforms, and provide a nice UI f
- [PyQt6 Installation](#pyqt6-installation) - [PyQt6 Installation](#pyqt6-installation)
- [Features](#features) - [Features](#features)
- [Extending Stylesheets](#extending-stylesheets) - [Extending Stylesheets](#extending-stylesheets)
- [Known Issues](#known-issues)
- [Debugging](#debugging) - [Debugging](#debugging)
- [Development Guide](#development-guide) - [Development Guide](#development-guide)
- [Configuring](#configuring) - [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). 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 # Debugging
Have an issue with the styles? Here's a few suggestions, prior to filing a bug report: 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 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 # Development Guide

View File

@ -127,11 +127,11 @@ def main():
os.environ['QT_SCALE_FACTOR'] = str(args.scale) os.environ['QT_SCALE_FACTOR'] = str(args.scale)
else: else:
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
app = QtWidgets.QApplication(sys.argv[:1] + unknown)
if args.style != 'native': if args.style != 'native':
style = QtWidgets.QStyleFactory.create(args.style) style = QtWidgets.QStyleFactory.create(args.style)
QtWidgets.QApplication.setStyle(style) app.setStyle(style)
app = QtWidgets.QApplication(sys.argv[:1] + unknown)
window = QtWidgets.QMainWindow() window = QtWidgets.QMainWindow()
# use the default font size # use the default font size

215
example/placeholder_text.py Normal file
View File

@ -0,0 +1,215 @@
#!/usr/bin/env python
#
# The MIT License (MIT)
#
# Copyright (c) <2013-2014> <Colin Duquesnoy>
# 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())

View File

@ -31,7 +31,6 @@
''' '''
import argparse import argparse
import logging
import os import os
import sys import sys
@ -444,12 +443,12 @@ def main():
os.environ['QT_SCALE_FACTOR'] = str(args.scale) os.environ['QT_SCALE_FACTOR'] = str(args.scale)
else: else:
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv[:1] + unknown) app = QtWidgets.QApplication(sys.argv[:1] + unknown)
window = QtWidgets.QMainWindow() window = QtWidgets.QMainWindow()
style = QtWidgets.QStyleFactory.create(args.style) if args.style != 'native':
app.setStyle(ApplicationStyle(style)) style = QtWidgets.QStyleFactory.create(args.style)
app.setStyle(ApplicationStyle(style))
# use the default font size # use the default font size
font = app.font() font = app.font()
@ -459,7 +458,6 @@ def main():
font.setFamily(args.font_family) font.setFamily(args.font_family)
app.setFont(font) app.setFont(font)
# setup ui # setup ui
ui = Ui() ui = Ui()
ui.setup(window) ui.setup(window)

View File

@ -31,7 +31,6 @@
''' '''
import argparse import argparse
import logging
import os import os
import sys import sys
@ -604,11 +603,12 @@ def main():
os.environ['QT_SCALE_FACTOR'] = str(args.scale) os.environ['QT_SCALE_FACTOR'] = str(args.scale)
else: else:
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
app = QtWidgets.QApplication(sys.argv[:1] + unknown)
if args.style != 'native': if args.style != 'native':
style = QtWidgets.QStyleFactory.create(args.style) style = QtWidgets.QStyleFactory.create(args.style)
QtWidgets.QApplication.setStyle(style) app.setStyle(style)
logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv[:1] + unknown)
window = QtWidgets.QMainWindow() window = QtWidgets.QMainWindow()
# use the default font size # use the default font size