<div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif;font-size:small">If you are going to put the Icon class in a module why don't just use module level __getattr__?<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Jul 7, 2023 at 10:07 AM Maurizio Berti <<a href="mailto:maurizio.berti@gmail.com">maurizio.berti@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>[Addendum]</div><div><br></div><div>I just realized that my idea can be furtherly improved in a very interesting way, so, thank you Christian and everybody else.</div><div><br></div><div>I suspect that almost anybody reading this has mixed feelings about the verbosity, readability and length of Qt namings (especially after the Enum introduction).</div><div><br></div><div>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 <span style="font-family:monospace">fromTheme()</span> syntax.</div><div><span style="font-family:monospace"><br></span></div><div><span style="font-family:monospace">_camelCaseSplit = re.compile(r'.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)')<br>class MetaIcon(type):<br> _icons = {}<br> def __getattr__(self, attr):<br> if attr in self._icons:<br> return self._icons[attr]</span></div><div><span style="font-family:monospace"> match = _camelCaseSplit.finditer(attr)</span></div><div><span style="font-family:monospace"> if match:<br> self._icons[attr] = icon = QIcon.fromTheme(</span></div><div><span style="font-family:monospace"> '-'.join(m.group(0).lower() for m in match))</span></div><div><span style="font-family:monospace"> return icon</span></div><div><span style="font-family:monospace"> return QIcon()<br></span></div><div><span style="font-family:monospace"><br></span></div><div><span style="font-family:monospace">class Icon(metaclass=MetaIcon): pass</span><br></div><div><br></div><div>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.<br></div><div><br></div><div>The above allows something like the following:</div><div><br></div><div><span style="font-family:monospace">undoButton = QPushButton(Icon.EditUndo, 'Undo') # as in: QIcon.fromTheme('edit-undo')<br></span></div><div><span style="font-family:monospace">quitAction = QAction(Icon.ApplicationExit, 'Quit') # or: QIcon.fromTheme('application-exit')<br></span></div><div><br></div><div>If anybody has further insights or suggestions, I'll be glad to read them.</div><div><br></div><div>Best regards,</div><div>MaurizioB<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Il giorno ven 7 lug 2023 alle ore 02:46 Maurizio Berti <<a href="mailto:maurizio.berti@gmail.com" target="_blank">maurizio.berti@gmail.com</a>> ha scritto:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>The main issue with commonly used resources like QIcon is that they require an existing QGuiApplication instance.</div><div><br></div><div>When using a single script with the canonical <span style="font-family:monospace">if __name__ == '__main__':</span> 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.</div><div><br></div><div>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.</div><div><br></div><div>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.<br></div><br><div>The concept is similar to that of enums, with the difference that the returned value is actually a QIcon.</div><div><div>Using an object reference is required since the "attribute" is not static, but it's returned at runtime.<br></div><div><br></div>Since we can assume that an icon will only be required from a QWidget instance, using this approach should be safe enough.<br></div><div><br></div><div>Let's suppose that this is the content of a <span style="font-family:monospace">MyIcons.py</span> script:<br></div><div><br></div><div><span style="font-family:monospace">class MetaIcon(type):</span></div><div><span style="font-family:monospace"> _names = {</span></div><div><span style="font-family:monospace"> PAUSE: 'media-playback-pause',</span></div><div><span style="font-family:monospace"> ...<br></span></div><div><span style="font-family:monospace"> }</span></div><div><span style="font-family:monospace"> def __getattr__(self, attr):</span></div><div><span style="font-family:monospace"> if attr in self._names:</span></div><div><span style="font-family:monospace"> return QIcon.fromTheme(self._names[attr])</span></div><div><span style="font-family:monospace"> return QIcon()</span></div><div><span style="font-family:monospace"><br></span></div><div><span style="font-family:monospace">class Icon(metaclass=MetaIcon): pass</span></div><div><br></div><div>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 <span style="font-family:monospace">Icon</span> class and override <span style="font-family:monospace">__getattr__</span>, since it only works on the instance. Doing that in the metaclass is fine, because it works on the "instance" of the type.<br></div><div><br></div><div>Then, you can just import Icon from that module, and `Icon.PAUSE` will return a proper QIcon.</div><div><br></div><div><span style="font-family:monospace">from MyIcons import Icon</span></div><div><span style="font-family:monospace">...</span></div><div><span style="font-family:monospace">button = QPushButton(icon=Icon.PAUSE)</span></div><div><br></div><div>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).</div><div><br></div><div>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 <a class="gmail_plusreply" id="m_-2578950056311741071m_-7691536371179284612plusReplyChip-1">@cached_property</a> decorator or create a null class attribute, then overwrite it at the class level within the instance init:</div><div><span style="font-family:monospace"><br></span></div><div><span style="font-family:monospace">class MyPlayer(QWidget):</span></div><div><span style="font-family:monospace"> icons = None<br></span></div><div><span style="font-family:monospace"> def __init__(self, *args, **kwargs):</span></div><div><span style="font-family:monospace"> super().__init__(*args, **kwargs)</span></div><div><span style="font-family:monospace"> if not self.icons:</span></div><div><span style="font-family:monospace"> self.icons.__class__.icons = [Icons.PLAY, Icons.PAUSE]</span></div><div><span style="font-family:monospace"> ...</span></div><div><span style="font-family:monospace"></span></div><div><span style="font-family:monospace"> def playerStateChanged(self, state):<br></span></div><div><span style="font-family:monospace"> self.playButton.setIcon(self.icons[state])<br></span></div><div><br></div><div>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.<br></div><div><br></div><div>Best regards,</div><div>MaurizioB<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Il giorno gio 6 lug 2023 alle ore 12:15 <<a href="mailto:c.buhtz@posteo.jp" target="_blank">c.buhtz@posteo.jp</a>> ha scritto:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hello,<br>
<br>
I do use PyQt5.<br>
<br>
I have a module "icon.py" in my application containing lines with <br>
constants like this:<br>
<br>
PAUSE = QIcon.fromTheme('media-playback-pause')<br>
<br>
Somewhere else in my application I do use "icon.PAUSE". But before doing <br>
this I have to "import icon" of course.<br>
<br>
The problem is that I'm not able to put the import statement in the <br>
beginning of the py files like you usually do it with imports. I have to <br>
put it e.g. in the __init__() method of my QMainWindow derived classed <br>
and after QApplication() was instanciated. I understand why it is that <br>
way.<br>
<br>
But maybe you can explain how you do manage icons in your application?<br>
<br>
Kind<br>
Christian<br>
</blockquote></div><br clear="all"><br><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature">È difficile avere una convinzione precisa quando si parla delle ragioni del cuore. - "Sostiene Pereira", Antonio Tabucchi<br><a href="http://www.jidesk.net" target="_blank">http://www.jidesk.net</a></div>
</blockquote></div><br clear="all"><br><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature">È difficile avere una convinzione precisa quando si parla delle ragioni del cuore. - "Sostiene Pereira", Antonio Tabucchi<br><a href="http://www.jidesk.net" target="_blank">http://www.jidesk.net</a></div>
</blockquote></div>