1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Designer of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "signalsloteditor.h"
30#include "signalsloteditor_p.h"
31#include "connectdialog_p.h"
32#include "signalslot_utils_p.h"
33
34#include <metadatabase_p.h>
35#include <qdesigner_formwindowcommand_p.h>
36
37#include <QtDesigner/private/ui4_p.h>
38#include <QtDesigner/abstractformwindow.h>
39#include <QtDesigner/abstractformeditor.h>
40#include <QtDesigner/abstractmetadatabase.h>
41
42#include <QtWidgets/qapplication.h>
43#include <QtWidgets/qundostack.h>
44#include <QtWidgets/qmenu.h>
45
46#include <QtCore/qcoreapplication.h>
47#include <QtCore/qdebug.h>
48
49QT_BEGIN_NAMESPACE
50
51namespace qdesigner_internal {
52
53/*******************************************************************************
54** SignalSlotConnection
55*/
56
57SignalSlotConnection::SignalSlotConnection(ConnectionEdit *edit, QWidget *source, QWidget *target)
58 : Connection(edit, source, target)
59{
60}
61
62DomConnection *SignalSlotConnection::toUi() const
63{
64 DomConnection *result = new DomConnection;
65
66 result->setElementSender(sender());
67 result->setElementSignal(signal());
68 result->setElementReceiver(receiver());
69 result->setElementSlot(slot());
70
71 DomConnectionHints *hints = new DomConnectionHints;
72 QVector<DomConnectionHint *> list;
73
74 QPoint sp = endPointPos(type: EndPoint::Source);
75 QPoint tp = endPointPos(type: EndPoint::Target);
76
77 DomConnectionHint *hint = new DomConnectionHint;
78 hint->setAttributeType(QStringLiteral("sourcelabel"));
79 hint->setElementX(sp.x());
80 hint->setElementY(sp.y());
81 list.append(t: hint);
82
83 hint = new DomConnectionHint;
84 hint->setAttributeType(QStringLiteral("destinationlabel"));
85 hint->setElementX(tp.x());
86 hint->setElementY(tp.y());
87 list.append(t: hint);
88
89 hints->setElementHint(list);
90 result->setElementHints(hints);
91
92 return result;
93}
94
95void SignalSlotConnection::setSignal(const QString &signal)
96{
97 m_signal = signal;
98 setLabel(type: EndPoint::Source, text: m_signal);
99}
100
101void SignalSlotConnection::setSlot(const QString &slot)
102{
103 m_slot = slot;
104 setLabel(type: EndPoint::Target, text: m_slot);
105}
106
107QString SignalSlotConnection::sender() const
108{
109 QObject *source = object(type: EndPoint::Source);
110 if (!source)
111 return QString();
112
113 SignalSlotEditor *edit = qobject_cast<SignalSlotEditor*>(object: this->edit());
114 Q_ASSERT(edit != nullptr);
115
116 return realObjectName(core: edit->formWindow()->core(), object: source);
117}
118
119QString SignalSlotConnection::receiver() const
120{
121 QObject *sink = object(type: EndPoint::Target);
122 if (!sink)
123 return QString();
124
125 SignalSlotEditor *edit = qobject_cast<SignalSlotEditor*>(object: this->edit());
126 Q_ASSERT(edit != nullptr);
127 return realObjectName(core: edit->formWindow()->core(), object: sink);
128}
129
130void SignalSlotConnection::updateVisibility()
131{
132 Connection::updateVisibility();
133 if (isVisible() && (signal().isEmpty() || slot().isEmpty()))
134 setVisible(false);
135}
136
137QString SignalSlotConnection::toString() const
138{
139 return QCoreApplication::translate(context: "SignalSlotConnection", key: "SENDER(%1), SIGNAL(%2), RECEIVER(%3), SLOT(%4)")
140 .arg(args: sender(), args: signal(), args: receiver(), args: slot());
141}
142
143SignalSlotConnection::State SignalSlotConnection::isValid(const QWidget *background) const
144{
145 const QObject *source = object(type: EndPoint::Source);
146 if (!source)
147 return ObjectDeleted;
148
149 const QObject *target = object(type: EndPoint::Target);
150 if (!target)
151 return ObjectDeleted;
152
153 if (m_slot.isEmpty() || m_signal.isEmpty())
154 return InvalidMethod;
155
156 if (const QWidget *sourceWidget = qobject_cast<const QWidget*>(o: source))
157 if (!background->isAncestorOf(child: sourceWidget))
158 return NotAncestor;
159
160 if (const QWidget *targetWidget = qobject_cast<const QWidget*>(o: target))
161 if (!background->isAncestorOf(child: targetWidget))
162 return NotAncestor;
163
164 return Valid;
165}
166
167/*******************************************************************************
168** Commands
169*/
170
171class SetMemberCommand : public QUndoCommand, public CETypes
172{
173public:
174 SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type,
175 const QString &member, SignalSlotEditor *editor);
176 void redo() override;
177 void undo() override;
178private:
179 const QString m_old_member;
180 const QString m_new_member;
181 const EndPoint::Type m_type;
182 SignalSlotConnection *m_con;
183 SignalSlotEditor *m_editor;
184};
185
186SetMemberCommand::SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type,
187 const QString &member, SignalSlotEditor *editor) :
188 m_old_member(type == EndPoint::Source ? con->signal() : con->slot()),
189 m_new_member(member),
190 m_type(type),
191 m_con(con),
192 m_editor(editor)
193{
194 if (type == EndPoint::Source)
195 setText(QApplication::translate(context: "Command", key: "Change signal"));
196 else
197 setText(QApplication::translate(context: "Command", key: "Change slot"));
198}
199
200void SetMemberCommand::redo()
201{
202 m_con->update();
203 if (m_type == EndPoint::Source)
204 m_con->setSignal(m_new_member);
205 else
206 m_con->setSlot(m_new_member);
207 m_con->update();
208 emit m_editor->connectionChanged(con: m_con);
209}
210
211void SetMemberCommand::undo()
212{
213 m_con->update();
214 if (m_type == EndPoint::Source)
215 m_con->setSignal(m_old_member);
216 else
217 m_con->setSlot(m_old_member);
218 m_con->update();
219 emit m_editor->connectionChanged(con: m_con);
220}
221
222// Command to modify a connection
223class ModifyConnectionCommand : public QDesignerFormWindowCommand
224{
225public:
226 explicit ModifyConnectionCommand(QDesignerFormWindowInterface *form,
227 SignalSlotConnection *conn,
228 const QString &newSignal,
229 const QString &newSlot);
230 void redo() override;
231 void undo() override;
232
233private:
234 SignalSlotConnection *m_conn;
235 const QString m_oldSignal;
236 const QString m_oldSlot;
237 const QString m_newSignal;
238 const QString m_newSlot;
239};
240
241ModifyConnectionCommand::ModifyConnectionCommand(QDesignerFormWindowInterface *form,
242 SignalSlotConnection *conn,
243 const QString &newSignal,
244 const QString &newSlot) :
245 QDesignerFormWindowCommand(QCoreApplication::translate(context: "Command", key: "Change signal-slot connection"), form),
246 m_conn(conn),
247 m_oldSignal(conn->signal()),
248 m_oldSlot(conn->slot()),
249 m_newSignal(newSignal),
250 m_newSlot(newSlot)
251{
252}
253
254void ModifyConnectionCommand::redo()
255{
256 m_conn->setSignal(m_newSignal);
257 m_conn->setSlot(m_newSlot);
258}
259
260void ModifyConnectionCommand::undo()
261{
262 m_conn->setSignal(m_oldSignal);
263 m_conn->setSlot(m_oldSlot);
264}
265
266/*******************************************************************************
267** SignalSlotEditor
268*/
269
270SignalSlotEditor::SignalSlotEditor(QDesignerFormWindowInterface *form_window, QWidget *parent) :
271 ConnectionEdit(parent, form_window),
272 m_form_window(form_window),
273 m_showAllSignalsSlots(false)
274{
275}
276
277void SignalSlotEditor::modifyConnection(Connection *con)
278{
279 SignalSlotConnection *sigslot_con = static_cast<SignalSlotConnection*>(con);
280 ConnectDialog dialog(m_form_window,
281 sigslot_con->widget(type: EndPoint::Source),
282 sigslot_con->widget(type: EndPoint::Target),
283 m_form_window->core()->topLevel());
284
285 dialog.setSignalSlot(signal: sigslot_con->signal(), slot: sigslot_con->slot());
286 dialog.setShowAllSignalsSlots(m_showAllSignalsSlots);
287
288 if (dialog.exec() == QDialog::Accepted) {
289 const QString newSignal = dialog.signal();
290 const QString newSlot = dialog.slot();
291 if (sigslot_con->signal() != newSignal || sigslot_con->slot() != newSlot) {
292 ModifyConnectionCommand *cmd = new ModifyConnectionCommand(m_form_window, sigslot_con, newSignal, newSlot);
293 m_form_window->commandHistory()->push(cmd);
294 }
295 }
296
297 m_showAllSignalsSlots = dialog.showAllSignalsSlots();
298}
299
300Connection *SignalSlotEditor::createConnection(QWidget *source, QWidget *destination)
301{
302 SignalSlotConnection *con = nullptr;
303
304 Q_ASSERT(source != nullptr);
305 Q_ASSERT(destination != nullptr);
306
307 ConnectDialog dialog(m_form_window, source, destination, m_form_window->core()->topLevel());
308 dialog.setShowAllSignalsSlots(m_showAllSignalsSlots);
309
310 if (dialog.exec() == QDialog::Accepted) {
311 con = new SignalSlotConnection(this, source, destination);
312 con->setSignal(dialog.signal());
313 con->setSlot(dialog.slot());
314 }
315
316 m_showAllSignalsSlots = dialog.showAllSignalsSlots();
317
318 return con;
319}
320
321DomConnections *SignalSlotEditor::toUi() const
322{
323 DomConnections *result = new DomConnections;
324 QVector<DomConnection *> list;
325
326 const int count = connectionCount();
327 list.reserve(asize: count);
328 for (int i = 0; i < count; ++i) {
329 const SignalSlotConnection *con = static_cast<const SignalSlotConnection*>(connection(i));
330 Q_ASSERT(con != nullptr);
331
332 // If a widget's parent has been removed or moved to a different form,
333 // and the parent was not a managed widget
334 // (a page in a tab widget), we never get a widgetRemoved(). So we filter out
335 // these child widgets here (check QPointer and verify ancestor).
336 // Also, the user might demote a promoted widget or remove a fake
337 // slot in the editor, which causes the connection to become invalid
338 // once he doubleclicks on the method combo.
339 switch (con->isValid(background: background())) {
340 case SignalSlotConnection::Valid:
341 list.append(t: con->toUi());
342 break;
343 case SignalSlotConnection::ObjectDeleted:
344 case SignalSlotConnection::InvalidMethod:
345 case SignalSlotConnection::NotAncestor:
346 break;
347 }
348 }
349 result->setElementConnection(list);
350 return result;
351}
352
353QObject *SignalSlotEditor::objectByName(QWidget *topLevel, const QString &name) const
354{
355 if (name.isEmpty())
356 return nullptr;
357
358 Q_ASSERT(topLevel);
359 QObject *object = nullptr;
360 if (topLevel->objectName() == name)
361 object = topLevel;
362 else
363 object = topLevel->findChild<QObject*>(aName: name);
364 const QDesignerMetaDataBaseInterface *mdb = formWindow()->core()->metaDataBase();
365 if (mdb->item(object))
366 return object;
367 return nullptr;
368}
369
370void SignalSlotEditor::fromUi(const DomConnections *connections, QWidget *parent)
371{
372 if (connections == nullptr)
373 return;
374
375 setBackground(parent);
376 clear();
377 const auto &list = connections->elementConnection();
378 for (const DomConnection *dom_con : list) {
379 QObject *source = objectByName(topLevel: parent, name: dom_con->elementSender());
380 if (source == nullptr) {
381 qDebug(msg: "SignalSlotEditor::fromUi(): no source widget called \"%s\"",
382 dom_con->elementSender().toUtf8().constData());
383 continue;
384 }
385 QObject *destination = objectByName(topLevel: parent, name: dom_con->elementReceiver());
386 if (destination == nullptr) {
387 qDebug(msg: "SignalSlotEditor::fromUi(): no destination widget called \"%s\"",
388 dom_con->elementReceiver().toUtf8().constData());
389 continue;
390 }
391
392 QPoint sp = QPoint(20, 20), tp = QPoint(20, 20);
393 const DomConnectionHints *dom_hints = dom_con->elementHints();
394 if (dom_hints != nullptr) {
395 const auto &hints = dom_hints->elementHint();
396 for (DomConnectionHint *hint : hints) {
397 QString attr_type = hint->attributeType();
398 QPoint p = QPoint(hint->elementX(), hint->elementY());
399 if (attr_type == QStringLiteral("sourcelabel"))
400 sp = p;
401 else if (attr_type == QStringLiteral("destinationlabel"))
402 tp = p;
403 }
404 }
405
406 SignalSlotConnection *con = new SignalSlotConnection(this);
407
408 con->setEndPoint(type: EndPoint::Source, w: source, pos: sp);
409 con->setEndPoint(type: EndPoint::Target, w: destination, pos: tp);
410 con->setSignal(dom_con->elementSignal());
411 con->setSlot(dom_con->elementSlot());
412 addConnection(con);
413 }
414}
415
416static bool skipWidget(const QWidget *w)
417{
418 const QString name = QLatin1String(w->metaObject()->className());
419 if (name == QStringLiteral("QDesignerWidget"))
420 return true;
421 if (name == QStringLiteral("QLayoutWidget"))
422 return true;
423 if (name == QStringLiteral("qdesigner_internal::FormWindow"))
424 return true;
425 if (name == QStringLiteral("Spacer"))
426 return true;
427 return false;
428}
429
430QWidget *SignalSlotEditor::widgetAt(const QPoint &pos) const
431{
432 QWidget *widget = ConnectionEdit::widgetAt(pos);
433
434 if (widget == m_form_window->mainContainer())
435 return widget;
436
437 for (; widget != nullptr; widget = widget->parentWidget()) {
438 QDesignerMetaDataBaseItemInterface *item = m_form_window->core()->metaDataBase()->item(object: widget);
439 if (item == nullptr)
440 continue;
441 if (skipWidget(w: widget))
442 continue;
443 break;
444 }
445
446 return widget;
447}
448
449void SignalSlotEditor::setSignal(SignalSlotConnection *con, const QString &member)
450{
451 if (member == con->signal())
452 return;
453
454 m_form_window->beginCommand(description: QApplication::translate(context: "Command", key: "Change signal"));
455 undoStack()->push(cmd: new SetMemberCommand(con, EndPoint::Source, member, this));
456 if (!signalMatchesSlot(core: m_form_window->core(), signal: member, slot: con->slot()))
457 undoStack()->push(cmd: new SetMemberCommand(con, EndPoint::Target, QString(), this));
458 m_form_window->endCommand();
459}
460
461void SignalSlotEditor::setSlot(SignalSlotConnection *con, const QString &member)
462{
463 if (member == con->slot())
464 return;
465
466 m_form_window->beginCommand(description: QApplication::translate(context: "Command", key: "Change slot"));
467 undoStack()->push(cmd: new SetMemberCommand(con, EndPoint::Target, member, this));
468 if (!signalMatchesSlot(core: m_form_window->core(), signal: con->signal(), slot: member))
469 undoStack()->push(cmd: new SetMemberCommand(con, EndPoint::Source, QString(), this));
470 m_form_window->endCommand();
471}
472
473void SignalSlotEditor::setSource(Connection *_con, const QString &obj_name)
474{
475 SignalSlotConnection *con = static_cast<SignalSlotConnection*>(_con);
476
477 if (con->sender() == obj_name)
478 return;
479
480 m_form_window->beginCommand(description: QApplication::translate(context: "Command", key: "Change sender"));
481 ConnectionEdit::setSource(con, obj_name);
482
483 QObject *sourceObject = con->object(type: EndPoint::Source);
484
485 if (!memberFunctionListContains(core: m_form_window->core(), object: sourceObject, type: SignalMember, signature: con->signal()))
486 undoStack()->push(cmd: new SetMemberCommand(con, EndPoint::Source, QString(), this));
487
488 m_form_window->endCommand();
489}
490
491void SignalSlotEditor::setTarget(Connection *_con, const QString &obj_name)
492{
493 SignalSlotConnection *con = static_cast<SignalSlotConnection*>(_con);
494
495 if (con->receiver() == obj_name)
496 return;
497
498 m_form_window->beginCommand(description: QApplication::translate(context: "Command", key: "Change receiver"));
499 ConnectionEdit::setTarget(con, obj_name);
500
501 QObject *targetObject = con->object(type: EndPoint::Target);
502 if (!memberFunctionListContains(core: m_form_window->core(), object: targetObject, type: SlotMember, signature: con->slot()))
503 undoStack()->push(cmd: new SetMemberCommand(con, EndPoint::Target, QString(), this));
504
505 m_form_window->endCommand();
506}
507
508void SignalSlotEditor::addEmptyConnection()
509{
510 SignalSlotConnection *con = new SignalSlotConnection(this);
511 undoStack()->push(cmd: new AddConnectionCommand(this, con));
512}
513
514} // namespace qdesigner_internal
515
516QT_END_NAMESPACE
517

source code of qttools/src/designer/src/components/signalsloteditor/signalsloteditor.cpp