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, ¬ify, &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