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
54QT_BEGIN_NAMESPACE
55
56// Regexp to match a function signature, arguments potentially
57// with namespace colons.
58static const char *signatureRegExp = "^[\\w+_]+\\(([\\w+:]\\*?,?)*\\)$";
59static const char *methodNameRegExp = "^[\\w+_]+$";
60
61static 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
68static 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
76static 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 *>(object: core->metaDataBase()))
81 if (const qdesigner_internal::MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(object: o)) {
82 slotList = item->fakeSlots();
83 signalList = item->fakeSignals();
84 }
85}
86
87static void fakeMethodsToMetaDataBase(QDesignerFormEditorInterface *core, QObject *o, const QStringList &slotList, const QStringList &signalList)
88{
89 if (qdesigner_internal::MetaDataBase *metaDataBase = qobject_cast<qdesigner_internal::MetaDataBase *>(object: core->metaDataBase())) {
90 qdesigner_internal::MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(object: o);
91 Q_ASSERT(item);
92 item->setFakeSlots(slotList);
93 item->setFakeSignals(signalList);
94 }
95}
96
97static 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*>(manager: core->extensionManager(), object: o);
105 if (!msheet)
106 return;
107
108 for (int i = 0, count = msheet->count(); i < count; ++i)
109 if (msheet->isVisible(index: i)) {
110 if (msheet->isSlot(index: i))
111 slotList += msheet->signature(index: i);
112 else
113 if (msheet->isSignal(index: i))
114 signalList += msheet->signature(index: i);
115 }
116}
117
118namespace {
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 *>(object: 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 *>(object: 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(subject: signature).hasMatch()) {
157 if (m_methodNameRegexp.match(subject: 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: core(), o: m_object, slotList: m_oldFakeSlots, signalList: m_oldFakeSignals); }
179 void redo() override
180 { fakeMethodsToMetaDataBase(core: core(), o: m_object, slotList: m_newFakeSlots, signalList: 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(context: "Command", key: "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
208namespace qdesigner_internal {
209
210// ------ SignalSlotDialogData
211void SignalSlotDialogData::clear()
212{
213 m_existingMethods.clear();
214 m_fakeMethods.clear();
215}
216
217// ------ SignatureModel
218SignatureModel::SignatureModel(QObject *parent) :
219 QStandardItemModel(parent)
220{
221}
222
223bool 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: &ok);
236 if (!ok)
237 return false;
238
239 return QStandardItemModel::setData(index, value, role);
240}
241
242// ------ SignaturePanel
243SignaturePanel::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(sender: addButton, signal: &QAbstractButton::clicked, receiver: this, slot: &SignaturePanel::slotAdd);
253 connect(sender: m_removeButton, signal: &QAbstractButton::clicked, receiver: this, slot: &SignaturePanel::slotRemove);
254
255 m_listView->setModel(m_model);
256 SignatureDelegate *delegate = new SignatureDelegate(this);
257 m_listView->setItemDelegate(delegate);
258 connect(sender: m_model, signal: &SignatureModel::checkSignature,
259 receiver: this, slot: &SignaturePanel::checkSignature);
260 connect(sender: m_listView->selectionModel(), signal: &QItemSelectionModel::selectionChanged,
261 receiver: this, slot: &SignaturePanel::slotSelectionChanged);
262}
263
264void 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(text: newSlot, flags: Qt::MatchStartsWith, column: 0).isEmpty()) {
274 newSlot += QLatin1Char(')');
275 QStandardItem * item = createEditableItem(text: newSlot);
276 m_model->appendRow(aitem: 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
285int SignaturePanel::count(const QString &signature) const
286{
287 return m_model->findItems(text: signature).size();
288}
289
290void 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(arow: row - 1, acolumn: 0));
300
301 for (int i = selectedIndexes.size() - 1; i >= 0; i--)
302 qDeleteAll(c: m_model->takeRow(row: selectedIndexes[i].row()));
303}
304
305void SignaturePanel::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &)
306{
307 m_removeButton->setEnabled(!selected.indexes().isEmpty());
308}
309
310void 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(text: s);
317 m_model->appendRow(aitem: lastExisting);
318 }
319 for (const QString &s : d.m_fakeMethods)
320 m_model->appendRow(aitem: createEditableItem(text: s));
321 if (lastExisting)
322 m_listView->scrollTo(index: m_model->indexFromItem(item: lastExisting));
323}
324
325QStringList 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(row: i);
331 if (item->flags() & Qt::ItemIsEditable)
332 rc += item->text();
333 }
334 return rc;
335}
336
337void SignaturePanel::closeEditor()
338{
339 const QModelIndex idx = m_listView->currentIndex();
340 if (idx.isValid())
341 m_listView->closePersistentEditor(index: idx);
342}
343
344// ------ SignalSlotDialog
345
346SignalSlotDialog::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(name: QString::fromUtf8(str: "plus.png"));
356 const QIcon minusIcon = qdesigner_internal::createIconSet(name: QString::fromUtf8(str: "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(sender: m_slotPanel, signal: &SignaturePanel::checkSignature,
365 receiver: this, slot: &SignalSlotDialog::slotCheckSignature);
366 connect(sender: m_signalPanel, signal: &SignaturePanel::checkSignature,
367 receiver: this, slot: &SignalSlotDialog::slotCheckSignature);
368
369 connect(sender: m_ui->buttonBox, signal: &QDialogButtonBox::accepted, receiver: this, slot: &QDialog::accept);
370 connect(sender: m_ui->buttonBox, signal: &QDialogButtonBox::rejected, receiver: this, slot: &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
382SignalSlotDialog::~SignalSlotDialog()
383{
384 delete m_ui;
385}
386
387void SignalSlotDialog::slotCheckSignature(const QString &signature, bool *ok)
388{
389 QString errorMessage;
390 do {
391 if (m_slotPanel->count(signature)) {
392 errorMessage = tr(s: "There is already a slot with the signature '%1'.").arg(a: signature);
393 *ok = false;
394 break;
395 }
396 if (m_signalPanel->count(signature)) {
397 errorMessage = tr(s: "There is already a signal with the signature '%1'.").arg(a: signature);
398 *ok = false;
399 break;
400 }
401 } while (false);
402 if (!*ok)
403 m_dialogGui->message(parent: this, context: QDesignerDialogGuiInterface::SignalSlotDialogMessage,
404 icon: QMessageBox::Warning, title: tr(s: "%1 - Duplicate Signature").arg(a: windowTitle()), text: errorMessage, buttons: QMessageBox::Close);
405}
406
407QDialog::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
421bool 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(s: "Signals/Slots of %1").arg(a: object->objectName()));
426
427 SignalSlotDialogData slotData;
428 SignalSlotDialogData signalData;
429
430 existingMethodsFromMemberSheet(core, o: object, slotList&: slotData.m_existingMethods, signalList&: signalData.m_existingMethods);
431 fakeMethodsFromMetaDataBase(core, o: object, slotList&: slotData.m_fakeMethods, signalList&: 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(o: object, oldFakeSlots: oldSlots, oldFakeSignals: oldSignals, newFakeSlots: slotData.m_fakeMethods, newFakeSignals: signalData.m_fakeMethods);
444 fw->commandHistory()->push(cmd);
445 return true;
446}
447
448bool SignalSlotDialog::editPromotedClass(QDesignerFormEditorInterface *core, const QString &promotedClassName, QWidget *parent, FocusMode mode)
449{
450 const int index = core->widgetDataBase()->indexOfClassName(className: 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(name: baseClassName, parentWidget: nullptr);
459 if (!widget)
460 return false;
461 const bool rc = editPromotedClass(core, promotedClassName, baseObject: widget, parent, m: mode);
462 widget->deleteLater();
463 return rc;
464}
465
466bool 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, w: qobject_cast<QWidget*>(o: baseObject));
472 if (promotedClassName.isEmpty())
473 return false;
474 return editPromotedClass(core, promotedClassName, baseObject, parent, m: mode);
475}
476
477
478bool SignalSlotDialog::editPromotedClass(QDesignerFormEditorInterface *core, const QString &promotedClassName, QObject *object, QWidget *parent, FocusMode mode)
479{
480 WidgetDataBase *db = qobject_cast<WidgetDataBase *>(object: core->widgetDataBase());
481 if (!db)
482 return false;
483
484 const int index = core->widgetDataBase()->indexOfClassName(className: 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, o: object, slotList&: slotData.m_existingMethods, signalList&: 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(s: "Signals/Slots of %1").arg(a: 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
517QT_END_NAMESPACE
518

source code of qttools/src/designer/src/lib/shared/signalslotdialog.cpp