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
SignalSlotConnection(ConnectionEdit * edit,QWidget * source,QWidget * target)57 SignalSlotConnection::SignalSlotConnection(ConnectionEdit *edit, QWidget *source, QWidget *target)
58 : Connection(edit, source, target)
59 {
60 }
61
toUi() const62 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(EndPoint::Source);
75 QPoint tp = endPointPos(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(hint);
82
83 hint = new DomConnectionHint;
84 hint->setAttributeType(QStringLiteral("destinationlabel"));
85 hint->setElementX(tp.x());
86 hint->setElementY(tp.y());
87 list.append(hint);
88
89 hints->setElementHint(list);
90 result->setElementHints(hints);
91
92 return result;
93 }
94
setSignal(const QString & signal)95 void SignalSlotConnection::setSignal(const QString &signal)
96 {
97 m_signal = signal;
98 setLabel(EndPoint::Source, m_signal);
99 }
100
setSlot(const QString & slot)101 void SignalSlotConnection::setSlot(const QString &slot)
102 {
103 m_slot = slot;
104 setLabel(EndPoint::Target, m_slot);
105 }
106
sender() const107 QString SignalSlotConnection::sender() const
108 {
109 QObject *source = object(EndPoint::Source);
110 if (!source)
111 return QString();
112
113 SignalSlotEditor *edit = qobject_cast<SignalSlotEditor*>(this->edit());
114 Q_ASSERT(edit != nullptr);
115
116 return realObjectName(edit->formWindow()->core(), source);
117 }
118
receiver() const119 QString SignalSlotConnection::receiver() const
120 {
121 QObject *sink = object(EndPoint::Target);
122 if (!sink)
123 return QString();
124
125 SignalSlotEditor *edit = qobject_cast<SignalSlotEditor*>(this->edit());
126 Q_ASSERT(edit != nullptr);
127 return realObjectName(edit->formWindow()->core(), sink);
128 }
129
updateVisibility()130 void SignalSlotConnection::updateVisibility()
131 {
132 Connection::updateVisibility();
133 if (isVisible() && (signal().isEmpty() || slot().isEmpty()))
134 setVisible(false);
135 }
136
toString() const137 QString SignalSlotConnection::toString() const
138 {
139 return QCoreApplication::translate("SignalSlotConnection", "SENDER(%1), SIGNAL(%2), RECEIVER(%3), SLOT(%4)")
140 .arg(sender(), signal(), receiver(), slot());
141 }
142
isValid(const QWidget * background) const143 SignalSlotConnection::State SignalSlotConnection::isValid(const QWidget *background) const
144 {
145 const QObject *source = object(EndPoint::Source);
146 if (!source)
147 return ObjectDeleted;
148
149 const QObject *target = object(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*>(source))
157 if (!background->isAncestorOf(sourceWidget))
158 return NotAncestor;
159
160 if (const QWidget *targetWidget = qobject_cast<const QWidget*>(target))
161 if (!background->isAncestorOf(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
SetMemberCommand(SignalSlotConnection * con,EndPoint::Type type,const QString & member,SignalSlotEditor * editor)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("Command", "Change signal"));
196 else
197 setText(QApplication::translate("Command", "Change slot"));
198 }
199
redo()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(m_con);
209 }
210
undo()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(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
ModifyConnectionCommand(QDesignerFormWindowInterface * form,SignalSlotConnection * conn,const QString & newSignal,const QString & newSlot)241 ModifyConnectionCommand::ModifyConnectionCommand(QDesignerFormWindowInterface *form,
242 SignalSlotConnection *conn,
243 const QString &newSignal,
244 const QString &newSlot) :
245 QDesignerFormWindowCommand(QCoreApplication::translate("Command", "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
redo()254 void ModifyConnectionCommand::redo()
255 {
256 m_conn->setSignal(m_newSignal);
257 m_conn->setSlot(m_newSlot);
258 }
259
undo()260 void ModifyConnectionCommand::undo()
261 {
262 m_conn->setSignal(m_oldSignal);
263 m_conn->setSlot(m_oldSlot);
264 }
265
266 /*******************************************************************************
267 ** SignalSlotEditor
268 */
269
SignalSlotEditor(QDesignerFormWindowInterface * form_window,QWidget * parent)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
modifyConnection(Connection * con)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(EndPoint::Source),
282 sigslot_con->widget(EndPoint::Target),
283 m_form_window->core()->topLevel());
284
285 dialog.setSignalSlot(sigslot_con->signal(), 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
createConnection(QWidget * source,QWidget * destination)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
toUi() const321 DomConnections *SignalSlotEditor::toUi() const
322 {
323 DomConnections *result = new DomConnections;
324 QVector<DomConnection *> list;
325
326 const int count = connectionCount();
327 list.reserve(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())) {
340 case SignalSlotConnection::Valid:
341 list.append(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
objectByName(QWidget * topLevel,const QString & name) const353 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*>(name);
364 const QDesignerMetaDataBaseInterface *mdb = formWindow()->core()->metaDataBase();
365 if (mdb->item(object))
366 return object;
367 return nullptr;
368 }
369
fromUi(const DomConnections * connections,QWidget * parent)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(parent, dom_con->elementSender());
380 if (source == nullptr) {
381 qDebug("SignalSlotEditor::fromUi(): no source widget called \"%s\"",
382 dom_con->elementSender().toUtf8().constData());
383 continue;
384 }
385 QObject *destination = objectByName(parent, dom_con->elementReceiver());
386 if (destination == nullptr) {
387 qDebug("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(EndPoint::Source, source, sp);
409 con->setEndPoint(EndPoint::Target, destination, tp);
410 con->setSignal(dom_con->elementSignal());
411 con->setSlot(dom_con->elementSlot());
412 addConnection(con);
413 }
414 }
415
skipWidget(const QWidget * w)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
widgetAt(const QPoint & pos) const430 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(widget);
439 if (item == nullptr)
440 continue;
441 if (skipWidget(widget))
442 continue;
443 break;
444 }
445
446 return widget;
447 }
448
setSignal(SignalSlotConnection * con,const QString & member)449 void SignalSlotEditor::setSignal(SignalSlotConnection *con, const QString &member)
450 {
451 if (member == con->signal())
452 return;
453
454 m_form_window->beginCommand(QApplication::translate("Command", "Change signal"));
455 undoStack()->push(new SetMemberCommand(con, EndPoint::Source, member, this));
456 if (!signalMatchesSlot(m_form_window->core(), member, con->slot()))
457 undoStack()->push(new SetMemberCommand(con, EndPoint::Target, QString(), this));
458 m_form_window->endCommand();
459 }
460
setSlot(SignalSlotConnection * con,const QString & member)461 void SignalSlotEditor::setSlot(SignalSlotConnection *con, const QString &member)
462 {
463 if (member == con->slot())
464 return;
465
466 m_form_window->beginCommand(QApplication::translate("Command", "Change slot"));
467 undoStack()->push(new SetMemberCommand(con, EndPoint::Target, member, this));
468 if (!signalMatchesSlot(m_form_window->core(), con->signal(), member))
469 undoStack()->push(new SetMemberCommand(con, EndPoint::Source, QString(), this));
470 m_form_window->endCommand();
471 }
472
setSource(Connection * _con,const QString & obj_name)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(QApplication::translate("Command", "Change sender"));
481 ConnectionEdit::setSource(con, obj_name);
482
483 QObject *sourceObject = con->object(EndPoint::Source);
484
485 if (!memberFunctionListContains(m_form_window->core(), sourceObject, SignalMember, con->signal()))
486 undoStack()->push(new SetMemberCommand(con, EndPoint::Source, QString(), this));
487
488 m_form_window->endCommand();
489 }
490
setTarget(Connection * _con,const QString & obj_name)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(QApplication::translate("Command", "Change receiver"));
499 ConnectionEdit::setTarget(con, obj_name);
500
501 QObject *targetObject = con->object(EndPoint::Target);
502 if (!memberFunctionListContains(m_form_window->core(), targetObject, SlotMember, con->slot()))
503 undoStack()->push(new SetMemberCommand(con, EndPoint::Target, QString(), this));
504
505 m_form_window->endCommand();
506 }
507
addEmptyConnection()508 void SignalSlotEditor::addEmptyConnection()
509 {
510 SignalSlotConnection *con = new SignalSlotConnection(this);
511 undoStack()->push(new AddConnectionCommand(this, con));
512 }
513
514 } // namespace qdesigner_internal
515
516 QT_END_NAMESPACE
517