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