Dark Theme with extended properties - python

Goal
While building a pyqt5 based gui that includes QListWidget with many dynamicly generated QListWidgetItem. Some of the QListWidgetItem have colors indicator based on properties of what they open when clicked.
So far i only used the default color theme and used a brush with .setBackground to each of the list items at creation to get the color scheme i had in mind.
Right now i attempt at adding a new dark theme while also changing colors dynamicly after the creation of the objects. To do that i created a new palette and set the palette for the app.
expected and actual results
To handle the list items i first attempted at setting a property as follows:
item.setProperty('cool', True)
This returned an error indicating QListWidgetItem has no setProperty attribute which i think the reasen for is that QListWidgetItem does not inherit QObject
Then i attempted at creating my own class to set the property:
class ThemedQListWidgetItem(QListWidgetItem):
def __init__(self, *args, **kwargs):
QListWidgetItem.__init__(self, *args, **kwargs)
self.cool = False
and setting the colors using:
app.setPalette(darkPalette)
app.setStyleSheet("""
ThemedQListWidgetItem[cool="true"] {
background-color: red;}
ThemedQListWidgetItem[cool="false"] {
background-color: palette(base);}
""")
The dark theme is applied but no red colored items are shown, and I don't get any errors.
Maybe im doing something wrong regarding the way pyqt is ment to be used. There must be a simple way to dynamicly change colors based on properties of gui elements.
While i could rebuild the whole list from scratch every time i doubt there isn't a better way.
My enviorment
Archlinux x64 kernel 5.10.5
Desktop Enviorment: Xfce4
Window Maneger: Xfwm4
Python Version: 3.9.1
Pyqt5 Version: 5.15.2

The QListWidgetItem are a representation of the information shown by the QListWidget, so the Qt StyleSheet cannot be used for painting as they are not visual elements. QListWidget uses the QSS to paint the visual items (in addition to using the information from the QListWidgetItem). So if you want a property based painting then you could do it through a delegate:
import random
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QApplication,
QListWidget,
QListWidgetItem,
QStyledItemDelegate,
)
CoolRole = Qt.UserRole
class Delegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
if index.data(CoolRole):
option.backgroundBrush = QColor("red")
def main():
app = QApplication(sys.argv)
w = QListWidget()
delegate = Delegate(w)
w.setItemDelegate(delegate)
for i in range(10):
it = QListWidgetItem()
it.setText(f"item {i}")
cool = random.choice((True, False))
it.setData(CoolRole, cool)
w.addItem(it)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Related

list all shortcuts of a QMainWindow

I have a PySide2 application that is growing in size, and I'd like to dump all shortcuts.
Is there a simple solution?
The first objective is to be able to list them (let's say to check they are all documented and there's no duplicates), but I'll very soon be interested in letting the user customize them (so if someone has an example of a shortcut editor, I'll be interested too; I only found this https://doc.qt.io/archives/qq/qq14-actioneditor.html for the moment).
This post Qt - Disable/enable all shortcuts suggests findChildren, so I've come up with a beginning of a solution (see code below), but I'm feeling there could be something included natively in Qt that I may have missed?
# This is file mygui.py
import sys
from PySide2.QtWidgets import QAction, QMessageBox, QMainWindow, QApplication
from PySide2.QtGui import QIcon, QKeySequence
class MyGUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('My GUI')
self.fileMenu = self.menuBar().addMenu('&File')
self.toolBar = self.addToolBar('my toolbar')
act = QAction('About', self)
act.triggered.connect(self.popup_hello)
act.setShortcuts(['Ctrl+A'])
for x in [self.fileMenu, self.toolBar]: x.addAction(act)
act = QAction('Show shortcuts', self)
act.triggered.connect(self.display_shortcuts)
for x in [self.fileMenu, self.toolBar]: x.addAction(act)
act = QAction('Quit', self, icon=QIcon.fromTheme('exit'))
act.setShortcuts(QKeySequence.Quit)
act.triggered.connect(self.close)
for x in [self.fileMenu, self.toolBar]: x.addAction(act)
def popup_hello(self):
self.statusBar().showMessage('Bienvenue')
QMessageBox.about(self, 'About', 'This is my GUI. v0.1')
def display_shortcuts(self):
for action in self.findChildren(QAction) :
print(type(action), action.toolTip(), [x.toString() for x in action.shortcuts()])
if __name__ == '__main__':
qt_app = QApplication(sys.argv)
app = MyGUI()
app.show()
#app.dumpObjectTree()
app.display_shortcuts()
qt_app.exec_()
This displays:
$ python3 mygui.py
<class 'PySide2.QtWidgets.QAction'> File []
<class 'PySide2.QtWidgets.QAction'> my toolbar []
<class 'PySide2.QtWidgets.QAction'> About ['Ctrl+A']
<class 'PySide2.QtWidgets.QAction'> Show shortcuts []
<class 'PySide2.QtWidgets.QAction'> Quit ['Ctrl+Q']
One bonus questions:
I don't see why 'my toolbar' is listed here as a QAction?
[edit] since there doesn't seem to be a native solution, I've started a small widget here.
Is there a simple solution?
There is no native method to find all the shortcuts in a window so your methodology is correct.
I don't see why 'my toolbar' is listed here as a QAction?
Not every QAction implies having an associated QShortcut, in the case of QToolBar it already has a default QAction which is the toggleViewAction(), that is the one you are getting and that does not have an associated shortcut.

PyQt4: How to color each tab in QTabWidget separately?

I'm working on a project with a GUI, for which I am using Python with PyQt4 module.
Here is my demo code:
import sys
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle('PyQt4 demo')
self.setGeometry(50, 50, 1000, 1000)
self.createTabs()
self.styleTabs()
self.show()
def createTabs(self):
'''Creates a QTabWidget with 5 tabs,
named 1, 2, 3, 4, 5
'''
self.tabs = QtGui.QTabWidget(self)
self.tabs.resize(1000, 1000)
contents1 = QtGui.QWidget()
contents2 = QtGui.QWidget()
contents3 = QtGui.QWidget()
contents4 = QtGui.QWidget()
contents5 = QtGui.QWidget()
self.tabs.addTab(contents1, '1')
self.tabs.addTab(contents2, '2')
self.tabs.addTab(contents3, '3')
self.tabs.addTab(contents4, '4')
self.tabs.addTab(contents5, '5')
def styleTabs(self):
#Would like to add some code here which colors
#each tab with a different color.
pass
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
run()
Most objects (including QtabWidget and QTabBar) support styling with CSS using .setStyleSheet(str) method. But with this I could only achieve coloring all tabs with the same color. I've also found a way to color selected, first, last tab, but could never achieve coloring a tab for ex.: with an index of 2.
For example:
self.tabs.setStyleSheet('''
QTabBar::tab {background-color: green;}
QTabBar::tab:selected {background-color: red;}
QTabBar::tab:first {background-color: red;}
QTabBar::tab:last {background-color: red;}
''')
I've also tried applying color to current QTabBar. This works with Qt, but not with PyQt apparently:
tab = self.tabs.tabBar()
tab.setStyleSheet('background-color: grey;')
The PyQt4 coloring methods didn't work either:
plt = QtGui.QPalette()
clr = QtGui.QColor()
clr.setRgb(100, 100, 100)
plt.setColor(10, clr)
tab.setPalette(plt)
I've been searching on web a lot, but haven't found any solutions for this problem. At this point, I'm not even sure an easy solution exists.
Is there a way to modify PyQt4 source code, so one of the above techniques could be applied?
Additional info:
Python version 3.4
PyQt version 4.12
Unfortunally, QTabBar doesn't expose all its properties, since its contents are not children widgets layed out in a normal fashion, but are drawn internally using private methods.
There are two possibilities, though.
Draw the tabbar by hand, using its paintEvent. Using QStyle draw* methods, it is possible to customize it as you want, while keeping consistency with the current theme; that's not an easy task, but can be done.
Customize the background of the current selected tab only: using QTabBar's currentChanged signal, you can easily reset the stylesheet everytime the current index is changed
This is an example:
def createTabs(self):
#[create your tabs, then...]
self.tabColors = {
0: 'green',
1: 'red',
2: 'yellow',
3: 'orange',
4: 'blue',
}
self.tabs.tabBar().currentChanged.connect(self.styleTabs)
[...]
def styleTabs(self, index):
self.tabs.setStyleSheet('''
QTabBar::tab {{}}
QTabBar::tab:selected {{background-color: {color};}}
'''.format(color=self.tabColors[index]))
You might want to "initialize" it when the widget is first shown, by calling styleTabs(0), since the colouring is applied when the signal is fired only.

Python - How to keep a QComboBox's QLineEdit from adding new items

Whenever you add a basic QLineEdit to a QComboBox, if you type anything into the QComboBox and press [Enter], the QLineEdit text will get added to the QComboBox.
Just to be clear, I still want to have the ability to manage the QComboBox items (add, remove, etc) like normal. In this case, only QLineEdit is not allowed to add new items.
I'd very much prefer a solution that did not require subclassing a built-in QLineEdit/QComboBox
Does anyone know how to do this?
import functools
import sys
# Use PySide/PyQt. Doesn't matter
from Qt import QtWidgets
def print_items(combo_box):
print([combo_box.itemText(index) for index in range(combo_box.count())])
def main():
app = QtWidgets.QApplication([])
window = QtWidgets.QComboBox()
line_edit = QtWidgets.QLineEdit()
line_edit.editingFinished.connect(functools.partial(print_items, window))
window.setLineEdit(line_edit)
window.show()
sys.exit(app.exec_())
# start writing in the QLineEdit and press [Enter].
# The item is now in the QComboBox and print in the terminal
if __name__ == '__main__':
main()

How to install new QStyle for PyQt?

I'm writing a GUI in PyQt4 (and migrating to PyQt5). This is how I start my GUI:
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion')) # <- Choose the style
myGUI = MyMainWindow("First GUI")
app.exec_()
Default Styles in PyQt4 :
Apparently, PyQt4 has the following styles:
'Windows'
'WindowsXP'
'WindowsVista'
'Motif'
'CDE'
'Plastique'
'Cleanlooks'
Default Styles in PyQt5 :
PyQt5 has the following styles:
'Windows'
'WindowsXP'
'WindowsVista'
'Fusion'
Custom styles?
None of these styles has proper support for HiDpi displays (4k and the like). For example, scrollbars are too small( see this post: How to resize the scrollbar from a QTextEdit in PyQt?). And I didn't even mention the problems for those people with unsharp eyesight..
Do you know a style (preferably open-source) that provides good support for 4k displays or for people with eyesight problems?
If so, how can one download this style, and install it?
Thank you so much.
I got the answer (or let's say a workaround) through another question:
How to make Icon in QMenu larger (PyQt)?
The most straightforward way to create a new QStyle is deriving it from an existing one. PyQt provides the QProxyStyle class for that purpose. Below is the example that I've also given in the How to make Icon in QMenu larger (PyQt)? question. In this example, a custom QStyle is created (derived from "Fusion" style), and the custom style provides very big icons for the QMenu.
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
# Create a custom "QProxyStyle" to enlarge the QMenu icons
#-----------------------------------------------------------
class MyProxyStyle(QProxyStyle):
pass
def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
if QStyle_PixelMetric == QStyle.PM_SmallIconSize:
return 40
else:
return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option, widget)
# This is the main window class (with a simple QMenu implemented)
# ------------------------------------------------------------------
class TestWindow(QMainWindow):
def __init__(self):
super(TestWindow, self).__init__()
# 1. Set basic geometry and color.
self.setGeometry(100, 100, 400, 400)
self.setWindowTitle('Hello World')
palette = QPalette()
palette.setColor(QPalette.Window, QColor(200, 200, 200))
self.setPalette(palette)
# 2. Create the central frame.
self.centralFrame = QFrame()
self.centralFrame.setFrameShape(QFrame.NoFrame)
self.setCentralWidget(self.centralFrame)
# 3. Create a menu bar.
myMenuBar = self.menuBar()
fileMenu = myMenuBar.addMenu("&File")
testMenuItem = QAction(QIcon("C:\\my\\path\\myFig.png"), "&Test", self)
testMenuItem.setStatusTip("Test for icon size")
testMenuItem.triggered.connect(lambda: print("Menu item has been clicked!"))
fileMenu.addAction(testMenuItem)
# 4. Show the window.
self.show()
# Start your Qt application based on the new style
#---------------------------------------------------
if __name__== '__main__':
app = QApplication(sys.argv)
myStyle = MyProxyStyle('Fusion') # The proxy style should be based on an existing style,
# like 'Windows', 'Motif', 'Plastique', 'Fusion', ...
app.setStyle(myStyle)
myGUI = TestWindow()
sys.exit(app.exec_())
Just copy-paste the code snippet, and paste it in a *.py file. Of course, you should replace the path to the icon with a valid path on your local computer. Just provide a complete path ("C:..") to be 100% sure that Qt finds the icon drawing.
Try it out, and you should get the following window:

How do I achieve consistent highlighting of QListWidget items across widget states?

I am using PyQT 4.8.3 to create a dialog with two QListWidgets both allowing multiple selection.
I find that if these QListWidgets are enabled, the selected items are highlighted in blue only when the QListWidget has focus, without focus the highlight is light-grey.
I also find that if the QListWidgets are disabled, the selected items are highlighted in blue despite lack of focus.
As the users go from one list to the other they will find this very confusing.
As a developer I find the light-grey/unfocused, blue/disabled behaviours undesirable. I would appreciate any advice on modifying them.
I've looked through the docs for QListWidget, QListView and QAbstractView without finding anything applicable, I have also looked through the stylesheet documentation without having any luck.
I would use stylesheets here. In this example, the selected items in this QListWidget will he highlighted in blue, and when the QListWidget is disabled or without focus they will turn gray:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
class myWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.setStyleSheet( """ QListWidget:item:selected:active {
background: blue;
}
QListWidget:item:selected:!active {
background: gray;
}
QListWidget:item:selected:disabled {
background: gray;
}
QListWidget:item:selected:!disabled {
background: blue;
}
"""
)
self.listWidget = QtGui.QListWidget(self)
self.listWidget.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)
self.button = QtGui.QPushButton(self)
self.button.setText("Disable the list!")
self.button.clicked.connect(self.on_button_clicked)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.layout.addWidget(self.listWidget)
for itemNumber in range(5):
item = QtGui.QListWidgetItem(self.listWidget)
item.setText("Item {0}".format(itemNumber))
self.listWidget.addItem(item)
#QtCore.pyqtSlot()
def on_button_clicked(self):
enable = False if self.listWidget.isEnabled() else True
self.listWidget.setEnabled(enable)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('myWindow')
main = myWindow()
main.show()
sys.exit(app.exec_())
The simplest approach is alter QPalette (if style sheet doesn't work for you).
Just set palette for QListView with values you want to alter (only).
Do something like that:
QPalette customPalette;
QPalette orginalPallete = listView->palette();
customPalette->setBrush(QPalette::Disabled, QPalette::Highlight,
orginalPallete.brush(QPalette::Active, QPalette::Highlight));
listView->setPalette(customPalette);
I recommend to read how palette property works (it merges values from parents and QApplication so you need set only thing you want to change).
You can also change this colors globally by changing palette for QApplication.
QItemDelegate can be used to provide custom display features. I hope it will help you. You can reimplement
virtual void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const

Categories