1 // This is the implementation of pyqtProperty.
2 //
3 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4 //
5 // This file is part of PyQt5.
6 //
7 // This file may be used under the terms of the GNU General Public License
8 // version 3.0 as published by the Free Software Foundation and appearing in
9 // the file LICENSE included in the packaging of this file.  Please review the
10 // following information to ensure the GNU General Public License version 3.0
11 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 //
13 // If you do not wish to use this file under the terms of the GPL version 3.0
14 // then you may purchase a commercial license.  For more information contact
15 // info@riverbankcomputing.com.
16 //
17 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19 
20 
21 #include <Python.h>
22 #include <structmember.h>
23 
24 #include "qpycore_chimera.h"
25 #include "qpycore_pyqtproperty.h"
26 #include "qpycore_pyqtsignal.h"
27 
28 
29 // The type object.
30 PyTypeObject *qpycore_pyqtProperty_TypeObject;
31 
32 
33 // Forward declarations.
34 extern "C" {
35 static PyObject *pyqtProperty_getter(PyObject *self, PyObject *getter);
36 static PyObject *pyqtProperty_setter(PyObject *self, PyObject *setter);
37 static PyObject *pyqtProperty_deleter(PyObject *self, PyObject *deleter);
38 static PyObject *pyqtProperty_reset(PyObject *self, PyObject *reset);
39 static PyObject *pyqtProperty_descr_get(PyObject *self, PyObject *obj,
40         PyObject *);
41 static int pyqtProperty_descr_set(PyObject *self, PyObject *obj,
42         PyObject *value);
43 static PyObject *pyqtProperty_call(PyObject *self, PyObject *args,
44         PyObject *kwds);
45 static void pyqtProperty_dealloc(PyObject *self);
46 static int pyqtProperty_init(PyObject *self, PyObject *args, PyObject *kwds);
47 static int pyqtProperty_traverse(PyObject *self, visitproc visit, void *arg);
48 }
49 
50 static qpycore_pyqtProperty *clone(qpycore_pyqtProperty *orig);
51 static PyObject *getter_docstring(PyObject *getter);
52 
53 
54 // Doc-strings.
55 PyDoc_STRVAR(pyqtProperty_doc,
56 "pyqtProperty(type, fget=None, fset=None, freset=None, fdel=None, doc=None,\n"
57 "        designable=True, scriptable=True, stored=True, user=False,\n"
58 "        constant=False, final=False, notify=None,\n"
59 "        revision=0) -> property attribute\n"
60 "\n"
61 "type is the type of the property.  It is either a type object or a string\n"
62 "that is the name of a C++ type.\n"
63 "freset is a function for resetting an attribute to its default value.\n"
64 "designable sets the DESIGNABLE flag (the default is True for writable\n"
65 "properties and False otherwise).\n"
66 "scriptable sets the SCRIPTABLE flag.\n"
67 "stored sets the STORED flag.\n"
68 "user sets the USER flag.\n"
69 "constant sets the CONSTANT flag.\n"
70 "final sets the FINAL flag.\n"
71 "notify is the NOTIFY signal.\n"
72 "revision is the REVISION.\n"
73 "The other parameters are the same as those required by the standard Python\n"
74 "property type.  Properties defined using pyqtProperty behave as both Python\n"
75 "and Qt properties."
76 "\n"
77 "Decorators can be used to define new properties or to modify existing ones.");
78 
79 PyDoc_STRVAR(getter_doc, "Descriptor to change the getter on a property.");
80 PyDoc_STRVAR(setter_doc, "Descriptor to change the setter on a property.");
81 PyDoc_STRVAR(deleter_doc, "Descriptor to change the deleter on a property.");
82 PyDoc_STRVAR(reset_doc, "Descriptor to change the reset on a property.");
83 
84 
85 // Define the attributes.
86 static PyMemberDef pyqtProperty_members[] = {
87     {const_cast<char *>("type"), T_OBJECT,
88             offsetof(qpycore_pyqtProperty, pyqtprop_type), READONLY, 0},
89     {const_cast<char *>("fget"), T_OBJECT,
90             offsetof(qpycore_pyqtProperty, pyqtprop_get), READONLY, 0},
91     {const_cast<char *>("fset"), T_OBJECT,
92             offsetof(qpycore_pyqtProperty, pyqtprop_set), READONLY, 0},
93     {const_cast<char *>("fdel"), T_OBJECT,
94             offsetof(qpycore_pyqtProperty, pyqtprop_del), READONLY, 0},
95     {const_cast<char *>("freset"), T_OBJECT,
96             offsetof(qpycore_pyqtProperty, pyqtprop_reset), READONLY, 0},
97     {const_cast<char *>("__doc__"), T_OBJECT,
98             offsetof(qpycore_pyqtProperty, pyqtprop_doc), READONLY, 0},
99     {0, 0, 0, 0, 0}
100 };
101 
102 
103 // Define the methods.
104 static PyMethodDef pyqtProperty_methods[] = {
105     {"getter", pyqtProperty_getter, METH_O, getter_doc},
106     {"read", pyqtProperty_getter, METH_O, getter_doc},
107     {"setter", pyqtProperty_setter, METH_O, setter_doc},
108     {"write", pyqtProperty_setter, METH_O, setter_doc},
109     {"deleter", pyqtProperty_deleter, METH_O, deleter_doc},
110     {"reset", pyqtProperty_reset, METH_O, reset_doc},
111     {0, 0, 0, 0}
112 };
113 
114 
115 #if PY_VERSION_HEX >= 0x03040000
116 // Define the slots.
117 static PyType_Slot qpycore_pyqtProperty_Slots[] = {
118     {Py_tp_new,         (void *)PyType_GenericNew},
119     {Py_tp_alloc,       (void *)PyType_GenericAlloc},
120     {Py_tp_init,        (void *)pyqtProperty_init},
121     {Py_tp_dealloc,     (void *)pyqtProperty_dealloc},
122     {Py_tp_free,        (void *)PyObject_GC_Del},
123     {Py_tp_call,        (void *)pyqtProperty_call},
124     {Py_tp_getattro,    (void *)PyObject_GenericGetAttr},
125     {Py_tp_doc,         (void *)pyqtProperty_doc},
126     {Py_tp_traverse,    (void *)pyqtProperty_traverse},
127     {Py_tp_descr_get,   (void *)pyqtProperty_descr_get},
128     {Py_tp_descr_set,   (void *)pyqtProperty_descr_set},
129     {Py_tp_methods,     pyqtProperty_methods},
130     {Py_tp_members,     pyqtProperty_members},
131     {0,                 0}
132 };
133 
134 
135 // Define the type.
136 static PyType_Spec qpycore_pyqtProperty_Spec = {
137     "PyQt5.QtCore.pyqtProperty",
138     sizeof (qpycore_pyqtProperty),
139     0,
140     Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC|Py_TPFLAGS_BASETYPE,
141     qpycore_pyqtProperty_Slots
142 };
143 #else
144 // Define the type.
145 static PyTypeObject qpycore_pyqtProperty_Type = {
146     PyVarObject_HEAD_INIT(NULL, 0)
147 #if PY_VERSION_HEX >= 0x02050000
148     "PyQt5.QtCore.pyqtProperty",
149 #else
150     const_cast<char *>("PyQt5.QtCore.pyqtProperty"),
151 #endif
152     sizeof (qpycore_pyqtProperty),
153     0,
154     pyqtProperty_dealloc,
155     0,
156     0,
157     0,
158     0,
159     0,
160     0,
161     0,
162     0,
163     0,
164     pyqtProperty_call,
165     0,
166     PyObject_GenericGetAttr,
167     0,
168     0,
169     Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC|Py_TPFLAGS_BASETYPE,
170     pyqtProperty_doc,
171     pyqtProperty_traverse,
172     0,
173     0,
174     0,
175     0,
176     0,
177     pyqtProperty_methods,
178     pyqtProperty_members,
179     0,
180     0,
181     0,
182     pyqtProperty_descr_get,
183     pyqtProperty_descr_set,
184     0,
185     pyqtProperty_init,
186     PyType_GenericAlloc,
187     PyType_GenericNew,
188     PyObject_GC_Del,
189     0,
190     0,
191     0,
192     0,
193     0,
194     0,
195     0,
196     0,
197 #if PY_VERSION_HEX >= 0x03040000
198     0,
199 #endif
200 };
201 #endif
202 
203 
204 // This is the sequence number to allocate to the next PyQt property to be
205 // defined.  This ensures that properties appear in the QMetaObject in the same
206 // order that they are defined in Python.
207 static uint pyqtprop_sequence_nr = 0;
208 
209 
210 // The pyqtProperty dealloc function.
pyqtProperty_dealloc(PyObject * self)211 static void pyqtProperty_dealloc(PyObject *self)
212 {
213     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
214 
215     PyObject_GC_UnTrack(self);
216 
217     Py_XDECREF(pp->pyqtprop_get);
218     Py_XDECREF(pp->pyqtprop_set);
219     Py_XDECREF(pp->pyqtprop_del);
220     Py_XDECREF(pp->pyqtprop_doc);
221     Py_XDECREF(pp->pyqtprop_reset);
222     Py_XDECREF(pp->pyqtprop_notify);
223     Py_XDECREF(pp->pyqtprop_type);
224 
225     delete pp->pyqtprop_parsed_type;
226 
227 #if PY_VERSION_HEX >= 0x03040000
228     ((destructor)PyType_GetSlot(Py_TYPE(self), Py_tp_free))(self);
229 #else
230     Py_TYPE(self)->tp_free(self);
231 #endif
232 }
233 
234 
235 // The descriptor getter.
pyqtProperty_descr_get(PyObject * self,PyObject * obj,PyObject *)236 static PyObject *pyqtProperty_descr_get(PyObject *self, PyObject *obj,
237         PyObject *)
238 {
239     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
240 
241     if (!obj || obj == Py_None)
242     {
243         Py_INCREF(self);
244         return self;
245     }
246 
247     if (!pp->pyqtprop_get)
248     {
249         PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
250         return 0;
251     }
252 
253     return PyObject_CallFunction(pp->pyqtprop_get, const_cast<char *>("(O)"),
254             obj);
255 }
256 
257 
258 // The descriptor setter.
pyqtProperty_descr_set(PyObject * self,PyObject * obj,PyObject * value)259 static int pyqtProperty_descr_set(PyObject *self, PyObject *obj,
260         PyObject *value)
261 {
262     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
263     PyObject *func, *res;
264 
265     func = (value ? pp->pyqtprop_set : pp->pyqtprop_del);
266 
267     if (!func)
268     {
269         PyErr_SetString(PyExc_AttributeError,
270                 (value ? "can't set attribute" : "can't delete attribute"));
271         return -1;
272     }
273 
274     if (value)
275         res = PyObject_CallFunction(func, const_cast<char *>("(OO)"), obj,
276                 value);
277     else
278         res = PyObject_CallFunction(func, const_cast<char *>("(O)"), obj);
279 
280     if (!res)
281         return -1;
282 
283     Py_DECREF(res);
284     return 0;
285 }
286 
287 
288 // The pyqtProperty traverse function.
pyqtProperty_traverse(PyObject * self,visitproc visit,void * arg)289 static int pyqtProperty_traverse(PyObject *self, visitproc visit, void *arg)
290 {
291     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
292     int vret;
293 
294     if (pp->pyqtprop_get)
295     {
296         vret = visit(pp->pyqtprop_get, arg);
297 
298         if (vret != 0)
299             return vret;
300     }
301 
302     if (pp->pyqtprop_set)
303     {
304         vret = visit(pp->pyqtprop_set, arg);
305 
306         if (vret != 0)
307             return vret;
308     }
309 
310     if (pp->pyqtprop_del)
311     {
312         vret = visit(pp->pyqtprop_del, arg);
313 
314         if (vret != 0)
315             return vret;
316     }
317 
318     if (pp->pyqtprop_doc)
319     {
320         vret = visit(pp->pyqtprop_doc, arg);
321 
322         if (vret != 0)
323             return vret;
324     }
325 
326     if (pp->pyqtprop_reset)
327     {
328         vret = visit(pp->pyqtprop_reset, arg);
329 
330         if (vret != 0)
331             return vret;
332     }
333 
334     if (pp->pyqtprop_notify)
335     {
336         vret = visit(pp->pyqtprop_notify, arg);
337 
338         if (vret != 0)
339             return vret;
340     }
341 
342     if (pp->pyqtprop_type)
343     {
344         vret = visit(pp->pyqtprop_type, arg);
345 
346         if (vret != 0)
347             return vret;
348     }
349 
350     return 0;
351 }
352 
353 
354 // The pyqtProperty init function.
pyqtProperty_init(PyObject * self,PyObject * args,PyObject * kwds)355 static int pyqtProperty_init(PyObject *self, PyObject *args, PyObject *kwds)
356 {
357     PyObject *type, *get = 0, *set = 0, *reset = 0, *del = 0, *doc = 0,
358             *notify = 0;
359     int scriptable = 1, stored = 1, user = 0, constant = 0, final = 0;
360     int designable = 1, revision = 0;
361     static const char *kwlist[] = {"type", "fget", "fset", "freset", "fdel",
362             "doc", "designable", "scriptable", "stored", "user", "constant",
363             "final", "notify", "revision", 0};
364     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
365 
366     pp->pyqtprop_sequence = pyqtprop_sequence_nr++;
367 
368     if (!PyArg_ParseTupleAndKeywords(args, kwds,
369             "O|OOOOOiiiiiiO!i:pyqtProperty",
370             const_cast<char **>(kwlist), &type, &get, &set, &reset, &del, &doc,
371             &designable, &scriptable, &stored, &user, &constant, &final,
372             qpycore_pyqtSignal_TypeObject, &notify, &revision))
373         return -1;
374 
375     if (get == Py_None)
376         get = 0;
377 
378     if (set == Py_None)
379         set = 0;
380 
381     if (del == Py_None)
382         del = 0;
383 
384     if (reset == Py_None)
385         reset = 0;
386 
387     if (notify == Py_None)
388         notify = 0;
389 
390     // Parse the type.
391     const Chimera *ptype = Chimera::parse(type);
392 
393     if (!ptype)
394     {
395         Chimera::raiseParseException(type, "a property");
396         return -1;
397     }
398 
399     pp->pyqtprop_parsed_type = ptype;
400 
401     Py_XINCREF(get);
402     Py_XINCREF(set);
403     Py_XINCREF(del);
404     Py_XINCREF(doc);
405     Py_XINCREF(reset);
406     Py_XINCREF(notify);
407     Py_INCREF(type);
408 
409     // If no docstring was given try the getter.
410     if (!doc || doc == Py_None)
411     {
412         PyObject *getter_doc = getter_docstring(get);
413 
414         if (getter_doc)
415         {
416             Py_XDECREF(doc);
417             doc = getter_doc;
418         }
419     }
420 
421     pp->pyqtprop_get = get;
422     pp->pyqtprop_set = set;
423     pp->pyqtprop_del = del;
424     pp->pyqtprop_doc = doc;
425     pp->pyqtprop_reset = reset;
426     pp->pyqtprop_notify = notify;
427     pp->pyqtprop_type = type;
428 
429     // ResolveEditable is always set.
430     unsigned flags = 0x00080000;
431 
432     if (designable)
433         flags |= 0x00001000;
434 
435     if (scriptable)
436         flags |= 0x00004000;
437 
438     if (stored)
439         flags |= 0x00010000;
440 
441     if (user)
442         flags |= 0x00100000;
443 
444     if (constant)
445         flags |= 0x00000400;
446 
447     if (final)
448         flags |= 0x00000800;
449 
450     pp->pyqtprop_flags = flags;
451 
452     pp->pyqtprop_revision = revision;
453 
454     return 0;
455 }
456 
457 
458 // Calling the property is a shorthand way of changing the getter.
pyqtProperty_call(PyObject * self,PyObject * args,PyObject * kwds)459 static PyObject *pyqtProperty_call(PyObject *self, PyObject *args,
460         PyObject *kwds)
461 {
462     PyObject *get;
463     static const char *kwlist[] = {"fget", 0};
464 
465     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:pyqtProperty",
466             const_cast<char **>(kwlist), &get))
467         return 0;
468 
469     return pyqtProperty_getter(self, get);
470 }
471 
472 
473 // Return a copy of the property with a different getter.
pyqtProperty_getter(PyObject * self,PyObject * getter)474 static PyObject *pyqtProperty_getter(PyObject *self, PyObject *getter)
475 {
476     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
477 
478     pp = clone(pp);
479 
480     if (pp)
481     {
482         Py_XDECREF(pp->pyqtprop_get);
483 
484         if (getter == Py_None)
485             getter = 0;
486         else
487             Py_INCREF(getter);
488 
489         pp->pyqtprop_get = getter;
490 
491         // Use the getter's docstring if it has one.
492         PyObject *getter_doc = getter_docstring(getter);
493 
494         if (getter_doc)
495         {
496             Py_XDECREF(pp->pyqtprop_doc);
497             pp->pyqtprop_doc = getter_doc;
498         }
499     }
500 
501     return (PyObject *)pp;
502 }
503 
504 
505 // Return a copy of the property with a different setter.
pyqtProperty_setter(PyObject * self,PyObject * setter)506 static PyObject *pyqtProperty_setter(PyObject *self, PyObject *setter)
507 {
508     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
509 
510     pp = clone(pp);
511 
512     if (pp)
513     {
514         Py_XDECREF(pp->pyqtprop_set);
515 
516         if (setter == Py_None)
517             setter = 0;
518         else
519             Py_INCREF(setter);
520 
521         pp->pyqtprop_set = setter;
522     }
523 
524     return (PyObject *)pp;
525 }
526 
527 
528 // Return a copy of the property with a different deleter.
pyqtProperty_deleter(PyObject * self,PyObject * deleter)529 static PyObject *pyqtProperty_deleter(PyObject *self, PyObject *deleter)
530 {
531     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
532 
533     pp = clone(pp);
534 
535     if (pp)
536     {
537         Py_XDECREF(pp->pyqtprop_del);
538 
539         if (deleter == Py_None)
540             deleter = 0;
541         else
542             Py_INCREF(deleter);
543 
544         pp->pyqtprop_del = deleter;
545     }
546 
547     return (PyObject *)pp;
548 }
549 
550 
551 // Return a copy of the property with a different reset.
pyqtProperty_reset(PyObject * self,PyObject * reset)552 static PyObject *pyqtProperty_reset(PyObject *self, PyObject *reset)
553 {
554     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
555 
556     pp = clone(pp);
557 
558     if (pp)
559     {
560         Py_XDECREF(pp->pyqtprop_reset);
561 
562         if (reset == Py_None)
563             reset = 0;
564         else
565             Py_INCREF(reset);
566 
567         pp->pyqtprop_reset = reset;
568     }
569 
570     return (PyObject *)pp;
571 }
572 
573 
574 // Create a clone of a property.
clone(qpycore_pyqtProperty * orig)575 static qpycore_pyqtProperty *clone(qpycore_pyqtProperty *orig)
576 {
577     qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)PyType_GenericNew(
578             Py_TYPE(orig), 0, 0);
579 
580     if (pp)
581     {
582         pp->pyqtprop_get = orig->pyqtprop_get;
583         Py_XINCREF(pp->pyqtprop_get);
584 
585         pp->pyqtprop_set = orig->pyqtprop_set;
586         Py_XINCREF(pp->pyqtprop_set);
587 
588         pp->pyqtprop_del = orig->pyqtprop_del;
589         Py_XINCREF(pp->pyqtprop_del);
590 
591         pp->pyqtprop_doc = orig->pyqtprop_doc;
592         Py_XINCREF(pp->pyqtprop_doc);
593 
594         pp->pyqtprop_reset = orig->pyqtprop_reset;
595         Py_XINCREF(pp->pyqtprop_reset);
596 
597         pp->pyqtprop_notify = orig->pyqtprop_notify;
598         Py_XINCREF(pp->pyqtprop_notify);
599 
600         pp->pyqtprop_type = orig->pyqtprop_type;
601         Py_XINCREF(pp->pyqtprop_type);
602 
603         pp->pyqtprop_parsed_type = new Chimera(*orig->pyqtprop_parsed_type);
604 
605         pp->pyqtprop_flags = orig->pyqtprop_flags;
606         pp->pyqtprop_revision = orig->pyqtprop_revision;
607         pp->pyqtprop_sequence = orig->pyqtprop_sequence;
608     }
609 
610     return pp;
611 }
612 
613 
614 // Return the docstring of an optional getter or 0 if it doesn't have one.
getter_docstring(PyObject * getter)615 static PyObject *getter_docstring(PyObject *getter)
616 {
617     // Handle the trivial case.
618     if (!getter)
619         return 0;
620 
621     PyObject *getter_doc = PyObject_GetAttrString(getter, "__doc__");
622 
623     if (!getter_doc)
624     {
625         PyErr_Clear();
626         return 0;
627     }
628 
629     if (getter_doc == Py_None)
630     {
631         Py_DECREF(getter_doc);
632         return 0;
633     }
634 
635     return getter_doc;
636 }
637 
638 
639 // Initialise the type and return true if there was no error.
qpycore_pyqtProperty_init_type()640 bool qpycore_pyqtProperty_init_type()
641 {
642 #if PY_VERSION_HEX >= 0x03040000
643     qpycore_pyqtProperty_TypeObject = (PyTypeObject *)PyType_FromSpec(
644             &qpycore_pyqtProperty_Spec);
645 
646     return qpycore_pyqtProperty_TypeObject;
647 #else
648     if (PyType_Ready(&qpycore_pyqtProperty_Type) < 0)
649         return false;
650 
651     qpycore_pyqtProperty_TypeObject = &qpycore_pyqtProperty_Type;
652 
653     return true;
654 #endif
655 }
656