1 // This is the implementation of the pyqtSlot decorator.
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
23 #include <QByteArray>
24
25 #include "qpycore_api.h"
26 #include "qpycore_chimera.h"
27 #include "qpycore_misc.h"
28 #include "qpycore_objectified_strings.h"
29
30
31 // Forward declarations.
32 extern "C" {static PyObject *decorator(PyObject *self, PyObject *f);}
33
34
35 // This implements the pyqtSlot decorator.
qpycore_pyqtslot(PyObject * args,PyObject * kwds)36 PyObject *qpycore_pyqtslot(PyObject *args, PyObject *kwds)
37 {
38 const char *name_str = 0;
39 PyObject *res_obj = 0;
40 int revision = 0;
41 static const char *kwlist[] = {"name", "result", "revision", 0};
42
43 static PyObject *no_args = 0;
44
45 if (!no_args)
46 {
47 no_args = PyTuple_New(0);
48
49 if (!no_args)
50 return 0;
51 }
52
53 if (!PyArg_ParseTupleAndKeywords(no_args, kwds, "|sOi:pyqtSlot",
54 const_cast<char **>(kwlist), &name_str, &res_obj, &revision))
55 return 0;
56
57 Chimera::Signature *parsed_sig = Chimera::parse(args, name_str,
58 "a pyqtSlot type argument");
59
60 if (!parsed_sig)
61 return 0;
62
63 // Sticking the revision here is an awful hack, but it saves creating
64 // another data structure wrapped in a capsule.
65 parsed_sig->revision = revision;
66
67 // Parse any result type.
68 if (res_obj)
69 {
70 parsed_sig->result = Chimera::parse(res_obj);
71
72 if (!parsed_sig->result)
73 {
74 Chimera::raiseParseException(res_obj, "a pyqtSlot result");
75 delete parsed_sig;
76 return 0;
77 }
78 }
79
80 // Wrap the parsed signature in a Python object.
81 PyObject *sig_obj = Chimera::Signature::toPyObject(parsed_sig);
82
83 if (!sig_obj)
84 return 0;
85
86 // Create the decorator function itself. We stash the arguments in "self".
87 // This may be an abuse, but it seems to be Ok.
88 static PyMethodDef deco_method = {
89 #if PY_VERSION_HEX >= 0x02050000
90 "_deco", decorator, METH_O, 0
91 #else
92 const_cast<char *>("_deco"), decorator, METH_O, 0
93 #endif
94 };
95
96 PyObject *obj = PyCFunction_New(&deco_method, sig_obj);
97 Py_DECREF(sig_obj);
98
99 return obj;
100 }
101
102
103 // This is the decorator function that saves the C++ signature as a function
104 // attribute.
decorator(PyObject * self,PyObject * f)105 static PyObject *decorator(PyObject *self, PyObject *f)
106 {
107 Chimera::Signature *parsed_sig = Chimera::Signature::fromPyObject(self);
108 const QByteArray &sig = parsed_sig->signature;
109
110 // Use the function name if there isn't already one.
111 if (sig.startsWith('('))
112 {
113 // Get the function's name.
114 PyObject *nobj = PyObject_GetAttr(f, qpycore_dunder_name);
115
116 if (!nobj)
117 return 0;
118
119 PyObject *ascii_obj = nobj;
120 const char *ascii = sipString_AsASCIIString(&ascii_obj);
121 Py_DECREF(nobj);
122
123 if (!ascii)
124 return 0;
125
126 parsed_sig->signature.prepend(ascii);
127 parsed_sig->py_signature.prepend(ascii);
128 Py_DECREF(ascii_obj);
129 }
130
131 // See if the function has already been decorated.
132 PyObject *decorations = PyObject_GetAttr(f, qpycore_dunder_pyqtsignature);
133 int rc;
134
135 if (decorations)
136 {
137 // Insert the new decoration at the head of the existing ones so that
138 // the list order matches the order they appear in the script.
139 rc = PyList_Insert(decorations, 0, self);
140 }
141 else
142 {
143 PyErr_Clear();
144
145 decorations = PyList_New(1);
146
147 if (!decorations)
148 return 0;
149
150 Py_INCREF(self);
151 PyList_SetItem(decorations, 0, self);
152
153 // Save the new decoration.
154 rc = PyObject_SetAttr(f, qpycore_dunder_pyqtsignature, decorations);
155 }
156
157 Py_DECREF(decorations);
158
159 if (rc < 0)
160 return 0;
161
162 // Return the function.
163 Py_INCREF(f);
164 return f;
165 }
166