1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 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 "selectsignaldialog_p.h" |
30 | |
31 | #include "ui_selectsignaldialog.h" |
32 | |
33 | #include <QtDesigner/abstractformeditor.h> |
34 | #include <QtDesigner/abstractpromotioninterface.h> |
35 | |
36 | #include "abstractintrospection_p.h" |
37 | #include "metadatabase_p.h" |
38 | #include "widgetdatabase_p.h" |
39 | |
40 | #include <QtWidgets/qapplication.h> |
41 | #include <QtWidgets/qdesktopwidget.h> |
42 | #include <QtWidgets/qpushbutton.h> |
43 | #include <QtGui/qstandarditemmodel.h> |
44 | #include <QtCore/qitemselectionmodel.h> |
45 | #include <QtCore/qvariant.h> |
46 | #include <QtCore/qvector.h> |
47 | |
48 | #include <algorithm> |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | namespace qdesigner_internal { |
53 | |
54 | enum { MethodRole = Qt::UserRole + 1 }; |
55 | |
56 | using Methods = QVector<SelectSignalDialog::Method>; |
57 | |
58 | SelectSignalDialog::SelectSignalDialog(QWidget *parent) |
59 | : QDialog(parent) |
60 | , m_ui(new Ui::SelectSignalDialog) |
61 | , m_model(new QStandardItemModel(0, 1, this)) |
62 | { |
63 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); |
64 | m_ui->setupUi(this); |
65 | m_okButton = m_ui->buttonBox->button(which: QDialogButtonBox::Ok); |
66 | |
67 | m_ui->signalList->setModel(m_model); |
68 | connect(sender: m_ui->signalList->selectionModel(), signal: &QItemSelectionModel::currentChanged, |
69 | receiver: this, slot: &SelectSignalDialog::currentChanged); |
70 | connect(sender: m_ui->signalList, signal: &QTreeView::activated, |
71 | receiver: this, slot: &SelectSignalDialog::activated); |
72 | const QRect availableGeometry = QApplication::desktop()->availableGeometry(widget: this); |
73 | resize(w: availableGeometry.width() / 5, h: availableGeometry.height() / 2); |
74 | } |
75 | |
76 | SelectSignalDialog::~SelectSignalDialog() |
77 | { |
78 | delete m_ui; |
79 | } |
80 | |
81 | SelectSignalDialog::Method SelectSignalDialog::selectedMethod() const |
82 | { |
83 | return methodFromIndex(m_ui->signalList->currentIndex()); |
84 | } |
85 | |
86 | SelectSignalDialog::Method SelectSignalDialog::methodFromIndex(const QModelIndex &index) const |
87 | { |
88 | if (index.isValid()) { |
89 | const QStandardItem *item = m_model->itemFromIndex(index); |
90 | const QVariant data = item->data(role: MethodRole); |
91 | if (data.canConvert<Method>()) |
92 | return data.value<Method>(); |
93 | } |
94 | return Method(); |
95 | } |
96 | |
97 | static QStandardItem *createTopLevelItem(const QString &text) |
98 | { |
99 | QStandardItem *result = new QStandardItem(text); |
100 | result->setFlags(Qt::ItemIsEnabled); |
101 | return result; |
102 | } |
103 | |
104 | static bool signatureLessThan(const SelectSignalDialog::Method &m1, const SelectSignalDialog::Method &m2) |
105 | { |
106 | return m1.signature.compare(s: m2.signature) < 0; |
107 | } |
108 | |
109 | // Append a class with alphabetically sorted methods to the model |
110 | static void appendClass(const QString &className, Methods methods, QStandardItemModel *model) |
111 | { |
112 | if (methods.isEmpty()) |
113 | return; |
114 | std::sort(first: methods.begin(), last: methods.end(), comp: signatureLessThan); |
115 | QStandardItem *topLevelItem = createTopLevelItem(text: className); |
116 | model->appendRow(aitem: topLevelItem); |
117 | for (const SelectSignalDialog::Method &m : qAsConst(t&: methods)) { |
118 | QStandardItem *item = new QStandardItem(m.signature); |
119 | item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); |
120 | item->setData(value: QVariant::fromValue(value: m), role: MethodRole); |
121 | topLevelItem->appendRow(aitem: item); |
122 | } |
123 | } |
124 | |
125 | static QString declaredInClass(const QDesignerMetaObjectInterface *metaObject, const QString &member) |
126 | { |
127 | // Find class whose superclass does not contain the method. |
128 | const QDesignerMetaObjectInterface *meta = metaObject; |
129 | |
130 | for (;;) { |
131 | const QDesignerMetaObjectInterface *tmpMeta = meta->superClass(); |
132 | if (tmpMeta == nullptr) |
133 | break; |
134 | if (tmpMeta->indexOfMethod(method: member) == -1) |
135 | break; |
136 | meta = tmpMeta; |
137 | } |
138 | return meta->className(); |
139 | } |
140 | |
141 | static inline QString msgNoSignals() |
142 | { |
143 | return QCoreApplication::translate(context: "QDesignerTaskMenu" , key: "no signals available" ); |
144 | } |
145 | |
146 | void SelectSignalDialog::populate(QDesignerFormEditorInterface *core, QObject *object, |
147 | const QString &defaultSignal) |
148 | { |
149 | m_okButton->setEnabled(false); |
150 | |
151 | populateModel(core, object); |
152 | |
153 | if (m_model->rowCount() == 0) { |
154 | m_model->appendRow(aitem: createTopLevelItem(text: msgNoSignals())); |
155 | return; |
156 | } |
157 | |
158 | m_ui->signalList->expandAll(); |
159 | m_ui->signalList->resizeColumnToContents(column: 0); |
160 | |
161 | QModelIndex selectedIndex; |
162 | if (defaultSignal.isEmpty()) { |
163 | selectedIndex = m_model->index(row: 0, column: 0, parent: m_model->index(row: 0, column: 0, parent: QModelIndex())); // first method |
164 | } else { |
165 | const auto items = m_model->findItems(text: defaultSignal, flags: Qt::MatchExactly | Qt::MatchRecursive, column: 0); |
166 | if (!items.isEmpty()) |
167 | selectedIndex = m_model->indexFromItem(item: items.constFirst()); |
168 | } |
169 | |
170 | if (selectedIndex.isValid()) |
171 | m_ui->signalList->setCurrentIndex(selectedIndex); |
172 | } |
173 | |
174 | void SelectSignalDialog::populateModel(QDesignerFormEditorInterface *core, QObject *object) |
175 | { |
176 | m_model->removeRows(row: 0, count: m_model->rowCount()); |
177 | |
178 | // Populate methods list in reverse order, starting from derived class. |
179 | if (object->isWidgetType() && qobject_cast<WidgetDataBase *>(object: core->widgetDataBase())) { |
180 | const QDesignerWidgetDataBaseInterface *db = core->widgetDataBase(); |
181 | const QString promotedClassName = promotedCustomClassName(core, w: static_cast<QWidget *>(object)); |
182 | const int index = db->indexOfClassName(className: promotedClassName); |
183 | if (index >= 0) { |
184 | Methods methods; |
185 | WidgetDataBaseItem* item = static_cast<WidgetDataBaseItem*>(db->item(index)); |
186 | const QStringList fakeSignals = item->fakeSignals(); |
187 | for (const QString &fakeSignal : fakeSignals) |
188 | methods.append(t: SelectSignalDialog::Method(promotedClassName, fakeSignal)); |
189 | appendClass(className: promotedClassName, methods, model: m_model); |
190 | } |
191 | } |
192 | |
193 | // fake signals |
194 | if (qdesigner_internal::MetaDataBase *metaDataBase |
195 | = qobject_cast<qdesigner_internal::MetaDataBase *>(object: core->metaDataBase())) { |
196 | Methods methods; |
197 | qdesigner_internal::MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(object); |
198 | Q_ASSERT(item); |
199 | const QStringList fakeSignals = item->fakeSignals(); |
200 | for (const QString &fakeSignal : fakeSignals) |
201 | methods.append(t: SelectSignalDialog::Method(item->customClassName(), fakeSignal)); |
202 | appendClass(className: item->customClassName(), methods, model: m_model); |
203 | } |
204 | |
205 | // "real" signals |
206 | if (const QDesignerMetaObjectInterface *metaObject = core->introspection()->metaObject(object)) { |
207 | QString lastClassName; |
208 | Methods methods; |
209 | for (int i = metaObject->methodCount() - 1; i >= 0; --i) { |
210 | const QDesignerMetaMethodInterface *metaMethod = metaObject->method(index: i); |
211 | if (metaMethod->methodType() == QDesignerMetaMethodInterface::Signal) { |
212 | const QString signature = metaMethod->signature(); |
213 | const QString className = declaredInClass(metaObject, member: signature); |
214 | if (lastClassName.isEmpty()) { |
215 | lastClassName = className; |
216 | } else if (className != lastClassName) { |
217 | appendClass(className: lastClassName, methods, model: m_model); |
218 | lastClassName = className; |
219 | methods.clear(); |
220 | } |
221 | methods.append(t: SelectSignalDialog::Method(className, signature, |
222 | metaMethod->parameterNames())); |
223 | } |
224 | } |
225 | appendClass(className: lastClassName, methods, model: m_model); |
226 | } |
227 | } |
228 | |
229 | void SelectSignalDialog::activated(const QModelIndex &index) |
230 | { |
231 | if (methodFromIndex(index).isValid()) |
232 | m_okButton->animateClick(); |
233 | } |
234 | |
235 | void SelectSignalDialog::currentChanged(const QModelIndex ¤t, const QModelIndex &) |
236 | { |
237 | m_okButton->setEnabled(methodFromIndex(index: current).isValid()); |
238 | } |
239 | |
240 | } // namespace qdesigner_internal |
241 | |
242 | QT_END_NAMESPACE |
243 | |