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 "signalslotdialog_p.h" 30 #include "ui_signalslotdialog.h" 31 #include "metadatabase_p.h" 32 #include "widgetdatabase_p.h" 33 34 #include "qdesigner_formwindowcommand_p.h" 35 #include "iconloader_p.h" 36 37 #include <QtDesigner/membersheet.h> 38 #include <QtDesigner/qextensionmanager.h> 39 #include <QtDesigner/abstractformeditor.h> 40 #include <QtDesigner/abstractformwindow.h> 41 #include <QtDesigner/abstractwidgetfactory.h> 42 #include <abstractdialoggui_p.h> 43 44 #include <QtGui/qstandarditemmodel.h> 45 #include <QtGui/qvalidator.h> 46 #include <QtWidgets/qitemdelegate.h> 47 #include <QtWidgets/qlineedit.h> 48 #include <QtWidgets/qapplication.h> 49 #include <QtWidgets/qmessagebox.h> 50 51 #include <QtCore/qregularexpression.h> 52 #include <QtCore/qdebug.h> 53 54 QT_BEGIN_NAMESPACE 55 56 // Regexp to match a function signature, arguments potentially 57 // with namespace colons. 58 static const char *signatureRegExp = "^[\\w+_]+\\(([\\w+:]\\*?,?)*\\)$"; 59 static const char *methodNameRegExp = "^[\\w+_]+$"; 60 61 static QStandardItem *createEditableItem(const QString &text) 62 { 63 QStandardItem *rc = new QStandardItem(text); 64 rc->setFlags(Qt::ItemIsEnabled|Qt::ItemIsEditable|Qt::ItemIsSelectable); 65 return rc; 66 } 67 68 static QStandardItem *createDisabledItem(const QString &text) 69 { 70 QStandardItem *rc = new QStandardItem(text); 71 Qt::ItemFlags flags = rc->flags(); 72 rc->setFlags(flags & ~(Qt::ItemIsEnabled|Qt::ItemIsEditable|Qt::ItemIsSelectable)); 73 return rc; 74 } 75 76 static void fakeMethodsFromMetaDataBase(QDesignerFormEditorInterface *core, QObject *o, QStringList &slotList, QStringList &signalList) 77 { 78 slotList.clear(); 79 signalList.clear(); 80 if (qdesigner_internal::MetaDataBase *metaDataBase = qobject_cast<qdesigner_internal::MetaDataBase *>(core->metaDataBase())) 81 if (const qdesigner_internal::MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(o)) { 82 slotList = item->fakeSlots(); 83 signalList = item->fakeSignals(); 84 } 85 } 86 87 static void fakeMethodsToMetaDataBase(QDesignerFormEditorInterface *core, QObject *o, const QStringList &slotList, const QStringList &signalList) 88 { 89 if (qdesigner_internal::MetaDataBase *metaDataBase = qobject_cast<qdesigner_internal::MetaDataBase *>(core->metaDataBase())) { 90 qdesigner_internal::MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(o); 91 Q_ASSERT(item); 92 item->setFakeSlots(slotList); 93 item->setFakeSignals(signalList); 94 } 95 } 96 97 static void existingMethodsFromMemberSheet(QDesignerFormEditorInterface *core, 98 QObject *o, 99 QStringList &slotList, QStringList &signalList) 100 { 101 slotList.clear(); 102 signalList.clear(); 103 104 QDesignerMemberSheetExtension *msheet = qt_extension<QDesignerMemberSheetExtension*>(core->extensionManager(), o); 105 if (!msheet) 106 return; 107 108 for (int i = 0, count = msheet->count(); i < count; ++i) 109 if (msheet->isVisible(i)) { 110 if (msheet->isSlot(i)) 111 slotList += msheet->signature(i); 112 else 113 if (msheet->isSignal(i)) 114 signalList += msheet->signature(i); 115 } 116 } 117 118 namespace { 119 // Internal helper class: A Delegate that validates using RegExps and additionally checks 120 // on closing (adds missing parentheses). 121 class SignatureDelegate : public QItemDelegate { 122 public: 123 SignatureDelegate(QObject * parent = nullptr); 124 QWidget * createEditor (QWidget * parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const override; 125 void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; 126 127 private: 128 const QRegularExpression m_signatureRegexp; 129 const QRegularExpression m_methodNameRegexp; 130 }; 131 132 SignatureDelegate::SignatureDelegate(QObject * parent) : 133 QItemDelegate(parent), 134 m_signatureRegexp(QLatin1String(signatureRegExp)), 135 m_methodNameRegexp(QLatin1String(methodNameRegExp)) 136 { 137 Q_ASSERT(m_signatureRegexp.isValid()); 138 Q_ASSERT(m_methodNameRegexp.isValid()); 139 } 140 141 QWidget * SignatureDelegate::createEditor ( QWidget * parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const 142 { 143 QWidget *rc = QItemDelegate::createEditor(parent, option, index); 144 QLineEdit *le = qobject_cast<QLineEdit *>(rc); 145 Q_ASSERT(le); 146 le->setValidator(new QRegularExpressionValidator(m_signatureRegexp, le)); 147 return rc; 148 } 149 150 void SignatureDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const 151 { 152 QLineEdit *le = qobject_cast<QLineEdit *>(editor); 153 Q_ASSERT(le); 154 // Did the user just type a name? .. Add parentheses 155 QString signature = le->text(); 156 if (!m_signatureRegexp.match(signature).hasMatch()) { 157 if (m_methodNameRegexp.match(signature).hasMatch()) { 158 signature += QStringLiteral("()"); 159 le->setText(signature); 160 } else { 161 return; 162 } 163 } 164 QItemDelegate::setModelData(editor, model, index); 165 } 166 167 // ------ FakeMethodMetaDBCommand: Undo Command to change fake methods in the meta DB. 168 class FakeMethodMetaDBCommand : public qdesigner_internal::QDesignerFormWindowCommand { 169 170 public: 171 explicit FakeMethodMetaDBCommand(QDesignerFormWindowInterface *formWindow); 172 173 void init(QObject *o, 174 const QStringList &oldFakeSlots, const QStringList &oldFakeSignals, 175 const QStringList &newFakeSlots, const QStringList &newFakeSignals); 176 177 void undo() override 178 { fakeMethodsToMetaDataBase(core(), m_object, m_oldFakeSlots, m_oldFakeSignals); } 179 void redo() override 180 { fakeMethodsToMetaDataBase(core(), m_object, m_newFakeSlots, m_newFakeSignals); } 181 182 private: 183 QObject *m_object; 184 QStringList m_oldFakeSlots; 185 QStringList m_oldFakeSignals; 186 QStringList m_newFakeSlots; 187 QStringList m_newFakeSignals; 188 }; 189 190 FakeMethodMetaDBCommand::FakeMethodMetaDBCommand(QDesignerFormWindowInterface *formWindow) : 191 qdesigner_internal::QDesignerFormWindowCommand(QApplication::translate("Command", "Change signals/slots"), formWindow), 192 m_object(nullptr) 193 { 194 } 195 196 void FakeMethodMetaDBCommand::init(QObject *o, 197 const QStringList &oldFakeSlots, const QStringList &oldFakeSignals, 198 const QStringList &newFakeSlots, const QStringList &newFakeSignals) 199 { 200 m_object = o; 201 m_oldFakeSlots = oldFakeSlots; 202 m_oldFakeSignals = oldFakeSignals; 203 m_newFakeSlots = newFakeSlots; 204 m_newFakeSignals = newFakeSignals; 205 } 206 } 207 208 namespace qdesigner_internal { 209 210 // ------ SignalSlotDialogData 211 void SignalSlotDialogData::clear() 212 { 213 m_existingMethods.clear(); 214 m_fakeMethods.clear(); 215 } 216 217 // ------ SignatureModel 218 SignatureModel::SignatureModel(QObject *parent) : 219 QStandardItemModel(parent) 220 { 221 } 222 223 bool SignatureModel::setData(const QModelIndex &index, const QVariant &value, int role) 224 { 225 if (role != Qt::EditRole) 226 return QStandardItemModel::setData(index, value, role); 227 // check via signal (unless it is the same), in which case we can't be bothered. 228 const QStandardItem *item = itemFromIndex(index); 229 Q_ASSERT(item); 230 const QString signature = value.toString(); 231 if (item->text() == signature) 232 return true; 233 234 bool ok = true; 235 emit checkSignature(signature, &ok); 236 if (!ok) 237 return false; 238 239 return QStandardItemModel::setData(index, value, role); 240 } 241 242 // ------ SignaturePanel 243 SignaturePanel::SignaturePanel(QObject *parent, QListView *listView, QToolButton *addButton, QToolButton *removeButton, const QString &newPrefix) : 244 QObject(parent), 245 m_newPrefix(newPrefix), 246 m_model(new SignatureModel(this)), 247 m_listView(listView), 248 m_removeButton(removeButton) 249 { 250 m_removeButton->setEnabled(false); 251 252 connect(addButton, &QAbstractButton::clicked, this, &SignaturePanel::slotAdd); 253 connect(m_removeButton, &QAbstractButton::clicked, this, &SignaturePanel::slotRemove); 254 255 m_listView->setModel(m_model); 256 SignatureDelegate *delegate = new SignatureDelegate(this); 257 m_listView->setItemDelegate(delegate); 258 connect(m_model, &SignatureModel::checkSignature, 259 this, &SignaturePanel::checkSignature); 260 connect(m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, 261 this, &SignaturePanel::slotSelectionChanged); 262 } 263 264 void SignaturePanel::slotAdd() 265 { 266 m_listView->selectionModel()->clearSelection(); 267 // find unique name 268 for (int i = 1; ; i++) { 269 QString newSlot = m_newPrefix; 270 newSlot += QString::number(i); // Always add number, Avoid setting 'slot' for first entry 271 newSlot += QLatin1Char('('); 272 // check for function name independent of parameters 273 if (m_model->findItems(newSlot, Qt::MatchStartsWith, 0).isEmpty()) { 274 newSlot += QLatin1Char(')'); 275 QStandardItem * item = createEditableItem(newSlot); 276 m_model->appendRow(item); 277 const QModelIndex index = m_model->indexFromItem (item); 278 m_listView->setCurrentIndex (index); 279 m_listView->edit(index); 280 return; 281 } 282 } 283 } 284 285 int SignaturePanel::count(const QString &signature) const 286 { 287 return m_model->findItems(signature).size(); 288 } 289 290 void SignaturePanel::slotRemove() 291 { 292 const QModelIndexList selectedIndexes = m_listView->selectionModel()->selectedIndexes (); 293 if (selectedIndexes.isEmpty()) 294 return; 295 296 closeEditor(); 297 // scroll to previous 298 if (const int row = selectedIndexes.constFirst().row()) 299 m_listView->setCurrentIndex (selectedIndexes.constFirst().sibling(row - 1, 0)); 300 301 for (int i = selectedIndexes.size() - 1; i >= 0; i--) 302 qDeleteAll(m_model->takeRow(selectedIndexes[i].row())); 303 } 304 305 void SignaturePanel::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) 306 { 307 m_removeButton->setEnabled(!selected.indexes().isEmpty()); 308 } 309 310 void SignaturePanel::setData(const SignalSlotDialogData &d) 311 { 312 m_model->clear(); 313 314 QStandardItem *lastExisting = nullptr; 315 for (const QString &s : d.m_existingMethods) { 316 lastExisting = createDisabledItem(s); 317 m_model->appendRow(lastExisting); 318 } 319 for (const QString &s : d.m_fakeMethods) 320 m_model->appendRow(createEditableItem(s)); 321 if (lastExisting) 322 m_listView->scrollTo(m_model->indexFromItem(lastExisting)); 323 } 324 325 QStringList SignaturePanel::fakeMethods() const 326 { 327 QStringList rc; 328 if (const int rowCount = m_model->rowCount()) 329 for (int i = 0; i < rowCount; i++) { 330 const QStandardItem *item = m_model->item(i); 331 if (item->flags() & Qt::ItemIsEditable) 332 rc += item->text(); 333 } 334 return rc; 335 } 336 337 void SignaturePanel::closeEditor() 338 { 339 const QModelIndex idx = m_listView->currentIndex(); 340 if (idx.isValid()) 341 m_listView->closePersistentEditor(idx); 342 } 343 344 // ------ SignalSlotDialog 345 346 SignalSlotDialog::SignalSlotDialog(QDesignerDialogGuiInterface *dialogGui, QWidget *parent, FocusMode mode) : 347 QDialog(parent), 348 m_focusMode(mode), 349 m_ui(new Ui::SignalSlotDialogClass), 350 m_dialogGui(dialogGui) 351 { 352 setModal(true); 353 m_ui->setupUi(this); 354 355 const QIcon plusIcon = qdesigner_internal::createIconSet(QString::fromUtf8("plus.png")); 356 const QIcon minusIcon = qdesigner_internal::createIconSet(QString::fromUtf8("minus.png")); 357 m_ui->addSlotButton->setIcon(plusIcon); 358 m_ui->removeSlotButton->setIcon(minusIcon); 359 m_ui->addSignalButton->setIcon(plusIcon); 360 m_ui->removeSignalButton->setIcon(minusIcon); 361 362 m_slotPanel = new SignaturePanel(this, m_ui->slotListView, m_ui->addSlotButton, m_ui->removeSlotButton, QStringLiteral("slot")); 363 m_signalPanel = new SignaturePanel(this, m_ui->signalListView, m_ui->addSignalButton, m_ui->removeSignalButton, QStringLiteral("signal")); 364 connect(m_slotPanel, &SignaturePanel::checkSignature, 365 this, &SignalSlotDialog::slotCheckSignature); 366 connect(m_signalPanel, &SignaturePanel::checkSignature, 367 this, &SignalSlotDialog::slotCheckSignature); 368 369 connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 370 connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 371 372 switch(m_focusMode) { 373 case FocusSlots: 374 m_ui->slotListView->setFocus(Qt::OtherFocusReason); 375 break; 376 case FocusSignals: 377 m_ui->signalListView->setFocus(Qt::OtherFocusReason); 378 break; 379 } 380 } 381 382 SignalSlotDialog::~SignalSlotDialog() 383 { 384 delete m_ui; 385 } 386 387 void SignalSlotDialog::slotCheckSignature(const QString &signature, bool *ok) 388 { 389 QString errorMessage; 390 do { 391 if (m_slotPanel->count(signature)) { 392 errorMessage = tr("There is already a slot with the signature '%1'.").arg(signature); 393 *ok = false; 394 break; 395 } 396 if (m_signalPanel->count(signature)) { 397 errorMessage = tr("There is already a signal with the signature '%1'.").arg(signature); 398 *ok = false; 399 break; 400 } 401 } while (false); 402 if (!*ok) 403 m_dialogGui->message(this, QDesignerDialogGuiInterface::SignalSlotDialogMessage, 404 QMessageBox::Warning, tr("%1 - Duplicate Signature").arg(windowTitle()), errorMessage, QMessageBox::Close); 405 } 406 407 QDialog::DialogCode SignalSlotDialog::showDialog(SignalSlotDialogData &slotData, SignalSlotDialogData &signalData) 408 { 409 m_slotPanel->setData(slotData); 410 m_signalPanel->setData(signalData); 411 412 const DialogCode rc = static_cast<DialogCode>(exec()); 413 if (rc == Rejected) 414 return rc; 415 416 slotData.m_fakeMethods = m_slotPanel->fakeMethods(); 417 signalData.m_fakeMethods = m_signalPanel->fakeMethods(); 418 return rc; 419 } 420 421 bool SignalSlotDialog::editMetaDataBase(QDesignerFormWindowInterface *fw, QObject *object, QWidget *parent, FocusMode mode) 422 { 423 QDesignerFormEditorInterface *core = fw->core(); 424 SignalSlotDialog dlg(core->dialogGui(), parent, mode); 425 dlg.setWindowTitle(tr("Signals/Slots of %1").arg(object->objectName())); 426 427 SignalSlotDialogData slotData; 428 SignalSlotDialogData signalData; 429 430 existingMethodsFromMemberSheet(core, object, slotData.m_existingMethods, signalData.m_existingMethods); 431 fakeMethodsFromMetaDataBase(core, object, slotData.m_fakeMethods, signalData.m_fakeMethods); 432 433 const QStringList oldSlots = slotData.m_fakeMethods; 434 const QStringList oldSignals = signalData.m_fakeMethods; 435 436 if (dlg.showDialog(slotData, signalData) == QDialog::Rejected) 437 return false; 438 439 if (oldSlots == slotData.m_fakeMethods && oldSignals == signalData.m_fakeMethods) 440 return false; 441 442 FakeMethodMetaDBCommand *cmd = new FakeMethodMetaDBCommand(fw); 443 cmd->init(object, oldSlots, oldSignals, slotData.m_fakeMethods, signalData.m_fakeMethods); 444 fw->commandHistory()->push(cmd); 445 return true; 446 } 447 448 bool SignalSlotDialog::editPromotedClass(QDesignerFormEditorInterface *core, const QString &promotedClassName, QWidget *parent, FocusMode mode) 449 { 450 const int index = core->widgetDataBase()->indexOfClassName(promotedClassName); 451 if (index == -1) 452 return false; 453 454 const QString baseClassName = core->widgetDataBase()->item(index)->extends(); 455 if (baseClassName.isEmpty()) 456 return false; 457 458 QWidget *widget = core->widgetFactory()->createWidget(baseClassName, nullptr); 459 if (!widget) 460 return false; 461 const bool rc = editPromotedClass(core, promotedClassName, widget, parent, mode); 462 widget->deleteLater(); 463 return rc; 464 } 465 466 bool SignalSlotDialog::editPromotedClass(QDesignerFormEditorInterface *core, QObject *baseObject, QWidget *parent, FocusMode mode) 467 { 468 if (!baseObject->isWidgetType()) 469 return false; 470 471 const QString promotedClassName = promotedCustomClassName(core, qobject_cast<QWidget*>(baseObject)); 472 if (promotedClassName.isEmpty()) 473 return false; 474 return editPromotedClass(core, promotedClassName, baseObject, parent, mode); 475 } 476 477 478 bool SignalSlotDialog::editPromotedClass(QDesignerFormEditorInterface *core, const QString &promotedClassName, QObject *object, QWidget *parent, FocusMode mode) 479 { 480 WidgetDataBase *db = qobject_cast<WidgetDataBase *>(core->widgetDataBase()); 481 if (!db) 482 return false; 483 484 const int index = core->widgetDataBase()->indexOfClassName(promotedClassName); 485 if (index == -1) 486 return false; 487 488 WidgetDataBaseItem* item = static_cast<WidgetDataBaseItem*>(db->item(index)); 489 490 SignalSlotDialogData slotData; 491 SignalSlotDialogData signalData; 492 493 existingMethodsFromMemberSheet(core, object, slotData.m_existingMethods, signalData.m_existingMethods); 494 slotData.m_fakeMethods = item->fakeSlots(); 495 signalData.m_fakeMethods = item->fakeSignals(); 496 497 const QStringList oldSlots = slotData.m_fakeMethods; 498 const QStringList oldSignals = signalData.m_fakeMethods; 499 500 SignalSlotDialog dlg(core->dialogGui(), parent, mode); 501 dlg.setWindowTitle(tr("Signals/Slots of %1").arg(promotedClassName)); 502 503 if (dlg.showDialog(slotData, signalData) == QDialog::Rejected) 504 return false; 505 506 if (oldSlots == slotData.m_fakeMethods && oldSignals == signalData.m_fakeMethods) 507 return false; 508 509 item->setFakeSlots(slotData.m_fakeMethods); 510 item->setFakeSignals(signalData.m_fakeMethods); 511 512 return true; 513 } 514 515 } 516 517 QT_END_NAMESPACE 518