| 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 | |