1 // This contains the implementation of the Q_ENUM, Q_ENUMS, Q_FLAG and Q_FLAGS
2 // support.
3 //
4 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
5 //
6 // This file is part of PyQt5.
7 //
8 // This file may be used under the terms of the GNU General Public License
9 // version 3.0 as published by the Free Software Foundation and appearing in
10 // the file LICENSE included in the packaging of this file. Please review the
11 // following information to ensure the GNU General Public License version 3.0
12 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
13 //
14 // If you do not wish to use this file under the terms of the GPL version 3.0
15 // then you may purchase a commercial license. For more information contact
16 // info@riverbankcomputing.com.
17 //
18 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20
21
22 #include <Python.h>
23
24 #include <QMultiHash>
25
26 #include "qpycore_chimera.h"
27 #include "qpycore_enums_flags.h"
28
29 #include "sipAPIQtCore.h"
30
31
32 // Forward declarations.
33 static bool add_enum_flag(PyObject *arg, bool flag, const char *context,
34 struct _frame *frame);
35 static void add_key_value(EnumFlag &enum_flag, PyObject *key, PyObject *value);
36 static struct _frame *get_calling_frame();
37 static bool objectify(const char *s, PyObject **objp);
38 static PyObject *parse_enum_flag(PyObject *arg, bool flag,
39 const char *context);
40 static PyObject *parse_enums_flags(PyObject *args, bool flags,
41 const char *context);
42 static bool trawl_members(PyObject *members, EnumFlag &enum_flag);
43 static void trawl_type_dict(PyObject *arg, EnumFlag &enum_flag);
44
45
46 // The enums and flags defined in each frame.
47 static QMultiHash<const struct _frame *, EnumFlag> enums_flags_hash;
48
49
50 // Add the given Q_ENUM() argument to the current enums/flags hash.
qpycore_Enum(PyObject * arg)51 PyObject *qpycore_Enum(PyObject *arg)
52 {
53 return parse_enum_flag(arg, false, "Q_ENUM");
54 }
55
56
57 // Add the given Q_ENUMS() arguments to the current enums/flags hash.
qpycore_Enums(PyObject * args)58 PyObject *qpycore_Enums(PyObject *args)
59 {
60 return parse_enums_flags(args, false, "Q_ENUMS");
61 }
62
63
64 // Add the given Q_FLAG() arguments to the current enums/flags hash.
qpycore_Flag(PyObject * arg)65 PyObject *qpycore_Flag(PyObject *arg)
66 {
67 return parse_enum_flag(arg, true, "Q_FLAG");
68 }
69
70
71 // Add the given Q_FLAGS() arguments to the current enums/flags hash.
qpycore_Flags(PyObject * args)72 PyObject *qpycore_Flags(PyObject *args)
73 {
74 return parse_enums_flags(args, true, "Q_FLAGS");
75 }
76
77
78 // Return the calling frame.
get_calling_frame()79 static struct _frame *get_calling_frame()
80 {
81 struct _frame *frame = sipGetFrame(1);
82
83 if (!frame)
84 {
85 PyErr_SetString(PyExc_RuntimeError, "no previous frame");
86 return 0;
87 }
88
89 return frame;
90 }
91
92
93 // Add the given Q_ENUM() or Q_FLAG() argument to the current enums/flags hash.
parse_enum_flag(PyObject * arg,bool flag,const char * context)94 static PyObject *parse_enum_flag(PyObject *arg, bool flag, const char *context)
95 {
96 #if defined(PYPY_VERSION)
97 PyErr_Format(PyExc_AttributeError, "%s is not supported on PyPy", context);
98 return 0;
99 #else
100 struct _frame *frame = get_calling_frame();
101
102 if (!frame)
103 return 0;
104
105 if (!add_enum_flag(arg, flag, context, frame))
106 return 0;
107
108 Py_INCREF(Py_None);
109 return Py_None;
110 #endif
111 }
112
113
114 // Add the given Q_ENUMS() or Q_FLAGS() arguments to the current enums/flags
115 // hash.
parse_enums_flags(PyObject * args,bool flags,const char * context)116 static PyObject *parse_enums_flags(PyObject *args, bool flags,
117 const char *context)
118 {
119 #if defined(PYPY_VERSION)
120 PyErr_Format(PyExc_AttributeError, "%s is not supported on PyPy", context);
121 return 0;
122 #else
123 struct _frame *frame = get_calling_frame();
124
125 if (!frame)
126 return 0;
127
128 // Each argument is a separate enum/flag.
129 for (Py_ssize_t i = 0; i < PyTuple_Size(args); ++i)
130 {
131 PyObject *arg = PyTuple_GetItem(args, i);
132
133 if (!add_enum_flag(arg, flags, context, frame))
134 return 0;
135 }
136
137 Py_INCREF(Py_None);
138 return Py_None;
139 #endif
140 }
141
142
143 // Add the given enum/flag to the current hash. Return true if successfull.
add_enum_flag(PyObject * arg,bool flag,const char * context,struct _frame * frame)144 static bool add_enum_flag(PyObject *arg, bool flag, const char *context,
145 struct _frame *frame)
146 {
147 // Check the argument's type is type. This will also pass Enums.
148 if (!PyType_Check(arg))
149 {
150 PyErr_Format(PyExc_TypeError,
151 "arguments to %s() must be type or enum.Enum objects",
152 context);
153
154 return false;
155 }
156
157 // Create the basic enum/flag.
158 EnumFlag enum_flag(sipPyTypeName((PyTypeObject *)arg), flag);
159
160 // See if it an Enum. We assume it is if it has a __members__ attribute.
161 static PyObject *members_s = 0;
162
163 if (!objectify("__members__", &members_s))
164 return false;
165
166 PyObject *members = PyObject_GetAttr(arg, members_s);
167
168 if (members)
169 {
170 bool ok = trawl_members(members, enum_flag);
171
172 Py_DECREF(members);
173
174 if (!ok)
175 return false;
176
177 enum_flag.isScoped = true;
178 }
179 else
180 {
181 trawl_type_dict(arg, enum_flag);
182 }
183
184 enums_flags_hash.insert(frame, enum_flag);
185
186 Chimera::registerPyEnum(arg);
187
188 // Make sure there are no exceptions left after failed value conversions.
189 PyErr_Clear();
190
191 return true;
192 }
193
194
195 // Trawl a __members__ mapping for int keys.
trawl_members(PyObject * members,EnumFlag & enum_flag)196 static bool trawl_members(PyObject *members, EnumFlag &enum_flag)
197 {
198 static PyObject *value_s = 0;
199
200 if (!objectify("value", &value_s))
201 return false;
202
203 PyObject *items;
204 Py_ssize_t nr_items;
205
206 // Get the contents of __members__.
207 #if PY_MAJOR_VERSION >= 3
208 items = PyMapping_Items(members);
209 #else
210 // Python v2 implements PyMapping_Items() as a macro that expands to the
211 // following - but without the const_cast. It isn't a problem for the
212 // version of MSVC supported by Python v2 but the const_cast is needed by
213 // later versions of MSVC and people will want to use them even though they
214 // are not supported.
215 items = PyObject_CallMethod(members, const_cast<char *>("items"), NULL);
216 #endif
217
218 if (!items)
219 goto return_error;
220
221 nr_items = PySequence_Length(items);
222 if (nr_items < 0)
223 goto release_items;
224
225 for (Py_ssize_t i = 0; i < nr_items; ++i)
226 {
227 PyObject *item, *key, *member, *value;
228
229 item = PySequence_GetItem(items, i);
230 if (!item)
231 goto release_items;
232
233 // The item should be a 2-element sequence of the key name and an
234 // object containing the value.
235 key = PySequence_GetItem(item, 0);
236 member = PySequence_GetItem(item, 1);
237
238 Py_DECREF(item);
239
240 if (!key || !member)
241 {
242 Py_XDECREF(key);
243 Py_XDECREF(member);
244
245 goto release_items;
246 }
247
248 // Get the value.
249 value = PyObject_GetAttr(member, value_s);
250
251 Py_DECREF(member);
252
253 if (!value)
254 {
255 Py_DECREF(key);
256
257 goto release_items;
258 }
259
260 add_key_value(enum_flag, key, value);
261
262 Py_DECREF(key);
263 Py_DECREF(value);
264 }
265
266 Py_DECREF(items);
267
268 return true;
269
270 release_items:
271 Py_DECREF(items);
272
273 return_error:
274 return false;
275 }
276
277
278 // Trawl the dictionary of a type looking for int attributes.
trawl_type_dict(PyObject * arg,EnumFlag & enum_flag)279 static void trawl_type_dict(PyObject *arg, EnumFlag &enum_flag)
280 {
281 Py_ssize_t pos = 0;
282 PyObject *key, *value, *dict;
283
284 dict = sipPyTypeDict((PyTypeObject *)arg);
285
286 while (PyDict_Next(dict, &pos, &key, &value))
287 add_key_value(enum_flag, key, value);
288 }
289
290
291 // Add a key/value to an enum/flag.
add_key_value(EnumFlag & enum_flag,PyObject * key,PyObject * value)292 static void add_key_value(EnumFlag &enum_flag, PyObject *key, PyObject *value)
293 {
294 PyErr_Clear();
295
296 int i_value = sipLong_AsInt(value);
297
298 if (!PyErr_Occurred())
299 {
300 const char *s_key = sipString_AsASCIIString(&key);
301
302 if (s_key)
303 {
304 enum_flag.keys.insert(s_key, i_value);
305 Py_DECREF(key);
306 }
307 }
308 }
309
310
311 // Convert an ASCII string to a Python object if it hasn't already been done.
objectify(const char * s,PyObject ** objp)312 static bool objectify(const char *s, PyObject **objp)
313 {
314 if (*objp == NULL)
315 {
316 #if PY_MAJOR_VERSION >= 3
317 *objp = PyUnicode_FromString(s);
318 #else
319 *objp = PyString_FromString(s);
320 #endif
321
322 if (*objp == NULL)
323 return false;
324 }
325
326 return true;
327 }
328
329
330 // Return the current enums/flags list.
qpycore_get_enums_flags_list()331 QList<EnumFlag> qpycore_get_enums_flags_list()
332 {
333 QList<EnumFlag> enums_flags_list;
334
335 #if !defined(PYPY_VERSION)
336 struct _frame *frame = sipGetFrame(0);
337
338 enums_flags_list = enums_flags_hash.values(frame);
339 enums_flags_hash.remove(frame);
340 #endif
341
342 return enums_flags_list;
343 }
344