1

I have a seperate qss stylesheet that contains my stylesheet definitions for my entire app. I need to add a close button as background image since I couldnt find documentation on using inbuilt icons.

customTabBar::close-button {
    padding: 0px;
    margin: 0px;
    border-radius: 2px;
    border-color: rgba(0, 0, 0, 50%);
    background-image: url(%(closeIcon)s);
    background-position: center center;
    background-repeat: none;
}

from fbs_runtime.application_context.PyQt5 import ApplicationContext

import qstylizer.parser


customIcons = {
    "customTabBar::closeButton.backgroundImage": f"url({app.get_resource('ui/close.png')})",
}

app = ApplicationContext()

with open(app.get_resource("app.qss"), "r") as stylesheet:
    css = qstylizer.parser.parse(stylesheet.read())
    for key, value in customIcons.items():
        property = key.split(".")
        css[property[0]][property[1]].setValue(value)
    app.app.setStyleSheet(css.toString())

the files are stored in the default fbs structure, under src/main/resource/base/ui/*.png

since I cannot use fstrings with curly braces being a part of qt. And this answer using python string formatting, but I keep getting key errors due to me having some rgba color values that also have % in it.

Since I cant use %ages or curly braces, I was thinking of building a qproperty out of get_resource, but I am not sure how. I need my qss cross compatible and cannot escape my curly braces.

My main problem is that the image wont be available when I package the application wtih fbs, using FBS freeze

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Blaine
  • 576
  • 9
  • 30
  • please provide a [mre] – eyllanesc Jun 18 '20 at 16:21
  • @eyllanesc I thought my issue was a bit straight forward but sure I added an MRE. – Blaine Jun 18 '20 at 16:29
  • there are things that are not defined, for example what is fileImporter, where are the icons, etc. – eyllanesc Jun 18 '20 at 16:32
  • my apologies, my file importer is an extension to get resource with some additional conditions. For the image file, you can use anything for now. All i care about is the file loading properly, while maintaining the qss structure – Blaine Jun 18 '20 at 16:42
  • do not delete the above from your question, instead add it to the end as I have done with my answers so that the question is understood by other readers – eyllanesc Jun 19 '20 at 14:08
  • the problem has nothing to do with FBS but you want to dynamically modify the qss – eyllanesc Jun 19 '20 at 14:09
  • my problem stems from fbs, since after releasing any file paths in the qss would not be valid, the only workaround i know of now, would be to have the qtabbar stylesheet in my qtabbar class itself. – Blaine Jun 19 '20 at 14:12
  • Why don't you use qresource? – eyllanesc Jun 19 '20 at 14:13
  • @eyllanesc because qresource demands relative path (starting with a /) and I simply cannot maintain that if package the application – Blaine Jun 19 '20 at 14:43
  • mmm, it seems that you have not handled qresource. On the contrary, the qresource generates a virtual path. Only the relative path is necessary at the time of converting the .qrc to .py. – eyllanesc Jun 19 '20 at 14:45
  • a qrc can be converted to py? i was using qresource.registerResource() ... – Blaine Jun 19 '20 at 15:25
  • 1
    plop, you have to use pyrcc5: `pyrcc5 your.qrc -o your_rc.py` – eyllanesc Jun 19 '20 at 15:34
  • Building a qrc file, running pyrcc5 and then executing my code , with background-image: url(:/closeIcon.png); is a no go. All i get is could not create pixmap from :\closeIcon.png. I followed this https://stackoverflow.com/questions/36673900/importing-resource-file-to-pyqt-code – Blaine Jun 19 '20 at 15:54
  • I do not understand you, explain yourself better. Even so it already seems that I have solved the qstylizer bug, I will send the PR in a few moments – eyllanesc Jun 19 '20 at 16:05
  • No worries, I can wait, He has merged the PR and hopefully will release soon. – Blaine Jun 19 '20 at 16:27
  • I will test it, thing is even without the properties i am getting a parse error after passing the .to_string() to setstylesheet. with the css I added in an edit to the original question. I ran the output through a text comparator against the original stylesheet, but I just cant see the issue. – Blaine Jun 19 '20 at 16:31
  • The author of qstylizer has already done the merge, so I will remove my repo, use qstylizer's gh, or wait for me to post it on pypi – eyllanesc Jun 19 '20 at 16:32

2 Answers2

2

Parsing and modifying Qt Style Sheet using the python formatting tools can be complicated, for example in your case you try to format (0, 0, 0, 50%) causing errors, so I recommend using qstylizer(python -m pip install qstylizer), so you can easily modify the properties:

QTabBar::close-button {
    padding: 0px;
    margin: 0px;
    border-radius: 2px;
    border-color: rgba(0, 0, 0, 50%);
    background-position: center center;
    background-repeat: none;
}
import functools

from fbs_runtime.application_context.PyQt5 import ApplicationContext

import qstylizer.parser


customIcons = {
    "QTabBar.closeButton.backgroundImage": f"url({app.get_resource('ui/close.png')})",
}

app = ApplicationContext()

with open(app.get_resource("app.qss"), "r") as stylesheet:
    css = qstylizer.parser.parse(stylesheet.read())
    for key, value in customIcons.items():
        obj = functools.reduce(getattr, key.split("."), css)
        obj.setValue(value)
    app.app.setStyleSheet(css.toString())

Update:

Analyzing the source code:

if key and key[0] not in ["Q", "#", "[", " "] and not key.istitle():
    key = inflection.underscore(key)

it seems that the classes are TitleCase so a possible solution is to change the name of the class to Customtabbar:

Customtabbar::close-button {
    padding: 0px;
    margin: 0px;
    border-radius: 2px;
    border-color: rgba(0, 0, 0, 50%);
    background-position: center center;
    background-repeat: none;
}
app = ApplicationContext()

customIcons = {
    "Customtabbar::close-button": {
        "background-image": f"url({app.get_resource('ui/close.png')})"
    },
}

with open(app.get_resource("app.qss"), "r") as stylesheet:
    css = qstylizer.parser.parse(stylesheet.read())
    for qcls, value in customIcons.items():
        for prop, v in value.items():
            css[qcls][prop] = v
    app.app.setStyleSheet(css.toString())

According to PEP the class names must be CapWords so I have created a fork by changing:

qstylizer/style.py

if key and key[0] not in ["Q", "#", "[", " "] and not key.istitle():

by

if key and key[0] not in ["Q", "#", "[", " "] and key != inflection.camelize(key):

Now accept the names of the classes that comply with PEP8.

CustomTabBar::close-button {
    padding: 0px;
    margin: 0px;
    border-radius: 2px;
    border-color: rgba(0, 0, 0, 50%);
    background-position: center center;
    background-repeat: none;
}
app = ApplicationContext()

customIcons = {
    "CustomTabBar::close-button": {
        "background-image": f"url({app.get_resource('ui/close.png')})"
    },
}

with open(app.get_resource("app.qss"), "r") as stylesheet:
    css = qstylizer.parser.parse(stylesheet.read())
    for qcls, value in customIcons.items():
        for prop, v in value.items():
            css[qcls][prop] = v
    app.app.setStyleSheet(css.toString())

Update2:

The PR has been accepted so it is only necessary to update the library: python -m pip install qstylizer --upgrade.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • getattr didnt work and i had to change a few things ```"tabBarPlus::closeButton.backgroundImage": f"url({app.get_resource('ui/close.png')})",``` and ```property = key.split(".") qss[property[0]][property[1]].setValue(value) ``` however, my custom classes for example customView become custom-view resulting in an stylesheet not being understood by qt (Could not parse application stylesheet) – Blaine Jun 18 '20 at 20:18
  • @Blaine that tells me that your example is not an MRE, provide and then I will try to help you – eyllanesc Jun 18 '20 at 20:40
  • I have updated my original question with a newer MRE, replicating my current issue. According to qstylesheet source code, I see that only snake case is intended to be modified but I am unsure. All I have changed in the example is that I changed QTabBar to customTabBar, following my naming convention in my code. After parsing it becomes custom-tab-bar – Blaine Jun 18 '20 at 21:00
  • @Blaine I think you discovered a bug, I am analyzing where is the cause to patch it but until now I discovered that if the class starts with Qt the problem does not happen, could you change your class to QCustomTabBar? – eyllanesc Jun 18 '20 at 21:55
  • Ah, well I wouldnt mind changing one class but provided that you always need to subclass qt classes I would have to change class names for every class I have. I think for a workaround I will look into a regex replacement on the string returned by to string – Blaine Jun 18 '20 at 22:00
  • @Blaine Update the library, the bug has been solved but you must use CustomTabBar – eyllanesc Jun 19 '20 at 03:26
  • apparently imho, its better if no sanitization is performed. qproperty-defaultAlignment is the valid property, qstylizer will santitize it to qproperty-defaultalignment. Making the stylesheet incorrect. – Blaine Jun 19 '20 at 08:57
  • I have updated my original question,and now I cant simply tell where the problem lies – Blaine Jun 19 '20 at 10:13
0

As per @eyllanesc 's suggestion, this was my solution. the rcc file,

<RCC>
  <qresource>
    <file alias="closeIcon">close.png</file>
  </qresource>
</RCC>

the shell command,

pyrcc5 -o resources.py resources.rcc

and here is the style sheet.

TabBarPlus::close-button {
    background-image: url(:/closeIcon);
    padding: 0px;
    margin: 0px;
    border-radius: 2px;
    background-position: center center;
    background-repeat: none;
}
Blaine
  • 576
  • 9
  • 30