[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pykde
Subject:    Re: Need help with custom enum properties in Qt6 Designer
From:       Ivan Sinkarenko <ivan.sinkarenko () cern ! ch>
Date:       2024-03-07 16:02:34
Message-ID: b46205d3-0eff-43a8-bdfb-9b5b5a989043 () cern ! ch
[Download RAW message or body]

Completely blind guessing here, since I'm not familiar with PyQt C++ 
side, but what if:

- Chimera::_py_enum_types contained mapping not only to C++ name, but 
also type id
- Chimera::registerPyEnum the would insert both items into the hash, 
generating type id using qRegisterMetaType()  (assuming that it's 
possible to get hold of corresponding QMetaType instance here)
- The lookup inside Chimera::parse_py_type would extract both name and 
value, and use assign "metatype = QMetaType(metaTypeId)" instead of 
"QMetaType(QMetaType::Int)"

Alternatively, it's probably possible to not track int IDs, just do:
- QMetaType::registerType() 
<https://doc.qt.io/qt-6/qmetatype.html#registerType> in 
Chimera::registerPyEnum
- Use QMetaType::fromName(cpp_qualname) in Chimera::parse_py_type

Cheers,
Ivan

On 05/03/2024 17:49, Phil Thompson wrote:
> The problem is 4.  For Qt6 PyQt has to use qMetaTypeId() to register 
> the Qt enums.  This is done statically with inline calls.  As it's 
> template based I don't know how to do the equivalent for dynamically 
> created Python enums. I suspect that this is the missing step.
>
> Phil
>
> On 04/03/2024 15:58, Ivan Sinkarenko wrote:
>> Hi Phil,
>>
>> Thanks for the info. Do you think this could be addressed in future
>> releases? My project has a frequent use of designer plugins that have
>> custom enum properties, so I would be interested to solve it.
>> Potentially I could give you some assistance?
>>
>> I've tried to investigate the flow myself a bit, here's what I found:
>>
>> 1. When setting a new value, QMetaProperty::write() is called
>> 2. It recognizes that it receives a new int value, and tries to
>> convert it to the target type
>> 3. Target type is derived from a meta type, referenced by the meta
>> property, through a call "QMetaType
>> t(mobj->d.metaTypes[data.index(mobj)]);"
>> 4. This target type for some reason has ID = 0 (i.e. UnknownType, as
>> defined by QMetaType::Type)
>> 5. Conversion fails early, rejecting UnknownType, chained through
>> QMetaProperty::write() -> QVariant::convert() ->
>> QMetaType::canConvert()
>> 6. QMetaProperty::write() returns early.
>>
>> Thanks,
>> Ivan
>>
>> On 01/03/2024 18:50, Phil Thompson wrote:
>>> On 01/03/2024 17:24, Ivan Sinkarenko wrote:
>>>> Hi everybody,
>>>>
>>>> I've seen there's been a lot happening with enums in PyQt6,
>>>> and I've managed to adapt to most of the problems, except one.
>>>> I cannot figure out how to make custom enums work in custom widgets
>>>> that are exposed to Qt Designer.
>>>>
>>>> This is my simplified code:
>>>>
>>>> ----------------------------------------------------------------------
>>>> import enum
>>>> from PyQt6 import QtDesigner, QtGui, QtWidgets, QtCore
>>>>
>>>> class MyWidget(QtWidgets.QWidget):
>>>>
>>>>     @QtCore.pyqtEnum
>>>>     class MyEnum(enum.IntEnum):
>>>>         ONE = enum.auto()
>>>>         TWO = enum.auto()
>>>>
>>>>     def __init__(self, *args, **kwargs) -> None:
>>>>         super().__init__(*args, **kwargs)
>>>>         self._prop = MyWidget.MyEnum.TWO
>>>>
>>>>     @QtCore.pyqtProperty(MyEnum)
>>>>     def prop(self):
>>>>         print(f'Getting property val {self._prop}')
>>>>         return self._prop
>>>>
>>>>     @prop.setter
>>>>     def prop(self, new_val):
>>>>         print(f'Setting new property val {new_val}')
>>>>         self._prop = new_val
>>>>
>>>> class Plugin(QtDesigner.QPyDesignerCustomWidgetPlugin):
>>>>
>>>>     def name(self):
>>>>         return "MyWidget"
>>>>
>>>>     def group(self):
>>>>         return "Buttons"
>>>>
>>>>     def isContainer(self):
>>>>         return False
>>>>
>>>>     def createWidget(self, parent):
>>>>         return MyWidget(parent)
>>>>
>>>>     def icon(self):
>>>>         return QtGui.QIcon()
>>>>
>>>>     def toolTip(self):
>>>>         return ""
>>>>
>>>>     def whatsThis(self):
>>>>         return ""
>>>>
>>>>     def includeFile(self):
>>>>         return "pyqt6_enum_designer_poc_plugin"
>>>> ----------------------------------------------------------------------
>>>>
>>>> I want an enum property to be displayed in the PropertySheet.
>>>> It's correctly represented by a combobox showing ONE and TWO as
>>>> available options.
>>>>
>>>> TWO is correctly selected by default. However, when in Property sheet
>>>> I try to set it to ONE,
>>>> as soon as I click away from there, it's reset back to TWO. In fact,
>>>> the setter does not get called,
>>>> since the message inside is never printed. (Getter message is being 
>>>> printed).
>>>>
>>>> Properties do work without issues, if they have built-in types, 
>>>> such as QColor,
>>>> or even native enums, such as Qt.Orientation.
>>>>
>>>> To try this code, you can save this code to
>>>> "pyqt6_enum_designer_poc_plugin.py" and run like so:
>>>> PYQTDESIGNERPATH=$(pwd) designer
>>>>
>>>> I use Qt Designer 6.6.2 and:
>>>> - PyQt6         6.6.1
>>>> - PyQt6-Qt6  6.6.2
>>>> - PyQt6-sip   13.6.0
>>>>
>>>> (Also tried with PyQt6-6.5.3 PyQt6-Qt6-6.5.3, same result)
>>>>
>>>> There used to be a way to make this work in PyQt5, but in PyQt6 I
>>>> tried multiple approaches without luck.
>>>> If anybody knows the correct path, that would be very appreciated!
>>>>
>>>> Thanks,
>>>> Ivan
>>>
>>> This is ringing a faint bell. I looked at it a long time ago and 
>>> found that Designer was just not making the normal call to write the 
>>> changed property value, maybe due to some sort of "optimisation". 
>>> There may be something wrong in the way that PyQt creates the 
>>> QMetaObject for the Python class but I never managed to get to the 
>>> bottom of it.
>>>
>>> Sorry for not being more helpful.
>>>
>>> Phil



[Attachment #3 (text/html)]

<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    
    Completely blind guessing here, since I'm not familiar with PyQt C++
    side, but what if:<br>
    <br>
    - <span style=""><a class="moz-txt-link-freetext" \
href="Chimera::_py_enum_types">Chimera::_py_enum_types</a></span> contained mapping  \
                not only to C++ name, but also type id<br>
    - <span style=""><a class="moz-txt-link-freetext" \
href="Chimera::registerPyEnum">Chimera::registerPyEnum</a></span> the would insert  \
both items into the hash, generating type id using  qRegisterMetaType()&nbsp; \
(assuming that it's possible to get hold of  corresponding QMetaType instance \
                here)<br>
    - The lookup inside <span style=""><a class="moz-txt-link-freetext" \
href="Chimera::parse_py_type">Chimera::parse_py_type</a></span>  would extract both \
name and value, and use assign &quot;metatype =  QMetaType(metaTypeId)&quot; instead \
of &quot;QMetaType(<span style=""><a class="moz-txt-link-freetext" \
href="QMetaType::Int">QMetaType::Int</a></span>)&quot;<br>  <br>
    Alternatively, it's probably possible to not track int IDs, just do:
    <br>
    - <a href="https://doc.qt.io/qt-6/qmetatype.html#registerType" \
moz-do-not-send="true">QMetaType::registerType()</a> in <span style=""><a \
class="moz-txt-link-freetext" \
                href="Chimera::registerPyEnum">Chimera::registerPyEnum</a></span><br>
    - Use <span style=""><a class="moz-txt-link-freetext" \
href="QMetaType::fromName(cpp_qualname)">QMetaType::fromName(cpp_qualname)</a></span> \
in <span style=""><a class="moz-txt-link-freetext" \
href="Chimera::parse_py_type">Chimera::parse_py_type</a></span><br>  <br>
    Cheers,<br>
    Ivan<br>
    <br>
    <div class="moz-cite-prefix">On 05/03/2024 17:49, Phil Thompson
      wrote:<br>
    </div>
    <blockquote type="cite" \
                cite="mid:4dd7c10738dcf84e5e30a406d29424d5@riverbankcomputing.com">The
                
      problem is 4.&nbsp; For Qt6 PyQt has to use qMetaTypeId() to register
      the Qt enums.&nbsp; This is done statically with inline calls.&nbsp; As it's
      template based I don't know how to do the equivalent for
      dynamically created Python enums. I suspect that this is the
      missing step. <br>
      <br>
      Phil <br>
      <br>
      On 04/03/2024 15:58, Ivan Sinkarenko wrote: <br>
      <blockquote type="cite">Hi Phil, <br>
        <br>
        Thanks for the info. Do you think this could be addressed in
        future <br>
        releases? My project has a frequent use of designer plugins that
        have <br>
        custom enum properties, so I would be interested to solve it. <br>
        Potentially I could give you some assistance? <br>
        <br>
        I've tried to investigate the flow myself a bit, here's what I
        found: <br>
        <br>
        1. When setting a new value, <a class="moz-txt-link-freetext" \
href="QMetaProperty::write()" moz-do-not-send="true">QMetaProperty::write()</a>  is \
called <br>  2. It recognizes that it receives a new int value, and tries to
        <br>
        convert it to the target type <br>
        3. Target type is derived from a meta type, referenced by the
        meta <br>
        property, through a call &quot;QMetaType <br>
        t(mobj-&gt;d.metaTypes[data.index(mobj)]);&quot; <br>
        4. This target type for some reason has ID = 0 (i.e.
        UnknownType, as <br>
        defined by <a class="moz-txt-link-freetext" href="QMetaType::Type" \
moz-do-not-send="true">QMetaType::Type</a>)  <br>
        5. Conversion fails early, rejecting UnknownType, chained
        through <br>
        <a class="moz-txt-link-freetext" href="QMetaProperty::write()" \
moz-do-not-send="true">QMetaProperty::write()</a> -&gt; <a \
class="moz-txt-link-freetext" href="QVariant::convert()" \
                moz-do-not-send="true">QVariant::convert()</a> -&gt; <br>
        <a class="moz-txt-link-freetext" href="QMetaType::canConvert()" \
                moz-do-not-send="true">QMetaType::canConvert()</a> <br>
        6. <a class="moz-txt-link-freetext" href="QMetaProperty::write()" \
moz-do-not-send="true">QMetaProperty::write()</a>  returns early. <br>
        <br>
        Thanks, <br>
        Ivan <br>
        <br>
        On 01/03/2024 18:50, Phil Thompson wrote: <br>
        <blockquote type="cite">On 01/03/2024 17:24, Ivan Sinkarenko
          wrote: <br>
          <blockquote type="cite">Hi everybody, <br>
            <br>
            I've seen there's been a lot happening with enums in PyQt6,
            <br>
            and I've managed to adapt to most of the problems, except
            one. <br>
            I cannot figure out how to make custom enums work in custom
            widgets <br>
            that are exposed to Qt Designer. <br>
            <br>
            This is my simplified code: <br>
            <br>
---------------------------------------------------------------------- <br>
            import enum <br>
            from PyQt6 import QtDesigner, QtGui, QtWidgets, QtCore <br>
            <br>
            class MyWidget(QtWidgets.QWidget): <br>
            <br>
            &nbsp;&nbsp;&nbsp; @QtCore.pyqtEnum <br>
            &nbsp;&nbsp;&nbsp; class MyEnum(enum.IntEnum): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ONE = enum.auto() <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TWO = enum.auto() <br>
            <br>
            &nbsp;&nbsp;&nbsp; def __init__(self, *args, **kwargs) -&gt; None: <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super().__init__(*args, \
                **kwargs) <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self._prop = \
MyWidget.MyEnum.TWO <br>  <br>
            &nbsp;&nbsp;&nbsp; @QtCore.pyqtProperty(MyEnum) <br>
            &nbsp;&nbsp;&nbsp; def prop(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print(f'Getting property val \
                {self._prop}') <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self._prop <br>
            <br>
            &nbsp;&nbsp;&nbsp; @prop.setter <br>
            &nbsp;&nbsp;&nbsp; def prop(self, new_val): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print(f'Setting new property \
                val {new_val}') <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self._prop = new_val <br>
            <br>
            class Plugin(QtDesigner.QPyDesignerCustomWidgetPlugin): <br>
            <br>
            &nbsp;&nbsp;&nbsp; def name(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &quot;MyWidget&quot; \
<br>  <br>
            &nbsp;&nbsp;&nbsp; def group(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &quot;Buttons&quot; \
<br>  <br>
            &nbsp;&nbsp;&nbsp; def isContainer(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return False <br>
            <br>
            &nbsp;&nbsp;&nbsp; def createWidget(self, parent): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return MyWidget(parent) <br>
            <br>
            &nbsp;&nbsp;&nbsp; def icon(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return QtGui.QIcon() <br>
            <br>
            &nbsp;&nbsp;&nbsp; def toolTip(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &quot;&quot; <br>
            <br>
            &nbsp;&nbsp;&nbsp; def whatsThis(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &quot;&quot; <br>
            <br>
            &nbsp;&nbsp;&nbsp; def includeFile(self): <br>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return \
                &quot;pyqt6_enum_designer_poc_plugin&quot; <br>
---------------------------------------------------------------------- <br>
            <br>
            I want an enum property to be displayed in the
            PropertySheet. <br>
            It's correctly represented by a combobox showing ONE and TWO
            as <br>
            available options. <br>
            <br>
            TWO is correctly selected by default. However, when in
            Property sheet <br>
            I try to set it to ONE, <br>
            as soon as I click away from there, it's reset back to TWO.
            In fact, <br>
            the setter does not get called, <br>
            since the message inside is never printed. (Getter message
            is being printed). <br>
            <br>
            Properties do work without issues, if they have built-in
            types, such as QColor, <br>
            or even native enums, such as Qt.Orientation. <br>
            <br>
            To try this code, you can save this code to <br>
            &quot;pyqt6_enum_designer_poc_plugin.py&quot; and run like so: <br>
            PYQTDESIGNERPATH=$(pwd) designer <br>
            <br>
            I use Qt Designer 6.6.2 and: <br>
            - PyQt6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.6.1 <br>
            - PyQt6-Qt6&nbsp; 6.6.2 <br>
            - PyQt6-sip&nbsp;&nbsp; 13.6.0 <br>
            <br>
            (Also tried with PyQt6-6.5.3 PyQt6-Qt6-6.5.3, same result) <br>
            <br>
            There used to be a way to make this work in PyQt5, but in
            PyQt6 I <br>
            tried multiple approaches without luck. <br>
            If anybody knows the correct path, that would be very
            appreciated! <br>
            <br>
            Thanks, <br>
            Ivan <br>
          </blockquote>
          <br>
          This is ringing a faint bell. I looked at it a long time ago
          and found that Designer was just not making the normal call to
          write the changed property value, maybe due to some sort of
          &quot;optimisation&quot;. There may be something wrong in the way that
          PyQt creates the QMetaObject for the Python class but I never
          managed to get to the bottom of it. <br>
          <br>
          Sorry for not being more helpful. <br>
          <br>
          Phil <br>
        </blockquote>
      </blockquote>
    </blockquote>
    <br>
    <br>
    <br>
  </body>
</html>



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic