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 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | namespace qdesigner_internal { |
52 | |
53 | /******************************************************************************* |
54 | ** SignalSlotConnection |
55 | */ |
56 | |
57 | SignalSlotConnection::SignalSlotConnection(ConnectionEdit *edit, QWidget *source, QWidget *target) |
58 | : Connection(edit, source, target) |
59 | { |
60 | } |
61 | |
62 | DomConnection *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 | |
95 | void SignalSlotConnection::setSignal(const QString &signal) |
96 | { |
97 | m_signal = signal; |
98 | setLabel(type: EndPoint::Source, text: m_signal); |
99 | } |
100 | |
101 | void SignalSlotConnection::setSlot(const QString &slot) |
102 | { |
103 | m_slot = slot; |
104 | setLabel(type: EndPoint::Target, text: m_slot); |
105 | } |
106 | |
107 | QString 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 | |
119 | QString 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 | |
130 | void SignalSlotConnection::updateVisibility() |
131 | { |
132 | Connection::updateVisibility(); |
133 | if (isVisible() && (signal().isEmpty() || slot().isEmpty())) |
134 | setVisible(false); |
135 | } |
136 | |
137 | QString 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 | |
143 | SignalSlotConnection::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 | |
171 | class SetMemberCommand : public QUndoCommand, public CETypes |
172 | { |
173 | public: |
174 | SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type, |
175 | const QString &member, SignalSlotEditor *editor); |
176 | void redo() override; |
177 | void undo() override; |
178 | private: |
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 | |
186 | SetMemberCommand::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 | |
200 | void 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 | |
211 | void 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 |
223 | class ModifyConnectionCommand : public QDesignerFormWindowCommand |
224 | { |
225 | public: |
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 | |
233 | private: |
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 | |
241 | ModifyConnectionCommand::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 | |
254 | void ModifyConnectionCommand::redo() |
255 | { |
256 | m_conn->setSignal(m_newSignal); |
257 | m_conn->setSlot(m_newSlot); |
258 | } |
259 | |
260 | void ModifyConnectionCommand::undo() |
261 | { |
262 | m_conn->setSignal(m_oldSignal); |
263 | m_conn->setSlot(m_oldSlot); |
264 | } |
265 | |
266 | /******************************************************************************* |
267 | ** SignalSlotEditor |
268 | */ |
269 | |
270 | SignalSlotEditor::SignalSlotEditor(QDesignerFormWindowInterface *form_window, QWidget *parent) : |
271 | ConnectionEdit(parent, form_window), |
272 | m_form_window(form_window), |
273 | m_showAllSignalsSlots(false) |
274 | { |
275 | } |
276 | |
277 | void 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 | |
300 | Connection *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 | |
321 | DomConnections *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 | |
353 | QObject *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 | |
370 | void 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 | |
416 | static 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 | |
430 | QWidget *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 | |
449 | void 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 | |
461 | void 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 | |
473 | void 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 | |
491 | void 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 | |
508 | void SignalSlotEditor::addEmptyConnection() |
509 | { |
510 | SignalSlotConnection *con = new SignalSlotConnection(this); |
511 | undoStack()->push(cmd: new AddConnectionCommand(this, con)); |
512 | } |
513 | |
514 | } // namespace qdesigner_internal |
515 | |
516 | QT_END_NAMESPACE |
517 | |