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