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
59QT_BEGIN_NAMESPACE
60
61namespace 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 suggestedHeader = 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 *headerView = 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::slotTreeViewContextMenu(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 menu;
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
446QT_END_NAMESPACE
447

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