1 // This implements the conversion of a QVariant to a Python object and is part
2 // of the public API.
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 <QVariant>
25 
26 #include "qpycore_chimera.h"
27 
28 #include "sipAPIQtCore.h"
29 
30 
31 // Forward declarations.
32 static PyObject *convert_hash(const Chimera *ct, const QVariantHash &value);
33 static PyObject *convert_list(const Chimera *ct, const QVariantList &value);
34 static PyObject *convert_map(const Chimera *ct, const QVariantMap &value);
35 static PyObject *convert(const Chimera *ct, const QVariant &value);
36 static int add_variant_to_dict(const Chimera *ct, PyObject *dict,
37         const QString &key, const QVariant &value);
38 
39 
40 // Convert a QVariant to a Python object according to an optional type.
pyqt5_from_qvariant_by_type(QVariant & value,PyObject * type)41 PyObject *pyqt5_from_qvariant_by_type(QVariant &value, PyObject *type)
42 {
43     PyObject *value_obj;
44 
45     if (type)
46     {
47         const Chimera *ct = Chimera::parse(type);
48 
49         if (!ct)
50             return 0;
51 
52         QVariant::Type wanted = static_cast<QVariant::Type>(ct->metatype());
53 
54         // Get QVariant to do a conversion if there is one to do.
55         if (value.isValid() && ct->metatype() < static_cast<int>(QVariant::UserType))
56         {
57             // If we have a QStringList but are not wanting one then convert it
58             // to a QVariantList.
59             if (wanted != QVariant::StringList && value.type() == QVariant::StringList)
60                 value.convert(QVariant::List);
61 
62             // If we have a container but are not wanting one then assume we
63             // want a container with elements of the wanted type.
64             if (wanted != QVariant::List && value.type() == QVariant::List)
65             {
66                 // If we have a QVariantList but we wanted a QStringList then
67                 // assume each variant is a string.
68                 if (wanted == QVariant::StringList)
69                     value_obj = convert(ct, value);
70                 else
71                     value_obj = convert_list(ct, value.toList());
72             }
73             else if (wanted != QVariant::Map && value.type() == QVariant::Map)
74             {
75                 value_obj = convert_map(ct, value.toMap());
76             }
77             else if (wanted != QVariant::Hash && value.type() == QVariant::Hash)
78             {
79                 value_obj = convert_hash(ct, value.toHash());
80             }
81             else
82             {
83                 value_obj = convert(ct, value);
84             }
85         }
86         else if (!value.isValid())
87         {
88             // Convert an invalid value to the default value of the requested
89             // type.
90             if (ct->py_type())
91                 value_obj = PyObject_CallObject(ct->py_type(), NULL);
92             else
93                 value_obj = ct->toPyObject(QVariant(wanted));
94         }
95         else
96         {
97             // This is likely to fail and the exception will say why.
98             value_obj = ct->toPyObject(value);
99         }
100 
101         delete ct;
102     }
103     else
104     {
105         QVariant *heap = new QVariant(value);
106         value_obj = sipConvertFromNewType(heap, sipType_QVariant, 0);
107 
108         if (!value_obj)
109             delete heap;
110     }
111 
112     return value_obj;
113 }
114 
115 
116 // Convert a QVariantList to a list of Python objects.
convert_list(const Chimera * ct,const QVariantList & value)117 static PyObject *convert_list(const Chimera *ct, const QVariantList &value)
118 {
119     PyObject *list = PyList_New(value.size());
120 
121     if (!list)
122         return 0;
123 
124     for (int i = 0; i < value.size(); ++i)
125     {
126         PyObject *el = convert(ct, value.at(i));
127 
128         if (!el)
129         {
130             Py_DECREF(list);
131             return 0;
132         }
133 
134         PyList_SetItem(list, i, el);
135     }
136 
137     return list;
138 }
139 
140 
141 // Convert a QVariantMap to a dict of Python objects.
convert_map(const Chimera * ct,const QVariantMap & value)142 static PyObject *convert_map(const Chimera *ct, const QVariantMap &value)
143 {
144     PyObject *dict = PyDict_New();
145 
146     if (!dict)
147         return 0;
148 
149     for (QVariantMap::const_iterator it = value.constBegin(); it != value.constEnd(); ++it)
150     {
151         if (add_variant_to_dict(ct, dict, it.key(), it.value()) < 0)
152         {
153             Py_DECREF(dict);
154             return 0;
155         }
156     }
157 
158     return dict;
159 }
160 
161 
162 // Convert a QVariantHash to a dict of Python objects.
convert_hash(const Chimera * ct,const QVariantHash & value)163 static PyObject *convert_hash(const Chimera *ct, const QVariantHash &value)
164 {
165     PyObject *dict = PyDict_New();
166 
167     if (!dict)
168         return 0;
169 
170     for (QVariantHash::const_iterator it = value.constBegin(); it != value.constEnd(); ++it)
171     {
172         if (add_variant_to_dict(ct, dict, it.key(), it.value()) < 0)
173         {
174             Py_DECREF(dict);
175             return 0;
176         }
177     }
178 
179     return dict;
180 }
181 
182 
183 // Convert a QVariant to a Python object.
convert(const Chimera * ct,const QVariant & value)184 static PyObject *convert(const Chimera *ct, const QVariant &value)
185 {
186     QVariant converted = value;
187 
188     if (!converted.convert(static_cast<QVariant::Type>(ct->metatype())))
189         converted = value;
190 
191     return ct->toPyObject(converted);
192 }
193 
194 
195 // Add a QVariant to a Python dict with a QString key.
add_variant_to_dict(const Chimera * ct,PyObject * dict,const QString & key,const QVariant & value)196 static int add_variant_to_dict(const Chimera *ct, PyObject *dict,
197         const QString &key, const QVariant &value)
198 {
199     QString *key_heap = new QString(key);
200     PyObject *key_obj = sipConvertFromNewType(key_heap, sipType_QString, 0);
201 
202     if (!key_obj)
203     {
204         delete key_heap;
205         return -1;
206     }
207 
208     PyObject *value_obj = convert(ct, value);
209 
210     if (!value_obj)
211     {
212         Py_DECREF(key_obj);
213         return -1;
214     }
215 
216     int rc = PyDict_SetItem(dict, key_obj, value_obj);
217 
218     Py_DECREF(key_obj);
219     Py_DECREF(value_obj);
220 
221     return rc;
222 }
223