Manage QIcon instances on a application global level

Charles peacech at gmail.com
Fri Jul 7 04:32:10 BST 2023


If you are going to put the Icon class in a module why don't just use
module level __getattr__?

On Fri, Jul 7, 2023 at 10:07 AM Maurizio Berti <maurizio.berti at gmail.com>
wrote:

> [Addendum]
>
> I just realized that my idea can be furtherly improved in a very
> interesting way, so, thank you Christian and everybody else.
>
> I suspect that almost anybody reading this has mixed feelings about the
> verbosity, readability and length of Qt namings (especially after the Enum
> introduction).
>
> Saving some characters could make our "readabilitiness" simpler, so here's
> my proposal: a basic camelCase regex that creates a syntax valid for the
> standard fromTheme() syntax.
>
> _camelCaseSplit =
> re.compile(r'.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)')
> class MetaIcon(type):
>     _icons = {}
>     def __getattr__(self, attr):
>         if attr in self._icons:
>             return self._icons[attr]
>         match = _camelCaseSplit.finditer(attr)
>         if match:
>             self._icons[attr] = icon = QIcon.fromTheme(
>                 '-'.join(m.group(0).lower() for m in match))
>             return icon
>         return QIcon()
>
> class Icon(metaclass=MetaIcon): pass
>
> Note that I'm not a regex expert, I suppose that there could be better
> syntaxes for this case (especially considering single letters, such as
> those used in MIME types), but this is mainly a proof of concept.
>
> The above allows something like the following:
>
> undoButton = QPushButton(Icon.EditUndo, 'Undo') # as in:
> QIcon.fromTheme('edit-undo')
> quitAction = QAction(Icon.ApplicationExit, 'Quit') # or:
> QIcon.fromTheme('application-exit')
>
> If anybody has further insights or suggestions, I'll be glad to read them.
>
> Best regards,
> MaurizioB
>
> Il giorno ven 7 lug 2023 alle ore 02:46 Maurizio Berti <
> maurizio.berti at gmail.com> ha scritto:
>
>> The main issue with commonly used resources like QIcon is that they
>> require an existing QGuiApplication instance.
>>
>> When using a single script with the canonical if __name__ == '__main__':
>> that's not a real problem, since the module can be loaded within that block
>> and it can be accessed from outside it due to the global scope.
>>
>> Unfortunately, this is obviously not possible for modules that are not
>> the main script but still use those QIcons and are imported before the
>> QApplication construction.
>>
>> A possible solution could be to create a custom metaclass that actually
>> returns a QIcon and a class based on it that implements its "enum", then
>> import that class instead.
>>
>> The concept is similar to that of enums, with the difference that the
>> returned value is actually a QIcon.
>> Using an object reference is required since the "attribute" is not
>> static, but it's returned at runtime.
>>
>> Since we can assume that an icon will only be required from a QWidget
>> instance, using this approach should be safe enough.
>>
>> Let's suppose that this is the content of a MyIcons.py script:
>>
>> class MetaIcon(type):
>>     _names = {
>>         PAUSE: 'media-playback-pause',
>>         ...
>>     }
>>     def __getattr__(self, attr):
>>         if attr in self._names:
>>             return QIcon.fromTheme(self._names[attr])
>>         return QIcon()
>>
>> class Icon(metaclass=MetaIcon): pass
>>
>> The above is necessary because an unknown class attribute has to be that
>> of a "class of the class" (aka, a type): you cannot just create a basic
>> Icon class and override __getattr__, since it only works on the
>> instance. Doing that in the metaclass is fine, because it works on the
>> "instance" of the type.
>>
>> Then, you can just import Icon from that module, and `Icon.PAUSE` will
>> return a proper QIcon.
>>
>> from MyIcons import Icon
>> ...
>> button = QPushButton(icon=Icon.PAUSE)
>>
>> Note that the constructor requirement of QIcon (as it goes for QPixmap)
>> remains: it shouldn't be created without an existing QApplication instance;
>> this means that you should not try to create class attributes using the
>> "icon enum" above (for example, to create class defaults).
>>
>> If you want to achieve some level of optimization (to avoid unnecessary
>> duplicates of QIcon instances - hence, further QIconEngine instances) or
>> code tidiness, use the @cached_property decorator or create a null class
>> attribute, then overwrite it at the class level within the instance init:
>>
>> class MyPlayer(QWidget):
>>     icons = None
>>     def __init__(self, *args, **kwargs):
>>         super().__init__(*args, **kwargs)
>>         if not self.icons:
>>             self.icons.__class__.icons = [Icons.PLAY, Icons.PAUSE]
>>     ...
>>     def playerStateChanged(self, state):
>>         self.playButton.setIcon(self.icons[state])
>>
>> The above will also work for deeper inheritance levels as long as the
>> super __init__ is called first, because the __init__ of the super class
>> will automatically make the attribute valid.
>>
>> Best regards,
>> MaurizioB
>>
>> Il giorno gio 6 lug 2023 alle ore 12:15 <c.buhtz at posteo.jp> ha scritto:
>>
>>> Hello,
>>>
>>> I do use PyQt5.
>>>
>>> I have a module "icon.py" in my application containing lines with
>>> constants like this:
>>>
>>>      PAUSE = QIcon.fromTheme('media-playback-pause')
>>>
>>> Somewhere else in my application I do use "icon.PAUSE". But before doing
>>> this I have to "import icon" of course.
>>>
>>> The problem is that I'm not able to put the import statement in the
>>> beginning of the py files like you usually do it with imports. I have to
>>> put it e.g. in the __init__() method of my QMainWindow derived classed
>>> and after QApplication() was instanciated. I understand why it is that
>>> way.
>>>
>>> But maybe you can explain how you do manage icons in your application?
>>>
>>> Kind
>>> Christian
>>>
>>
>>
>> --
>> È difficile avere una convinzione precisa quando si parla delle ragioni
>> del cuore. - "Sostiene Pereira", Antonio Tabucchi
>> http://www.jidesk.net
>>
>
>
> --
> È difficile avere una convinzione precisa quando si parla delle ragioni
> del cuore. - "Sostiene Pereira", Antonio Tabucchi
> http://www.jidesk.net
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20230707/355b50cf/attachment-0001.htm>


More information about the PyQt mailing list