1 // This implements the helpers for QMetaObject.
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 <QMetaMethod>
25 #include <QMetaObject>
26 #include <QObject>
27
28 #include "qpycore_api.h"
29 #include "qpycore_chimera.h"
30 #include "qpycore_misc.h"
31 #include "qpycore_objectified_strings.h"
32 #include "qpycore_public_api.h"
33
34 #include "sipAPIQtCore.h"
35
36
37 // Forward declarations.
38 static void connect(QObject *qobj, PyObject *slot_obj,
39 const QByteArray &slot_nm, const QByteArray &args);
40
41
qpycore_qmetaobject_connectslotsbyname(QObject * qobj,PyObject * qobj_wrapper)42 void qpycore_qmetaobject_connectslotsbyname(QObject *qobj,
43 PyObject *qobj_wrapper)
44 {
45 // Get the class attributes.
46 PyObject *dir = PyObject_Dir((PyObject *)Py_TYPE(qobj_wrapper));
47
48 if (!dir)
49 return;
50
51 PyObject *slot_obj = 0;
52
53 for (Py_ssize_t li = 0; li < PyList_Size(dir); ++li)
54 {
55 PyObject *name_obj = PyList_GetItem(dir, li);
56
57 // Get the slot object.
58 Py_XDECREF(slot_obj);
59 slot_obj = PyObject_GetAttr(qobj_wrapper, name_obj);
60
61 if (!slot_obj)
62 continue;
63
64 // Ignore it if it is not a callable.
65 if (!PyCallable_Check(slot_obj))
66 continue;
67
68 // Use the signature attribute instead of the name if there is one.
69 PyObject *sigattr = PyObject_GetAttr(slot_obj,
70 qpycore_dunder_pyqtsignature);
71
72 if (sigattr)
73 {
74 for (Py_ssize_t i = 0; i < PyList_Size(sigattr); ++i)
75 {
76 PyObject *decoration = PyList_GetItem(sigattr, i);
77 Chimera::Signature *sig = Chimera::Signature::fromPyObject(decoration);
78 QByteArray args = sig->arguments();
79
80 if (!args.isEmpty())
81 connect(qobj, slot_obj, sig->name(), args);
82 }
83
84 Py_DECREF(sigattr);
85 }
86 else
87 {
88 const char *ascii_name = sipString_AsASCIIString(&name_obj);
89
90 if (!ascii_name)
91 continue;
92
93 PyErr_Clear();
94
95 connect(qobj, slot_obj, QByteArray(ascii_name), QByteArray());
96
97 Py_DECREF(name_obj);
98 }
99 }
100
101 Py_XDECREF(slot_obj);
102 Py_DECREF(dir);
103 }
104
105
106 // Connect up a particular slot name, with optional arguments.
connect(QObject * qobj,PyObject * slot_obj,const QByteArray & slot_nm,const QByteArray & args)107 static void connect(QObject *qobj, PyObject *slot_obj,
108 const QByteArray &slot_nm, const QByteArray &args)
109 {
110 // Ignore if it's not an autoconnect slot.
111 if (!slot_nm.startsWith("on_"))
112 return;
113
114 // Extract the names of the emitting object and the signal.
115 int i;
116
117 i = slot_nm.lastIndexOf('_');
118
119 if (i - 3 < 1 || i + 1 >= slot_nm.size())
120 return;
121
122 QByteArray ename = slot_nm.mid(3, i - 3);
123 QByteArray sname = slot_nm.mid(i + 1);
124
125 // Find the emitting object and get its meta-object.
126 QObject *eobj = qobj->findChild<QObject *>(ename);
127
128 if (!eobj)
129 return;
130
131 const QMetaObject *mo = eobj->metaObject();
132
133 // Got through the methods looking for a matching signal.
134 for (int m = 0; m < mo->methodCount(); ++m)
135 {
136 QMetaMethod mm = mo->method(m);
137
138 if (mm.methodType() != QMetaMethod::Signal)
139 continue;
140
141 QByteArray sig(mm.methodSignature());
142
143 if (Chimera::Signature::name(sig) != sname)
144 continue;
145
146 // If we have slot arguments then they must match as well.
147 if (!args.isEmpty() && Chimera::Signature::arguments(sig) != args)
148 continue;
149
150 QObject *receiver;
151 QByteArray slot_sig;
152
153 if (pyqt5_get_connection_parts(slot_obj, eobj, sig.constData(), false, &receiver, slot_sig) != sipErrorNone)
154 continue;
155
156 // Add the type character.
157 sig.prepend('2');
158
159 // Connect the signal.
160 QObject::connect(eobj, sig.constData(), receiver, slot_sig.constData());
161 }
162 }
163