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 "signalsloteditorwindow.h" |
30 | #include "signalsloteditor_p.h" |
31 | #include "signalsloteditor.h" |
32 | #include "signalslot_utils_p.h" |
33 | |
34 | #include <iconloader_p.h> |
35 | #include <spacer_widget_p.h> |
36 | #include <qlayout_widget_p.h> |
37 | |
38 | #include <QtDesigner/abstractformwindow.h> |
39 | #include <QtDesigner/abstractformeditor.h> |
40 | #include <QtDesigner/abstractformwindowmanager.h> |
41 | #include <QtDesigner/qextensionmanager.h> |
42 | #include <QtDesigner/abstractintegration.h> |
43 | #include <QtDesigner/container.h> |
44 | #include <QtDesigner/abstractmetadatabase.h> |
45 | #include <QtDesigner/abstractformwindowcursor.h> |
46 | #include <abstractdialoggui_p.h> |
47 | |
48 | #include <QtCore/qabstractitemmodel.h> |
49 | #include <QtCore/qdebug.h> |
50 | #include <QtWidgets/qaction.h> |
51 | #include <QtWidgets/qbuttongroup.h> |
52 | #include <QtWidgets/qmenu.h> |
53 | #include <QtCore/qsortfilterproxymodel.h> |
54 | #include <QtGui/qstandarditemmodel.h> |
55 | #include <QtWidgets/qcombobox.h> |
56 | #include <QtWidgets/qapplication.h> |
57 | #include <QtWidgets/qitemdelegate.h> |
58 | #include <QtWidgets/qitemeditorfactory.h> |
59 | #include <QtWidgets/qtreeview.h> |
60 | #include <QtWidgets/qheaderview.h> |
61 | #include <QtWidgets/qboxlayout.h> |
62 | #include <QtWidgets/qtoolbutton.h> |
63 | #include <QtWidgets/qbuttongroup.h> |
64 | #include <QtWidgets/qtoolbar.h> |
65 | |
66 | QT_BEGIN_NAMESPACE |
67 | |
68 | // Add suitable form widgets to a list of objects for the signal slot |
69 | // editor. Prevent special widgets from showing up there. |
70 | static void addWidgetToObjectList(const QWidget *w, QStringList &r) |
71 | { |
72 | const QMetaObject *mo = w->metaObject(); |
73 | if (mo != &QLayoutWidget::staticMetaObject && mo != &Spacer::staticMetaObject) { |
74 | const QString name = w->objectName().trimmed(); |
75 | if (!name.isEmpty()) |
76 | r.push_back(t: name); |
77 | } |
78 | } |
79 | |
80 | static QStringList objectNameList(QDesignerFormWindowInterface *form) |
81 | { |
82 | QStringList result; |
83 | |
84 | QWidget *mainContainer = form->mainContainer(); |
85 | if (!mainContainer) |
86 | return result; |
87 | |
88 | // Add main container container pages (QStatusBar, QWizardPages) etc. |
89 | // to the list. Pages of containers on the form are not added, however. |
90 | if (const QDesignerContainerExtension *c = qt_extension<QDesignerContainerExtension *>(manager: form->core()->extensionManager(), object: mainContainer)) { |
91 | const int count = c->count(); |
92 | for (int i = 0 ; i < count; i++) |
93 | addWidgetToObjectList(w: c->widget(index: i), r&: result); |
94 | } |
95 | |
96 | const QDesignerFormWindowCursorInterface *cursor = form->cursor(); |
97 | const int widgetCount = cursor->widgetCount(); |
98 | for (int i = 0; i < widgetCount; ++i) |
99 | addWidgetToObjectList(w: cursor->widget(index: i), r&: result); |
100 | |
101 | const QDesignerMetaDataBaseInterface *mdb = form->core()->metaDataBase(); |
102 | |
103 | // Add managed actions and actions with managed menus |
104 | const auto actions = mainContainer->findChildren<QAction*>(); |
105 | for (QAction *a : actions) { |
106 | if (!a->isSeparator()) { |
107 | if (QMenu * = a->menu()) { |
108 | if (mdb->item(object: menu)) |
109 | result.push_back(t: menu->objectName()); |
110 | } else { |
111 | if (mdb->item(object: a)) |
112 | result.push_back(t: a->objectName()); |
113 | } |
114 | } |
115 | } |
116 | |
117 | // Add managed buttons groups |
118 | const auto buttonGroups = mainContainer->findChildren<QButtonGroup *>(); |
119 | for (QButtonGroup * b : buttonGroups) { |
120 | if (mdb->item(object: b)) |
121 | result.append(t: b->objectName()); |
122 | } |
123 | |
124 | result.sort(); |
125 | return result; |
126 | } |
127 | |
128 | namespace qdesigner_internal { |
129 | |
130 | // ------------ ConnectionModel |
131 | |
132 | ConnectionModel::ConnectionModel(QObject *parent) : |
133 | QAbstractItemModel(parent) |
134 | { |
135 | } |
136 | |
137 | void ConnectionModel::setEditor(SignalSlotEditor *editor) |
138 | { |
139 | if (m_editor == editor) |
140 | return; |
141 | beginResetModel(); |
142 | |
143 | if (m_editor) { |
144 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionAdded, |
145 | receiver: this, slot: &ConnectionModel::connectionAdded); |
146 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionRemoved, |
147 | receiver: this, slot: &ConnectionModel::connectionRemoved); |
148 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::aboutToRemoveConnection, |
149 | receiver: this, slot: &ConnectionModel::aboutToRemoveConnection); |
150 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::aboutToAddConnection, |
151 | receiver: this, slot: &ConnectionModel::aboutToAddConnection); |
152 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionChanged, |
153 | receiver: this, slot: &ConnectionModel::connectionChanged); |
154 | } |
155 | m_editor = editor; |
156 | if (m_editor) { |
157 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionAdded, |
158 | receiver: this, slot: &ConnectionModel::connectionAdded); |
159 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionRemoved, |
160 | receiver: this, slot: &ConnectionModel::connectionRemoved); |
161 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::aboutToRemoveConnection, |
162 | receiver: this, slot: &ConnectionModel::aboutToRemoveConnection); |
163 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::aboutToAddConnection, |
164 | receiver: this, slot: &ConnectionModel::aboutToAddConnection); |
165 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionChanged, |
166 | receiver: this, slot: &ConnectionModel::connectionChanged); |
167 | } |
168 | endResetModel(); |
169 | } |
170 | |
171 | QVariant ConnectionModel::(int section, Qt::Orientation orientation, |
172 | int role) const |
173 | { |
174 | if (orientation == Qt::Vertical || role != Qt::DisplayRole) |
175 | return QVariant(); |
176 | |
177 | static const QVariant senderTitle = tr(s: "Sender" ); |
178 | static const QVariant signalTitle = tr(s: "Signal" ); |
179 | static const QVariant receiverTitle = tr(s: "Receiver" ); |
180 | static const QVariant slotTitle = tr(s: "Slot" ); |
181 | |
182 | switch (section) { |
183 | case 0: |
184 | return senderTitle; |
185 | case 1: |
186 | return signalTitle; |
187 | case 2: |
188 | return receiverTitle; |
189 | case 3: |
190 | return slotTitle; |
191 | } |
192 | return QVariant(); |
193 | } |
194 | |
195 | QModelIndex ConnectionModel::index(int row, int column, |
196 | const QModelIndex &parent) const |
197 | { |
198 | if (parent.isValid() || !m_editor) |
199 | return QModelIndex(); |
200 | if (row < 0 || row >= m_editor->connectionCount()) |
201 | return QModelIndex(); |
202 | return createIndex(arow: row, acolumn: column); |
203 | } |
204 | |
205 | Connection *ConnectionModel::indexToConnection(const QModelIndex &index) const |
206 | { |
207 | if (!index.isValid() || !m_editor) |
208 | return nullptr; |
209 | if (index.row() < 0 || index.row() >= m_editor->connectionCount()) |
210 | return nullptr; |
211 | return m_editor->connection(i: index.row()); |
212 | } |
213 | |
214 | QModelIndex ConnectionModel::connectionToIndex(Connection *con) const |
215 | { |
216 | Q_ASSERT(m_editor); |
217 | return createIndex(arow: m_editor->indexOfConnection(con), acolumn: 0); |
218 | } |
219 | |
220 | QModelIndex ConnectionModel::parent(const QModelIndex&) const |
221 | { |
222 | return QModelIndex(); |
223 | } |
224 | |
225 | int ConnectionModel::rowCount(const QModelIndex &parent) const |
226 | { |
227 | if (parent.isValid() || !m_editor) |
228 | return 0; |
229 | return m_editor->connectionCount(); |
230 | } |
231 | |
232 | int ConnectionModel::columnCount(const QModelIndex &parent) const |
233 | { |
234 | if (parent.isValid()) |
235 | return 0; |
236 | return 4; |
237 | } |
238 | |
239 | const SignalSlotConnection *ConnectionModel::connectionAt(const QModelIndex &index) const |
240 | { |
241 | const int row = index.row(); |
242 | return m_editor != nullptr && row >= 0 && row < m_editor->connectionCount() |
243 | ? static_cast<const SignalSlotConnection*>(m_editor->connection(i: row)) |
244 | : nullptr; |
245 | } |
246 | |
247 | QVariant ConnectionModel::data(const QModelIndex &index, int role) const |
248 | { |
249 | enum { deprecatedMember = 0 }; |
250 | |
251 | const SignalSlotConnection *con = connectionAt(index); |
252 | if (con == nullptr) |
253 | return QVariant(); |
254 | |
255 | // Mark deprecated slots red/italic. Not currently in use (historically for Qt 3 slots in Qt 4), |
256 | // but may be used again in the future. |
257 | switch (role) { |
258 | case Qt::ForegroundRole: |
259 | return deprecatedMember ? QColor(Qt::red) : QVariant(); |
260 | case Qt::FontRole: |
261 | if (deprecatedMember) { |
262 | QFont font = QApplication::font(); |
263 | font.setItalic(true); |
264 | return font; |
265 | } |
266 | return QVariant(); |
267 | case Qt::DisplayRole: |
268 | case Qt::EditRole: |
269 | return ConnectionModel::columnText(con, column: index.column()); |
270 | default: |
271 | break; |
272 | } |
273 | |
274 | return QVariant(); |
275 | } |
276 | |
277 | QString ConnectionModel::columnText(const SignalSlotConnection *con, int column) |
278 | { |
279 | static const QString senderDefault = tr(s: "<sender>" ); |
280 | static const QString signalDefault = tr(s: "<signal>" ); |
281 | static const QString receiverDefault = tr(s: "<receiver>" ); |
282 | static const QString slotDefault = tr(s: "<slot>" ); |
283 | |
284 | switch (column) { |
285 | case 0: { |
286 | const QString sender = con->sender(); |
287 | return sender.isEmpty() ? senderDefault : sender; |
288 | } |
289 | case 1: { |
290 | const QString signalName = con->signal(); |
291 | return signalName.isEmpty() ? signalDefault : signalName; |
292 | } |
293 | case 2: { |
294 | const QString receiver = con->receiver(); |
295 | return receiver.isEmpty() ? receiverDefault : receiver; |
296 | } |
297 | case 3: { |
298 | const QString slotName = con->slot(); |
299 | return slotName.isEmpty() ? slotDefault : slotName; |
300 | } |
301 | } |
302 | return QString(); |
303 | } |
304 | |
305 | bool ConnectionModel::setData(const QModelIndex &index, const QVariant &data, int) |
306 | { |
307 | if (!index.isValid() || !m_editor) |
308 | return false; |
309 | if (data.type() != QVariant::String) |
310 | return false; |
311 | |
312 | SignalSlotConnection *con = static_cast<SignalSlotConnection*>(m_editor->connection(i: index.row())); |
313 | QDesignerFormWindowInterface *form = m_editor->formWindow(); |
314 | |
315 | QString s = data.toString(); |
316 | switch (index.column()) { |
317 | case 0: |
318 | if (!s.isEmpty() && !objectNameList(form).contains(str: s)) |
319 | s.clear(); |
320 | m_editor->setSource(con, obj_name: s); |
321 | break; |
322 | case 1: |
323 | if (!memberFunctionListContains(core: form->core(), object: con->object(type: CETypes::EndPoint::Source), type: SignalMember, signature: s)) |
324 | s.clear(); |
325 | m_editor->setSignal(con, member: s); |
326 | break; |
327 | case 2: |
328 | if (!s.isEmpty() && !objectNameList(form).contains(str: s)) |
329 | s.clear(); |
330 | m_editor->setTarget(con, obj_name: s); |
331 | break; |
332 | case 3: |
333 | if (!memberFunctionListContains(core: form->core(), object: con->object(type: CETypes::EndPoint::Target), type: SlotMember, signature: s)) |
334 | s.clear(); |
335 | m_editor->setSlot(con, member: s); |
336 | break; |
337 | } |
338 | |
339 | return true; |
340 | } |
341 | |
342 | void ConnectionModel::connectionAdded(Connection*) |
343 | { |
344 | endInsertRows(); |
345 | } |
346 | |
347 | void ConnectionModel::connectionRemoved(int) |
348 | { |
349 | endRemoveRows(); |
350 | } |
351 | |
352 | void ConnectionModel::aboutToRemoveConnection(Connection *con) |
353 | { |
354 | Q_ASSERT(m_editor); |
355 | int idx = m_editor->indexOfConnection(con); |
356 | beginRemoveRows(parent: QModelIndex(), first: idx, last: idx); |
357 | } |
358 | |
359 | void ConnectionModel::aboutToAddConnection(int idx) |
360 | { |
361 | Q_ASSERT(m_editor); |
362 | beginInsertRows(parent: QModelIndex(), first: idx, last: idx); |
363 | } |
364 | |
365 | Qt::ItemFlags ConnectionModel::flags(const QModelIndex&) const |
366 | { |
367 | return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; |
368 | } |
369 | |
370 | void ConnectionModel::connectionChanged(Connection *con) |
371 | { |
372 | Q_ASSERT(m_editor); |
373 | const int idx = m_editor->indexOfConnection(con); |
374 | SignalSlotConnection *changedCon = static_cast<SignalSlotConnection*>(m_editor->connection(i: idx)); |
375 | SignalSlotConnection *c = nullptr; |
376 | for (int i=0; i<m_editor->connectionCount(); ++i) { |
377 | if (i == idx) |
378 | continue; |
379 | c = static_cast<SignalSlotConnection*>(m_editor->connection(i)); |
380 | if (c->sender() == changedCon->sender() && c->signal() == changedCon->signal() |
381 | && c->receiver() == changedCon->receiver() && c->slot() == changedCon->slot()) { |
382 | const QString message = tr(s: "The connection already exists!<br>%1" ).arg(a: changedCon->toString()); |
383 | m_editor->formWindow()->core()->dialogGui()->message(parent: m_editor->parentWidget(), context: QDesignerDialogGuiInterface::SignalSlotEditorMessage, |
384 | icon: QMessageBox::Warning, title: tr(s: "Signal and Slot Editor" ), text: message, buttons: QMessageBox::Ok); |
385 | break; |
386 | } |
387 | } |
388 | emit dataChanged(topLeft: createIndex(arow: idx, acolumn: 0), bottomRight: createIndex(arow: idx, acolumn: 3)); |
389 | } |
390 | |
391 | void ConnectionModel::updateAll() |
392 | { |
393 | emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: rowCount() - 1, column: columnCount() - 1)); |
394 | } |
395 | } |
396 | |
397 | namespace { |
398 | // ---------------------- InlineEditorModel |
399 | |
400 | class InlineEditorModel : public QStandardItemModel |
401 | { |
402 | Q_OBJECT |
403 | public: |
404 | enum { TitleItem = 1 }; |
405 | |
406 | InlineEditorModel(int rows, int cols, QObject *parent = nullptr); |
407 | |
408 | void addTitle(const QString &title); |
409 | void addTextList(const QMap<QString, bool> &text_list); |
410 | void addText(const QString &text); |
411 | bool isTitle(int idx) const; |
412 | |
413 | int findText(const QString &text) const; |
414 | |
415 | Qt::ItemFlags flags(const QModelIndex &index) const override; |
416 | }; |
417 | |
418 | InlineEditorModel::InlineEditorModel(int rows, int cols, QObject *parent) |
419 | : QStandardItemModel(rows, cols, parent) |
420 | { |
421 | } |
422 | |
423 | void InlineEditorModel::addTitle(const QString &title) |
424 | { |
425 | const int cnt = rowCount(); |
426 | insertRows(row: cnt, count: 1); |
427 | QModelIndex cat_idx = index(row: cnt, column: 0); |
428 | setData(index: cat_idx, value: QString(title + QLatin1Char(':')), role: Qt::DisplayRole); |
429 | setData(index: cat_idx, value: TitleItem, role: Qt::UserRole); |
430 | QFont font = QApplication::font(); |
431 | font.setBold(true); |
432 | setData(index: cat_idx, value: font, role: Qt::FontRole); |
433 | } |
434 | |
435 | bool InlineEditorModel::isTitle(int idx) const |
436 | { |
437 | if (idx == -1) |
438 | return false; |
439 | |
440 | return data(index: index(row: idx, column: 0), role: Qt::UserRole).toInt() == TitleItem; |
441 | } |
442 | |
443 | void InlineEditorModel::addText(const QString &text) |
444 | { |
445 | const int cnt = rowCount(); |
446 | insertRows(row: cnt, count: 1); |
447 | setData(index: index(row: cnt, column: 0), value: text, role: Qt::DisplayRole); |
448 | } |
449 | |
450 | void InlineEditorModel::addTextList(const QMap<QString, bool> &text_list) |
451 | { |
452 | int cnt = rowCount(); |
453 | insertRows(row: cnt, count: text_list.size()); |
454 | QFont font = QApplication::font(); |
455 | font.setItalic(true); |
456 | QVariant fontVariant = QVariant::fromValue(value: font); |
457 | QMap<QString, bool>::ConstIterator it = text_list.constBegin(); |
458 | const QMap<QString, bool>::ConstIterator itEnd = text_list.constEnd(); |
459 | while (it != itEnd) { |
460 | const QModelIndex text_idx = index(row: cnt++, column: 0); |
461 | setData(index: text_idx, value: it.key(), role: Qt::DisplayRole); |
462 | if (it.value()) { |
463 | setData(index: text_idx, value: fontVariant, role: Qt::FontRole); |
464 | setData(index: text_idx, value: QColor(Qt::red), role: Qt::ForegroundRole); |
465 | } |
466 | ++it; |
467 | } |
468 | } |
469 | |
470 | Qt::ItemFlags InlineEditorModel::flags(const QModelIndex &index) const |
471 | { |
472 | return isTitle(idx: index.row()) |
473 | ? Qt::ItemFlags(Qt::ItemIsEnabled) |
474 | : Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); |
475 | } |
476 | |
477 | int InlineEditorModel::findText(const QString &text) const |
478 | { |
479 | const int cnt = rowCount(); |
480 | for (int i = 0; i < cnt; ++i) { |
481 | const QModelIndex idx = index(row: i, column: 0); |
482 | if (data(index: idx, role: Qt::UserRole).toInt() == TitleItem) |
483 | continue; |
484 | if (data(index: idx, role: Qt::DisplayRole).toString() == text) |
485 | return i; |
486 | } |
487 | return -1; |
488 | } |
489 | |
490 | // ------------ InlineEditor |
491 | class InlineEditor : public QComboBox |
492 | { |
493 | Q_OBJECT |
494 | Q_PROPERTY(QString text READ text WRITE setText USER true) |
495 | public: |
496 | InlineEditor(QWidget *parent = nullptr); |
497 | |
498 | QString text() const; |
499 | void setText(const QString &text); |
500 | |
501 | void addTitle(const QString &title); |
502 | void addText(const QString &text); |
503 | void addTextList(const QMap<QString, bool> &text_list); |
504 | |
505 | private slots: |
506 | void checkSelection(int idx); |
507 | |
508 | private: |
509 | InlineEditorModel *m_model; |
510 | int m_idx = -1; |
511 | }; |
512 | |
513 | InlineEditor::InlineEditor(QWidget *parent) : |
514 | QComboBox(parent) |
515 | { |
516 | setModel(m_model = new InlineEditorModel(0, 4, this)); |
517 | setFrame(false); |
518 | m_idx = -1; |
519 | connect(sender: this, signal: QOverload<int>::of(ptr: &QComboBox::activated), |
520 | receiver: this, slot: &InlineEditor::checkSelection); |
521 | } |
522 | |
523 | void InlineEditor::checkSelection(int idx) |
524 | { |
525 | if (idx == m_idx) |
526 | return; |
527 | |
528 | if (m_model->isTitle(idx)) |
529 | setCurrentIndex(m_idx); |
530 | else |
531 | m_idx = idx; |
532 | } |
533 | |
534 | void InlineEditor::addTitle(const QString &title) |
535 | { |
536 | m_model->addTitle(title); |
537 | } |
538 | |
539 | void InlineEditor::addTextList(const QMap<QString, bool> &text_list) |
540 | { |
541 | m_model->addTextList(text_list); |
542 | } |
543 | |
544 | void InlineEditor::addText(const QString &text) |
545 | { |
546 | m_model->addText(text); |
547 | } |
548 | |
549 | QString InlineEditor::text() const |
550 | { |
551 | return currentText(); |
552 | } |
553 | |
554 | void InlineEditor::setText(const QString &text) |
555 | { |
556 | m_idx = m_model->findText(text); |
557 | if (m_idx == -1) |
558 | m_idx = 0; |
559 | setCurrentIndex(m_idx); |
560 | } |
561 | |
562 | // ------------------ ConnectionDelegate |
563 | |
564 | class ConnectionDelegate : public QItemDelegate |
565 | { |
566 | Q_OBJECT |
567 | public: |
568 | ConnectionDelegate(QWidget *parent = nullptr); |
569 | |
570 | void setForm(QDesignerFormWindowInterface *form); |
571 | |
572 | QWidget *createEditor(QWidget *parent, |
573 | const QStyleOptionViewItem &option, |
574 | const QModelIndex &index) const override; |
575 | |
576 | private slots: |
577 | void emitCommitData(); |
578 | |
579 | private: |
580 | QDesignerFormWindowInterface *m_form; |
581 | }; |
582 | |
583 | ConnectionDelegate::ConnectionDelegate(QWidget *parent) |
584 | : QItemDelegate(parent) |
585 | { |
586 | m_form = nullptr; |
587 | |
588 | static QItemEditorFactory *factory = nullptr; |
589 | if (factory == nullptr) { |
590 | factory = new QItemEditorFactory; |
591 | QItemEditorCreatorBase *creator |
592 | = new QItemEditorCreator<InlineEditor>("text" ); |
593 | factory->registerEditor(userType: QVariant::String, creator); |
594 | } |
595 | |
596 | setItemEditorFactory(factory); |
597 | } |
598 | |
599 | void ConnectionDelegate::setForm(QDesignerFormWindowInterface *form) |
600 | { |
601 | m_form = form; |
602 | } |
603 | |
604 | QWidget *ConnectionDelegate::createEditor(QWidget *parent, |
605 | const QStyleOptionViewItem &option, |
606 | const QModelIndex &index) const |
607 | { |
608 | if (m_form == nullptr) |
609 | return nullptr; |
610 | |
611 | QWidget *w = QItemDelegate::createEditor(parent, option, index); |
612 | InlineEditor *inline_editor = qobject_cast<InlineEditor*>(object: w); |
613 | Q_ASSERT(inline_editor != nullptr); |
614 | const QAbstractItemModel *model = index.model(); |
615 | |
616 | const QModelIndex obj_name_idx = model->index(row: index.row(), column: index.column() <= 1 ? 0 : 2); |
617 | const QString obj_name = model->data(index: obj_name_idx, role: Qt::DisplayRole).toString(); |
618 | |
619 | switch (index.column()) { |
620 | case 0: |
621 | case 2: { // object names |
622 | const QStringList &obj_name_list = objectNameList(form: m_form); |
623 | QMap<QString, bool> markedNameList; |
624 | markedNameList.insert(akey: tr(s: "<object>" ), avalue: false); |
625 | inline_editor->addTextList(text_list: markedNameList); |
626 | markedNameList.clear(); |
627 | for (const QString &name : obj_name_list) |
628 | markedNameList.insert(akey: name, avalue: false); |
629 | inline_editor->addTextList(text_list: markedNameList); |
630 | } |
631 | break; |
632 | case 1: |
633 | case 3: { // signals, slots |
634 | const qdesigner_internal::MemberType type = index.column() == 1 ? qdesigner_internal::SignalMember : qdesigner_internal::SlotMember; |
635 | const QModelIndex peer_index = model->index(row: index.row(), column: type == qdesigner_internal::SignalMember ? 3 : 1); |
636 | const QString peer = model->data(index: peer_index, role: Qt::DisplayRole).toString(); |
637 | |
638 | const qdesigner_internal::ClassesMemberFunctions class_list = qdesigner_internal::reverseClassesMemberFunctions(obj_name, member_type: type, peer, form: m_form); |
639 | |
640 | inline_editor->addText(text: type == qdesigner_internal::SignalMember ? tr(s: "<signal>" ) : tr(s: "<slot>" )); |
641 | for (const qdesigner_internal::ClassMemberFunctions &classInfo : class_list) { |
642 | if (classInfo.m_className.isEmpty() || classInfo.m_memberList.isEmpty()) |
643 | continue; |
644 | // Mark deprecated members by passing bool=true. |
645 | QMap<QString, bool> markedMemberList; |
646 | for (const QString &member : qAsConst(t: classInfo.m_memberList)) |
647 | markedMemberList.insert(akey: member, avalue: false); |
648 | inline_editor->addTitle(title: classInfo.m_className); |
649 | inline_editor->addTextList(text_list: markedMemberList); |
650 | } |
651 | } |
652 | break; |
653 | default: |
654 | break; |
655 | } |
656 | |
657 | connect(sender: inline_editor, signal: QOverload<int>::of(ptr: &QComboBox::activated), |
658 | receiver: this, slot: &ConnectionDelegate::emitCommitData); |
659 | |
660 | return inline_editor; |
661 | } |
662 | |
663 | void ConnectionDelegate::emitCommitData() |
664 | { |
665 | InlineEditor *editor = qobject_cast<InlineEditor*>(object: sender()); |
666 | emit commitData(editor); |
667 | } |
668 | |
669 | } |
670 | |
671 | namespace qdesigner_internal { |
672 | |
673 | /******************************************************************************* |
674 | ** SignalSlotEditorWindow |
675 | */ |
676 | |
677 | SignalSlotEditorWindow::SignalSlotEditorWindow(QDesignerFormEditorInterface *core, |
678 | QWidget *parent) : |
679 | QWidget(parent), |
680 | m_view(new QTreeView), |
681 | m_editor(nullptr), |
682 | m_add_button(new QToolButton), |
683 | m_remove_button(new QToolButton), |
684 | m_core(core), |
685 | m_model(new ConnectionModel(this)), |
686 | m_proxy_model(new QSortFilterProxyModel(this)), |
687 | m_handling_selection_change(false) |
688 | { |
689 | m_proxy_model->setSourceModel(m_model); |
690 | m_view->setModel(m_proxy_model); |
691 | m_view->setSortingEnabled(true); |
692 | m_view->setItemDelegate(new ConnectionDelegate(this)); |
693 | m_view->setEditTriggers(QAbstractItemView::DoubleClicked |
694 | | QAbstractItemView::EditKeyPressed); |
695 | m_view->setRootIsDecorated(false); |
696 | m_view->setTextElideMode (Qt::ElideMiddle); |
697 | connect(sender: m_view->selectionModel(), signal: &QItemSelectionModel::currentChanged, |
698 | receiver: this, slot: &SignalSlotEditorWindow::updateUi); |
699 | connect(sender: m_view->header(), signal: &QHeaderView::sectionDoubleClicked, |
700 | receiver: m_view, slot: &QTreeView::resizeColumnToContents); |
701 | |
702 | QVBoxLayout *layout = new QVBoxLayout(this); |
703 | layout->setContentsMargins(QMargins()); |
704 | layout->setSpacing(0); |
705 | |
706 | QToolBar *toolBar = new QToolBar; |
707 | toolBar->setIconSize(QSize(22, 22)); |
708 | m_add_button->setIcon(createIconSet(QStringLiteral("plus.png" ))); |
709 | connect(sender: m_add_button, signal: &QAbstractButton::clicked, receiver: this, slot: &SignalSlotEditorWindow::addConnection); |
710 | toolBar->addWidget(widget: m_add_button); |
711 | |
712 | m_remove_button->setIcon(createIconSet(QStringLiteral("minus.png" ))); |
713 | connect(sender: m_remove_button, signal: &QAbstractButton::clicked, receiver: this, slot: &SignalSlotEditorWindow::removeConnection); |
714 | toolBar->addWidget(widget: m_remove_button); |
715 | |
716 | layout->addWidget(toolBar); |
717 | layout->addWidget(m_view); |
718 | |
719 | connect(sender: core->formWindowManager(), |
720 | signal: &QDesignerFormWindowManagerInterface::activeFormWindowChanged, |
721 | receiver: this, slot: &SignalSlotEditorWindow::setActiveFormWindow); |
722 | |
723 | updateUi(); |
724 | } |
725 | |
726 | void SignalSlotEditorWindow::setActiveFormWindow(QDesignerFormWindowInterface *form) |
727 | { |
728 | QDesignerIntegrationInterface *integration = m_core->integration(); |
729 | |
730 | if (!m_editor.isNull()) { |
731 | disconnect(sender: m_view->selectionModel(), |
732 | signal: &QItemSelectionModel::currentChanged, |
733 | receiver: this, slot: &SignalSlotEditorWindow::updateEditorSelection); |
734 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionSelected, |
735 | receiver: this, slot: &SignalSlotEditorWindow::updateDialogSelection); |
736 | disconnect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionAdded, |
737 | receiver: this, slot: &SignalSlotEditorWindow::resizeColumns); |
738 | if (integration) { |
739 | disconnect(sender: integration, signal: &QDesignerIntegrationInterface::objectNameChanged, |
740 | receiver: this, slot: &SignalSlotEditorWindow::objectNameChanged); |
741 | } |
742 | } |
743 | |
744 | m_editor = form->findChild<SignalSlotEditor*>(); |
745 | m_model->setEditor(m_editor); |
746 | if (!m_editor.isNull()) { |
747 | ConnectionDelegate *delegate |
748 | = qobject_cast<ConnectionDelegate*>(object: m_view->itemDelegate()); |
749 | if (delegate != nullptr) |
750 | delegate->setForm(form); |
751 | |
752 | connect(sender: m_view->selectionModel(), |
753 | signal: &QItemSelectionModel::currentChanged, |
754 | receiver: this, slot: &SignalSlotEditorWindow::updateEditorSelection); |
755 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionSelected, |
756 | receiver: this, slot: &SignalSlotEditorWindow::updateDialogSelection); |
757 | connect(sender: m_editor.data(), signal: &SignalSlotEditor::connectionAdded, |
758 | receiver: this, slot: &SignalSlotEditorWindow::resizeColumns); |
759 | if (integration) { |
760 | connect(sender: integration, signal: &QDesignerIntegrationInterface::objectNameChanged, |
761 | receiver: this, slot: &SignalSlotEditorWindow::objectNameChanged); |
762 | } |
763 | } |
764 | |
765 | resizeColumns(); |
766 | updateUi(); |
767 | } |
768 | |
769 | void SignalSlotEditorWindow::updateDialogSelection(Connection *con) |
770 | { |
771 | if (m_handling_selection_change || m_editor == nullptr) |
772 | return; |
773 | |
774 | QModelIndex index = m_proxy_model->mapFromSource(sourceIndex: m_model->connectionToIndex(con)); |
775 | if (!index.isValid() || index == m_view->currentIndex()) |
776 | return; |
777 | m_handling_selection_change = true; |
778 | m_view->scrollTo(index, hint: QTreeView::EnsureVisible); |
779 | m_view->setCurrentIndex(index); |
780 | m_handling_selection_change = false; |
781 | |
782 | updateUi(); |
783 | } |
784 | |
785 | void SignalSlotEditorWindow::updateEditorSelection(const QModelIndex &index) |
786 | { |
787 | if (m_handling_selection_change || m_editor == nullptr) |
788 | return; |
789 | |
790 | if (m_editor == nullptr) |
791 | return; |
792 | |
793 | Connection *con = m_model->indexToConnection(index: m_proxy_model->mapToSource(proxyIndex: index)); |
794 | if (m_editor->selected(con)) |
795 | return; |
796 | m_handling_selection_change = true; |
797 | m_editor->selectNone(); |
798 | m_editor->setSelected(con, sel: true); |
799 | m_handling_selection_change = false; |
800 | |
801 | updateUi(); |
802 | } |
803 | |
804 | void SignalSlotEditorWindow::objectNameChanged(QDesignerFormWindowInterface *, QObject *, const QString &, const QString &) |
805 | { |
806 | if (m_editor) |
807 | m_model->updateAll(); |
808 | } |
809 | |
810 | void SignalSlotEditorWindow::addConnection() |
811 | { |
812 | if (m_editor.isNull()) |
813 | return; |
814 | |
815 | m_editor->addEmptyConnection(); |
816 | updateUi(); |
817 | } |
818 | |
819 | void SignalSlotEditorWindow::removeConnection() |
820 | { |
821 | if (m_editor.isNull()) |
822 | return; |
823 | |
824 | m_editor->deleteSelected(); |
825 | updateUi(); |
826 | } |
827 | |
828 | void SignalSlotEditorWindow::updateUi() |
829 | { |
830 | m_add_button->setEnabled(!m_editor.isNull()); |
831 | m_remove_button->setEnabled(!m_editor.isNull() && m_view->currentIndex().isValid()); |
832 | } |
833 | |
834 | void SignalSlotEditorWindow::resizeColumns() |
835 | { |
836 | for (int c = 0, count = m_model->columnCount(); c < count; ++c) |
837 | m_view->resizeColumnToContents(column: c); |
838 | } |
839 | |
840 | } // namespace qdesigner_internal |
841 | |
842 | QT_END_NAMESPACE |
843 | |
844 | #include "signalsloteditorwindow.moc" |
845 | |