1 // This implements the helper for QObject.__getattr__().
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 #include <QHash>
25 #include <QMetaMethod>
26 #include <QObject>
27
28 #include "qpycore_api.h"
29 #include "qpycore_pyqtboundsignal.h"
30 #include "qpycore_pyqtmethodproxy.h"
31 #include "qpycore_pyqtsignal.h"
32
33
34 // See if we can find an attribute in the Qt meta-type system. This is
35 // primarily to support access to JavaScript (e.g. QQuickItem) so we don't
36 // support overloads.
qpycore_qobject_getattr(const QObject * qobj,PyObject * py_qobj,const char * name)37 PyObject *qpycore_qobject_getattr(const QObject *qobj, PyObject *py_qobj,
38 const char *name)
39 {
40 const QMetaObject *mo = qobj->metaObject();
41
42 // Try and find a method with the name.
43 QMetaMethod method;
44 int method_index = -1;
45
46 // Count down to allow overrides (assuming they are possible).
47 for (int m = mo->methodCount() - 1; m >= 0; --m)
48 {
49 method = mo->method(m);
50
51 if (method.methodType() == QMetaMethod::Constructor)
52 continue;
53
54 // Get the method name.
55 QByteArray mname(method.methodSignature());
56 int idx = mname.indexOf('(');
57
58 if (idx >= 0)
59 mname.truncate(idx);
60
61 if (mname == name)
62 {
63 method_index = m;
64 break;
65 }
66 }
67
68 if (method_index >= 0)
69 {
70 // Get the value to return. Note that this is recreated each time. We
71 // could put a descriptor in the type dictionary to satisfy the request
72 // in future but the typical use case is getting a value from a C++
73 // proxy (e.g. QDeclarativeItem) and we can't assume that what is being
74 // proxied is the same each time.
75 if (method.methodType() == QMetaMethod::Signal)
76 {
77 // We need to keep explicit references to the unbound signals
78 // (because we don't use the type dictionary to do so) because they
79 // own the parsed signature which may be needed by a PyQtSlotProxy
80 // at some point.
81 typedef QHash<QByteArray, PyObject *> SignalHash;
82
83 static SignalHash *sig_hash = 0;
84
85 // For crappy compilers.
86 if (!sig_hash)
87 sig_hash = new SignalHash;
88
89 PyObject *sig_obj;
90
91 QByteArray sig_str = method.methodSignature();
92
93 SignalHash::const_iterator it = sig_hash->find(sig_str);
94
95 if (it == sig_hash->end())
96 {
97 sig_obj = (PyObject *)qpycore_pyqtSignal_New(
98 sig_str.constData());
99
100 if (!sig_obj)
101 return 0;
102
103 sig_hash->insert(sig_str, sig_obj);
104 }
105 else
106 {
107 sig_obj = it.value();
108 }
109
110 return qpycore_pyqtBoundSignal_New((qpycore_pyqtSignal *)sig_obj,
111 py_qobj, const_cast<QObject *>(qobj));
112 }
113
114 // Respect the 'private' nature of __ names.
115 if (name[0] != '_' || name[1] != '_')
116 {
117 QByteArray py_name(sipPyTypeName(Py_TYPE(py_qobj)));
118 py_name.append('.');
119 py_name.append(name);
120
121 return qpycore_pyqtMethodProxy_New(const_cast<QObject *>(qobj),
122 method_index, py_name);
123 }
124 }
125
126 // Replicate the standard Python exception.
127 PyErr_Format(PyExc_AttributeError, "'%s' object has no attribute '%s'",
128 sipPyTypeName(Py_TYPE(py_qobj)), name);
129
130 return 0;
131 }
132