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 "qdesigner_promotiondialog_p.h" |
30 | #include "promotionmodel_p.h" |
31 | #include "iconloader_p.h" |
32 | #include "widgetdatabase_p.h" |
33 | #include "signalslotdialog_p.h" |
34 | |
35 | #include <QtDesigner/abstractformeditor.h> |
36 | #include <QtDesigner/abstractformwindow.h> |
37 | #include <QtDesigner/abstractpromotioninterface.h> |
38 | #include <QtDesigner/abstractwidgetdatabase.h> |
39 | #include <QtDesigner/abstractintegration.h> |
40 | #include <abstractdialoggui_p.h> |
41 | |
42 | #include <QtCore/qtimer.h> |
43 | #include <QtWidgets/qboxlayout.h> |
44 | #include <QtWidgets/qformlayout.h> |
45 | #include <QtWidgets/qdialogbuttonbox.h> |
46 | #include <QtWidgets/qtreeview.h> |
47 | #include <QtWidgets/qheaderview.h> |
48 | #include <QtWidgets/qpushbutton.h> |
49 | #include <QtCore/qitemselectionmodel.h> |
50 | #include <QtWidgets/qcombobox.h> |
51 | #include <QtWidgets/qlineedit.h> |
52 | #include <QtWidgets/qcheckbox.h> |
53 | #include <QtGui/qvalidator.h> |
54 | #include <QtWidgets/qlabel.h> |
55 | #include <QtWidgets/qlayoutitem.h> |
56 | #include <QtWidgets/qmenu.h> |
57 | #include <QtWidgets/qaction.h> |
58 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | namespace qdesigner_internal { |
62 | // PromotionParameters |
63 | struct PromotionParameters { |
64 | QString m_baseClass; |
65 | QString m_className; |
66 | QString m_includeFile; |
67 | }; |
68 | |
69 | // NewPromotedClassPanel |
70 | NewPromotedClassPanel::NewPromotedClassPanel(const QStringList &baseClasses, |
71 | int selectedBaseClass, |
72 | QWidget *parent) : |
73 | QGroupBox(parent), |
74 | m_baseClassCombo(new QComboBox), |
75 | m_classNameEdit(new QLineEdit), |
76 | m_includeFileEdit(new QLineEdit), |
77 | m_globalIncludeCheckBox(new QCheckBox), |
78 | m_addButton(new QPushButton(tr(s: "Add" ))) |
79 | { |
80 | setTitle(tr(s: "New Promoted Class" )); |
81 | setSizePolicy(hor: QSizePolicy::MinimumExpanding, ver: QSizePolicy::Maximum); |
82 | QHBoxLayout *hboxLayout = new QHBoxLayout(this); |
83 | |
84 | m_classNameEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("^[_a-zA-Z:][:_a-zA-Z0-9]*$" )), m_classNameEdit)); |
85 | connect(sender: m_classNameEdit, signal: &QLineEdit::textChanged, |
86 | receiver: this, slot: &NewPromotedClassPanel::slotNameChanged); |
87 | connect(sender: m_includeFileEdit, signal: &QLineEdit::textChanged, |
88 | receiver: this, slot: &NewPromotedClassPanel::slotIncludeFileChanged); |
89 | |
90 | m_baseClassCombo->setEditable(false); |
91 | m_baseClassCombo->addItems(texts: baseClasses); |
92 | if (selectedBaseClass != -1) |
93 | m_baseClassCombo->setCurrentIndex(selectedBaseClass); |
94 | |
95 | // Grid |
96 | QFormLayout *formLayout = new QFormLayout(); |
97 | formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); // Mac |
98 | formLayout->addRow(labelText: tr(s: "Base class name:" ), field: m_baseClassCombo); |
99 | formLayout->addRow(labelText: tr(s: "Promoted class name:" ), field: m_classNameEdit); |
100 | formLayout->addRow(labelText: tr(s: "Header file:" ), field: m_includeFileEdit); |
101 | formLayout->addRow(labelText: tr(s: "Global include" ), field: m_globalIncludeCheckBox); |
102 | hboxLayout->addLayout(layout: formLayout); |
103 | hboxLayout->addItem(new QSpacerItem(15, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); |
104 | // Button box |
105 | QVBoxLayout *buttonLayout = new QVBoxLayout(); |
106 | |
107 | m_addButton->setAutoDefault(false); |
108 | connect(sender: m_addButton, signal: &QAbstractButton::clicked, receiver: this, slot: &NewPromotedClassPanel::slotAdd); |
109 | m_addButton->setEnabled(false); |
110 | buttonLayout->addWidget(m_addButton); |
111 | |
112 | QPushButton *resetButton = new QPushButton(tr(s: "Reset" )); |
113 | resetButton->setAutoDefault(false); |
114 | connect(sender: resetButton, signal: &QAbstractButton::clicked, receiver: this, slot: &NewPromotedClassPanel::slotReset); |
115 | |
116 | buttonLayout->addWidget(resetButton); |
117 | buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); |
118 | hboxLayout->addLayout(layout: buttonLayout); |
119 | |
120 | enableButtons(); |
121 | } |
122 | |
123 | void NewPromotedClassPanel::slotAdd() { |
124 | bool ok = false; |
125 | emit newPromotedClass(promotionParameters(), ok: &ok); |
126 | if (ok) |
127 | slotReset(); |
128 | } |
129 | |
130 | void NewPromotedClassPanel::slotReset() { |
131 | const QString empty; |
132 | m_classNameEdit->setText(empty); |
133 | m_includeFileEdit->setText(empty); |
134 | m_globalIncludeCheckBox->setCheckState(Qt::Unchecked); |
135 | } |
136 | |
137 | void NewPromotedClassPanel::grabFocus() { |
138 | m_classNameEdit->setFocus(Qt::OtherFocusReason); |
139 | } |
140 | |
141 | void NewPromotedClassPanel::slotNameChanged(const QString &className) { |
142 | // Suggest a name |
143 | if (!className.isEmpty()) { |
144 | const QChar dot(QLatin1Char('.')); |
145 | QString = m_promotedHeaderLowerCase ? |
146 | className.toLower() : className; |
147 | suggestedHeader.replace(QStringLiteral("::" ), after: QString(QLatin1Char('_'))); |
148 | if (!m_promotedHeaderSuffix.startsWith(c: dot)) |
149 | suggestedHeader += dot; |
150 | suggestedHeader += m_promotedHeaderSuffix; |
151 | |
152 | const bool blocked = m_includeFileEdit->blockSignals(b: true); |
153 | m_includeFileEdit->setText(suggestedHeader); |
154 | m_includeFileEdit->blockSignals(b: blocked); |
155 | } |
156 | enableButtons(); |
157 | } |
158 | |
159 | void NewPromotedClassPanel::slotIncludeFileChanged(const QString &){ |
160 | enableButtons(); |
161 | } |
162 | |
163 | void NewPromotedClassPanel::enableButtons() { |
164 | const bool enabled = !m_classNameEdit->text().isEmpty() && !m_includeFileEdit->text().isEmpty(); |
165 | m_addButton->setEnabled(enabled); |
166 | m_addButton->setDefault(enabled); |
167 | } |
168 | |
169 | PromotionParameters NewPromotedClassPanel::promotionParameters() const { |
170 | PromotionParameters rc; |
171 | rc.m_baseClass = m_baseClassCombo->currentText(); |
172 | rc.m_className = m_classNameEdit->text(); |
173 | rc.m_includeFile = buildIncludeFile(includeFile: m_includeFileEdit->text(), |
174 | includeType: m_globalIncludeCheckBox->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal); |
175 | return rc; |
176 | } |
177 | |
178 | void NewPromotedClassPanel::chooseBaseClass(const QString &baseClass) { |
179 | const int index = m_baseClassCombo->findText (text: baseClass); |
180 | if (index != -1) |
181 | m_baseClassCombo->setCurrentIndex (index); |
182 | } |
183 | |
184 | // --------------- QDesignerPromotionDialog |
185 | QDesignerPromotionDialog::QDesignerPromotionDialog(QDesignerFormEditorInterface *core, |
186 | QWidget *parent, |
187 | const QString &promotableWidgetClassName, |
188 | QString *promoteTo) : |
189 | QDialog(parent), |
190 | m_mode(promotableWidgetClassName.isEmpty() || promoteTo == nullptr ? ModeEdit : ModeEditChooseClass), |
191 | m_promotableWidgetClassName(promotableWidgetClassName), |
192 | m_core(core), |
193 | m_promoteTo(promoteTo), |
194 | m_promotion(core->promotion()), |
195 | m_model(new PromotionModel(core)), |
196 | m_treeView(new QTreeView), |
197 | m_buttonBox(nullptr), |
198 | m_removeButton(new QPushButton(createIconSet(name: QString::fromUtf8(str: "minus.png" )), QString())) |
199 | { |
200 | m_buttonBox = createButtonBox(); |
201 | setModal(true); |
202 | setWindowTitle(tr(s: "Promoted Widgets" )); |
203 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); |
204 | |
205 | QVBoxLayout *vboxLayout = new QVBoxLayout(this); |
206 | |
207 | // tree view group |
208 | QGroupBox *treeViewGroup = new QGroupBox(); |
209 | treeViewGroup->setTitle(tr(s: "Promoted Classes" )); |
210 | QVBoxLayout *treeViewVBoxLayout = new QVBoxLayout(treeViewGroup); |
211 | // tree view |
212 | m_treeView->setModel (m_model); |
213 | m_treeView->setMinimumWidth(450); |
214 | m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); |
215 | |
216 | connect(sender: m_treeView->selectionModel(), signal: &QItemSelectionModel::selectionChanged, |
217 | receiver: this, slot: &QDesignerPromotionDialog::slotSelectionChanged); |
218 | |
219 | connect(sender: m_treeView, signal: &QWidget::customContextMenuRequested, |
220 | receiver: this, slot: &QDesignerPromotionDialog::slotTreeViewContextMenu); |
221 | |
222 | QHeaderView * = m_treeView->header(); |
223 | headerView->setSectionResizeMode(QHeaderView::ResizeToContents); |
224 | treeViewVBoxLayout->addWidget(m_treeView); |
225 | // remove button |
226 | QHBoxLayout *hboxLayout = new QHBoxLayout(); |
227 | hboxLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); |
228 | |
229 | m_removeButton->setAutoDefault(false); |
230 | connect(sender: m_removeButton, signal: &QAbstractButton::clicked, receiver: this, slot: &QDesignerPromotionDialog::slotRemove); |
231 | m_removeButton->setEnabled(false); |
232 | hboxLayout->addWidget(m_removeButton); |
233 | treeViewVBoxLayout->addLayout(layout: hboxLayout); |
234 | vboxLayout->addWidget(treeViewGroup); |
235 | // Create new panel: Try to be smart and preselect a base class. Default to QFrame |
236 | const QStringList &baseClassNameList = baseClassNames(promotion: m_promotion); |
237 | int preselectedBaseClass = -1; |
238 | if (m_mode == ModeEditChooseClass) { |
239 | preselectedBaseClass = baseClassNameList.indexOf(t: m_promotableWidgetClassName); |
240 | } |
241 | if (preselectedBaseClass == -1) |
242 | preselectedBaseClass = baseClassNameList.indexOf(QStringLiteral("QFrame" )); |
243 | |
244 | NewPromotedClassPanel *newPromotedClassPanel = new NewPromotedClassPanel(baseClassNameList, preselectedBaseClass); |
245 | newPromotedClassPanel->setPromotedHeaderSuffix(core->integration()->headerSuffix()); |
246 | newPromotedClassPanel->setPromotedHeaderLowerCase(core->integration()->isHeaderLowercase()); |
247 | connect(sender: newPromotedClassPanel, signal: &NewPromotedClassPanel::newPromotedClass, |
248 | receiver: this, slot: &QDesignerPromotionDialog::slotNewPromotedClass); |
249 | connect(sender: this, signal: &QDesignerPromotionDialog::selectedBaseClassChanged, |
250 | receiver: newPromotedClassPanel, slot: &NewPromotedClassPanel::chooseBaseClass); |
251 | vboxLayout->addWidget(newPromotedClassPanel); |
252 | // button box |
253 | vboxLayout->addWidget(m_buttonBox); |
254 | // connect model |
255 | connect(sender: m_model, signal: &PromotionModel::includeFileChanged, |
256 | receiver: this, slot: &QDesignerPromotionDialog::slotIncludeFileChanged); |
257 | |
258 | connect(sender: m_model, signal: &PromotionModel::classNameChanged, |
259 | receiver: this, slot: &QDesignerPromotionDialog::slotClassNameChanged); |
260 | |
261 | // focus |
262 | if (m_mode == ModeEditChooseClass) |
263 | newPromotedClassPanel->grabFocus(); |
264 | |
265 | slotUpdateFromWidgetDatabase(); |
266 | } |
267 | |
268 | QDialogButtonBox *QDesignerPromotionDialog::createButtonBox() { |
269 | QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Close); |
270 | |
271 | connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, |
272 | receiver: this, slot: &QDesignerPromotionDialog::slotAcceptPromoteTo); |
273 | buttonBox->button(which: QDialogButtonBox::Ok)->setText(tr(s: "Promote" )); |
274 | buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(false); |
275 | |
276 | connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, receiver: this, slot: &QDialog::reject); |
277 | return buttonBox; |
278 | } |
279 | |
280 | void QDesignerPromotionDialog::slotUpdateFromWidgetDatabase() { |
281 | m_model->updateFromWidgetDatabase(); |
282 | m_treeView->expandAll(); |
283 | m_removeButton->setEnabled(false); |
284 | } |
285 | |
286 | void QDesignerPromotionDialog::delayedUpdateFromWidgetDatabase() { |
287 | QTimer::singleShot(interval: 0, receiver: this, slot: &QDesignerPromotionDialog::slotUpdateFromWidgetDatabase); |
288 | } |
289 | |
290 | const QStringList &QDesignerPromotionDialog::baseClassNames(const QDesignerPromotionInterface *promotion) { |
291 | using WidgetDataBaseItemList = QList<QDesignerWidgetDataBaseItemInterface *>; |
292 | static QStringList rc; |
293 | if (rc.isEmpty()) { |
294 | // Convert the item list into a string list. |
295 | const WidgetDataBaseItemList dbItems = promotion->promotionBaseClasses(); |
296 | const WidgetDataBaseItemList::const_iterator cend = dbItems.constEnd(); |
297 | for (WidgetDataBaseItemList::const_iterator it = dbItems.constBegin() ; it != cend; ++it) { |
298 | rc.push_back( t: (*it)->name()); |
299 | } |
300 | } |
301 | return rc; |
302 | } |
303 | |
304 | void QDesignerPromotionDialog::slotAcceptPromoteTo() { |
305 | Q_ASSERT(m_mode == ModeEditChooseClass); |
306 | unsigned flags; |
307 | // Ok pressed: Promote to selected class |
308 | if (QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags)) { |
309 | if (flags & CanPromote) { |
310 | *m_promoteTo = dbItem ->name(); |
311 | accept(); |
312 | } |
313 | } |
314 | } |
315 | |
316 | void QDesignerPromotionDialog::slotRemove() { |
317 | unsigned flags; |
318 | QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); |
319 | if (!dbItem || (flags & Referenced)) |
320 | return; |
321 | |
322 | QString errorMessage; |
323 | if (m_promotion->removePromotedClass(className: dbItem->name(), errorMessage: &errorMessage)) { |
324 | slotUpdateFromWidgetDatabase(); |
325 | } else { |
326 | displayError(message: errorMessage); |
327 | } |
328 | } |
329 | |
330 | void QDesignerPromotionDialog::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) { |
331 | // Enable deleting non-referenced items |
332 | unsigned flags; |
333 | const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected, flags); |
334 | m_removeButton->setEnabled(dbItem && !(flags & Referenced)); |
335 | // In choose mode, can we promote to the class? |
336 | if (m_mode == ModeEditChooseClass) { |
337 | const bool enablePromoted = flags & CanPromote; |
338 | m_buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(enablePromoted); |
339 | m_buttonBox->button(which: QDialogButtonBox::Ok)->setDefault(enablePromoted); |
340 | } |
341 | // different base? |
342 | if (dbItem) { |
343 | const QString baseClass = dbItem->extends(); |
344 | if (baseClass != m_lastSelectedBaseClass) { |
345 | m_lastSelectedBaseClass = baseClass; |
346 | emit selectedBaseClassChanged(m_lastSelectedBaseClass); |
347 | } |
348 | } |
349 | } |
350 | |
351 | QDesignerWidgetDataBaseItemInterface *QDesignerPromotionDialog::databaseItemAt(const QItemSelection &selected, unsigned &flags) const { |
352 | flags = 0; |
353 | const QModelIndexList indexes = selected.indexes(); |
354 | if (indexes.isEmpty()) |
355 | return nullptr; |
356 | const PromotionModel::ModelData data = m_model->modelData(index: indexes.constFirst()); |
357 | QDesignerWidgetDataBaseItemInterface *dbItem = data.promotedItem; |
358 | |
359 | if (dbItem) { |
360 | if (data.referenced) |
361 | flags |= Referenced; |
362 | // In choose mode, can we promote to the class? |
363 | if (m_mode == ModeEditChooseClass && dbItem && dbItem->isPromoted() && dbItem->extends() == m_promotableWidgetClassName) |
364 | flags |= CanPromote; |
365 | |
366 | } |
367 | return dbItem; |
368 | } |
369 | |
370 | void QDesignerPromotionDialog::slotNewPromotedClass(const PromotionParameters &p, bool *ok) { |
371 | QString errorMessage; |
372 | *ok = m_promotion->addPromotedClass(baseClass: p.m_baseClass, className: p.m_className, includeFile: p.m_includeFile, errorMessage: &errorMessage); |
373 | if (*ok) { |
374 | // update and select |
375 | slotUpdateFromWidgetDatabase(); |
376 | const QModelIndex newClassIndex = m_model->indexOfClass(className: p.m_className); |
377 | if (newClassIndex.isValid()) { |
378 | m_treeView->selectionModel()->select(index: newClassIndex, command: QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows); |
379 | } |
380 | } else { |
381 | displayError(message: errorMessage); |
382 | } |
383 | } |
384 | |
385 | void QDesignerPromotionDialog::slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &includeFile) { |
386 | if (includeFile.isEmpty()) { |
387 | delayedUpdateFromWidgetDatabase(); |
388 | return; |
389 | } |
390 | |
391 | if (dbItem->includeFile() == includeFile) |
392 | return; |
393 | |
394 | QString errorMessage; |
395 | if (!m_promotion->setPromotedClassIncludeFile(className: dbItem->name(), includeFile, errorMessage: &errorMessage)) { |
396 | displayError(message: errorMessage); |
397 | delayedUpdateFromWidgetDatabase(); |
398 | } |
399 | } |
400 | |
401 | void QDesignerPromotionDialog::slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &newName) { |
402 | if (newName.isEmpty()) { |
403 | delayedUpdateFromWidgetDatabase(); |
404 | return; |
405 | } |
406 | const QString oldName = dbItem->name(); |
407 | if (newName == oldName) |
408 | return; |
409 | |
410 | QString errorMessage; |
411 | if (!m_promotion->changePromotedClassName(oldClassName: oldName , newClassName: newName, errorMessage: &errorMessage)) { |
412 | displayError(message: errorMessage); |
413 | delayedUpdateFromWidgetDatabase(); |
414 | } |
415 | } |
416 | |
417 | void QDesignerPromotionDialog::(const QPoint &pos) { |
418 | unsigned flags; |
419 | const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected: m_treeView->selectionModel()->selection(), flags); |
420 | if (!dbItem) |
421 | return; |
422 | |
423 | QMenu ; |
424 | QAction *signalSlotAction = menu.addAction(text: tr(s: "Change signals/slots..." )); |
425 | connect(sender: signalSlotAction, signal: &QAction::triggered, |
426 | receiver: this, slot: &QDesignerPromotionDialog::slotEditSignalsSlots); |
427 | |
428 | menu.exec(pos: m_treeView->viewport()->mapToGlobal(pos)); |
429 | } |
430 | |
431 | void QDesignerPromotionDialog::slotEditSignalsSlots() { |
432 | unsigned flags; |
433 | const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected: m_treeView->selectionModel()->selection(), flags); |
434 | if (!dbItem) |
435 | return; |
436 | |
437 | SignalSlotDialog::editPromotedClass(core: m_core, promotedClassName: dbItem->name(), parent: this); |
438 | } |
439 | |
440 | void QDesignerPromotionDialog::displayError(const QString &message) { |
441 | m_core->dialogGui()->message(parent: this, context: QDesignerDialogGuiInterface::PromotionErrorMessage, icon: QMessageBox::Warning, |
442 | title: tr(s: "%1 - Error" ).arg(a: windowTitle()), text: message, buttons: QMessageBox::Close); |
443 | } |
444 | } // namespace qdesigner_internal |
445 | |
446 | QT_END_NAMESPACE |
447 | |