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