Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
formlayoutmenu.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5#include "layoutinfo_p.h"
9#include "ui_formlayoutrowdialog.h"
10
11#include <QtDesigner/abstractformwindow.h>
12#include <QtDesigner/abstractformeditor.h>
13#include <QtDesigner/abstractwidgetfactory.h>
14#include <QtDesigner/propertysheet.h>
15#include <QtDesigner/qextensionmanager.h>
16#include <QtDesigner/abstractwidgetdatabase.h>
17#include <QtDesigner/abstractlanguage.h>
18
19#include <QtWidgets/qwidget.h>
20#include <QtWidgets/qformlayout.h>
21#include <QtWidgets/qdialog.h>
22#include <QtWidgets/qpushbutton.h>
23
24#include <QtGui/qaction.h>
25#include <QtGui/qvalidator.h>
26#include <QtGui/qundostack.h>
27
28#include <QtCore/qpair.h>
29#include <QtCore/qcoreapplication.h>
30#include <QtCore/qregularexpression.h>
31#include <QtCore/qhash.h>
32#include <QtCore/qdebug.h>
33
35
36using namespace Qt::StringLiterals;
37
38static constexpr auto buddyPropertyC = "buddy"_L1;
39static const char *fieldWidgetBaseClasses[] = {
40 "QLineEdit", "QComboBox", "QSpinBox", "QDoubleSpinBox", "QCheckBox",
41 "QDateEdit", "QTimeEdit", "QDateTimeEdit", "QDial", "QWidget"
42};
43
44namespace qdesigner_internal {
45
46// Struct that describes a row of controls (descriptive label and control) to
47// be added to a form layout.
49 QString labelName;
50 QString labelText;
52 QString fieldName;
53 bool buddy{false};
54};
55
56// A Dialog to edit a FormLayoutRow. Lets the user input a label text, label
57// name, field widget type, field object name and buddy setting. As the
58// user types the label text; the object names to be used for label and field
59// are updated. It also checks the buddy setting depending on whether the
60// label text contains a buddy marker.
64public:
67
69
70 bool buddy() const;
71 void setBuddy(bool);
72
73 // Accessors for form layout row numbers using 0..[n-1] convention
74 int row() const;
75 void setRow(int);
76 void setRowRange(int, int);
77
78 QString fieldClass() const;
79 QString labelText() const;
80
81 static QStringList fieldWidgetClasses(QDesignerFormEditorInterface *core);
82
83private slots:
84 void labelTextEdited(const QString &text);
85 void labelNameEdited(const QString &text);
86 void fieldNameEdited(const QString &text);
87 void buddyClicked();
88 void fieldClassChanged(int);
89
90private:
91 bool isValid() const;
92 void updateObjectNames(bool updateLabel, bool updateField);
93 void updateOkButton();
94
95 // Check for buddy marker in string
96 const QRegularExpression m_buddyMarkerRegexp;
97
98 QT_PREPEND_NAMESPACE(Ui)::FormLayoutRowDialog m_ui;
99 bool m_labelNameEdited;
100 bool m_fieldNameEdited;
101 bool m_buddyClicked;
102};
103
104FormLayoutRowDialog::FormLayoutRowDialog(QDesignerFormEditorInterface *core,
105 QWidget *parent) :
106 QDialog(parent),
107 m_buddyMarkerRegexp(u"\\&[^&]"_s),
108 m_labelNameEdited(false),
109 m_fieldNameEdited(false),
110 m_buddyClicked(false)
111{
112 Q_ASSERT(m_buddyMarkerRegexp.isValid());
113
114 setModal(true);
115 m_ui.setupUi(this);
116 connect(m_ui.labelTextLineEdit, &QLineEdit::textEdited, this, &FormLayoutRowDialog::labelTextEdited);
117
118 auto *nameValidator = new QRegularExpressionValidator(QRegularExpression(u"^[a-zA-Z0-9_]+$"_s), this);
119 Q_ASSERT(nameValidator->regularExpression().isValid());
120
121 m_ui.labelNameLineEdit->setValidator(nameValidator);
122 connect(m_ui.labelNameLineEdit, &QLineEdit::textEdited,
123 this, &FormLayoutRowDialog::labelNameEdited);
124
125 m_ui.fieldNameLineEdit->setValidator(nameValidator);
126 connect(m_ui.fieldNameLineEdit, &QLineEdit::textEdited,
127 this, &FormLayoutRowDialog::fieldNameEdited);
128
129 connect(m_ui.buddyCheckBox, &QAbstractButton::clicked, this, &FormLayoutRowDialog::buddyClicked);
130
131 m_ui.fieldClassComboBox->addItems(fieldWidgetClasses(core));
132 m_ui.fieldClassComboBox->setCurrentIndex(0);
133 connect(m_ui.fieldClassComboBox,
134 &QComboBox::currentIndexChanged,
135 this, &FormLayoutRowDialog::fieldClassChanged);
136
137 updateOkButton();
138}
139
141{
142 FormLayoutRow rc;
143 rc.labelText = labelText();
144 rc.labelName = m_ui.labelNameLineEdit->text();
145 rc.fieldClassName = fieldClass();
146 rc.fieldName = m_ui.fieldNameLineEdit->text();
147 rc.buddy = buddy();
148 return rc;
149}
150
152{
153 return m_ui.buddyCheckBox->checkState() == Qt::Checked;
154}
155
157{
158 m_ui.buddyCheckBox->setCheckState(b ? Qt::Checked : Qt::Unchecked);
159}
160
161// Convert rows to 1..n convention for users
163{
164 return m_ui.rowSpinBox->value() - 1;
165}
166
168{
169 m_ui.rowSpinBox->setValue(row + 1);
170}
171
172void FormLayoutRowDialog::setRowRange(int from, int to)
173{
174 m_ui.rowSpinBox->setMinimum(from + 1);
175 m_ui.rowSpinBox->setMaximum(to + 1);
176 m_ui.rowSpinBox->setEnabled(to - from > 0);
177}
178
180{
181 return m_ui.fieldClassComboBox->itemText(m_ui.fieldClassComboBox->currentIndex());
182}
183
185{
186 return m_ui.labelTextLineEdit->text();
187}
188
189bool FormLayoutRowDialog::isValid() const
190{
191 // Check for non-empty names and presence of buddy marker if checked
192 const QString name = labelText();
193 if (name.isEmpty() || m_ui.labelNameLineEdit->text().isEmpty() || m_ui.fieldNameLineEdit->text().isEmpty())
194 return false;
195 if (buddy() && !name.contains(m_buddyMarkerRegexp))
196 return false;
197 return true;
198}
199
200void FormLayoutRowDialog::updateOkButton()
201{
202 m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid());
203}
204
205void FormLayoutRowDialog::labelTextEdited(const QString &text)
206{
207 updateObjectNames(true, true);
208 // Set buddy if '&' is present unless the user changed it
209 if (!m_buddyClicked)
210 setBuddy(text.contains(m_buddyMarkerRegexp));
211
212 updateOkButton();
213}
214
215// Get a suitable object name postfix from a class name:
216// "namespace::QLineEdit"->"LineEdit"
217static inline QString postFixFromClassName(QString className)
218{
219 const int index = className.lastIndexOf("::"_L1);
220 if (index != -1)
221 className.remove(0, index + 2);
222 if (className.size() > 2)
223 if (className.at(0) == u'Q' || className.at(0) == u'K')
224 if (className.at(1).isUpper())
225 className.remove(0, 1);
226 return className;
227}
228
229// Helper routines to filter out characters for converting texts into
230// class name prefixes. Only accepts ASCII characters/digits and underscores.
231
234
235static inline PrefixCharacterKind prefixCharacterKind(const QChar &c)
236{
237 switch (c.category()) {
238 case QChar::Number_DecimalDigit:
239 return PC_Digit;
240 case QChar::Letter_Lowercase: {
241 const char a = c.toLatin1();
242 if (a >= 'a' && a <= 'z')
243 return PC_LowerCaseLetter;
244 }
245 break;
246 case QChar::Letter_Uppercase: {
247 const char a = c.toLatin1();
248 if (a >= 'A' && a <= 'Z')
249 return PC_UpperCaseLetter;
250 }
251 break;
252 case QChar::Punctuation_Connector:
253 if (c.toLatin1() == '_')
254 return PC_Other;
255 break;
256 default:
257 break;
258 }
259 return PC_Invalid;
260}
261
262// Convert the text the user types into a usable class name prefix by filtering
263// characters, lower-casing the first character and camel-casing subsequent
264// words. ("zip code:") --> ("zipCode").
265
266static QString prefixFromLabel(const QString &prefix)
267{
268 QString rc;
269 bool lastWasAcceptable = false;
270 for (const QChar &c : prefix) {
271 const PrefixCharacterKind kind = prefixCharacterKind(c);
272 const bool acceptable = kind != PC_Invalid;
273 if (acceptable) {
274 if (rc.isEmpty()) {
275 // Lower-case first character
276 rc += kind == PC_UpperCaseLetter ? c.toLower() : c;
277 } else {
278 // Camel-case words
279 rc += !lastWasAcceptable && kind == PC_LowerCaseLetter ? c.toUpper() : c;
280 }
281 }
282 lastWasAcceptable = acceptable;
283 }
284 return rc;
285}
286
287void FormLayoutRowDialog::updateObjectNames(bool updateLabel, bool updateField)
288{
289 // Generate label + field object names from the label text, that is,
290 // "&Zip code:" -> "zipcodeLabel", "zipcodeLineEdit" unless the user
291 // edited it.
292 const bool doUpdateLabel = !m_labelNameEdited && updateLabel;
293 const bool doUpdateField = !m_fieldNameEdited && updateField;
294 if (!doUpdateLabel && !doUpdateField)
295 return;
296
297 const QString prefix = prefixFromLabel(labelText());
298 // Set names
299 if (doUpdateLabel)
300 m_ui.labelNameLineEdit->setText(prefix + "Label"_L1);
301 if (doUpdateField)
302 m_ui.fieldNameLineEdit->setText(prefix + postFixFromClassName(fieldClass()));
303}
304
305void FormLayoutRowDialog::fieldClassChanged(int)
306{
307 updateObjectNames(false, true);
308}
309
310void FormLayoutRowDialog::labelNameEdited(const QString & /*text*/)
311{
312 m_labelNameEdited = true; // stop auto-updating after user change
313 updateOkButton();
314}
315
316void FormLayoutRowDialog::fieldNameEdited(const QString & /*text*/)
317{
318 m_fieldNameEdited = true; // stop auto-updating after user change
319 updateOkButton();
320}
321
322void FormLayoutRowDialog::buddyClicked()
323{
324 m_buddyClicked = true; // stop auto-updating after user change
325 updateOkButton();
326}
327
328/* Create a list of classes suitable for field widgets. Take the fixed base
329 * classes provided and look in the widget database for custom widgets derived
330 * from them ("QLineEdit", "CustomLineEdit", "QComboBox"...). */
331QStringList FormLayoutRowDialog::fieldWidgetClasses(QDesignerFormEditorInterface *core)
332{
333 static QStringList rc;
334 if (rc.isEmpty()) {
335 // Turn known base classes into list
336 QStringList baseClasses;
337 for (auto fw : fieldWidgetBaseClasses)
338 baseClasses.append(QLatin1StringView(fw));
339 // Scan for custom widgets that inherit them and store them in a
340 // multimap of base class->custom widgets unless we have a language
341 // extension installed which might do funny things with custom widgets.
342 QMultiHash<QString, QString> customClassMap; // Base class -> custom widgets map
343 if (qt_extension<QDesignerLanguageExtension *>(core->extensionManager(), core) == nullptr) {
344 const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase();
345 const int wdbCount = wdb->count();
346 for (int w = 0; w < wdbCount; ++w) {
347 // Check for non-container custom types that extend the
348 // respective base class.
349 const QDesignerWidgetDataBaseItemInterface *dbItem = wdb->item(w);
350 if (!dbItem->isPromoted() && !dbItem->isContainer() && dbItem->isCustom()) {
351 const int index = baseClasses.indexOf(dbItem->extends());
352 if (index != -1)
353 customClassMap.insert(baseClasses.at(index), dbItem->name());
354 }
355 }
356 }
357 // Compile final list, taking each base class and append custom widgets
358 // based on it.
359 for (const auto &baseClass : baseClasses) {
360 rc.append(baseClass);
361 rc += customClassMap.values(baseClass);
362 }
363 }
364 return rc;
365}
366
367// ------------------ Utilities
368
369static QFormLayout *managedFormLayout(const QDesignerFormEditorInterface *core, const QWidget *w)
370{
371 QLayout *l = nullptr;
372 if (LayoutInfo::managedLayoutType(core, w, &l) == LayoutInfo::Form)
373 return qobject_cast<QFormLayout *>(l);
374 return nullptr;
375}
376
377// Create the widgets of a control row and apply text properties contained
378// in the struct, called by addFormLayoutRow()
379static std::pair<QWidget *,QWidget *>
381 QDesignerFormWindowInterface *formWindow)
382{
383 QDesignerFormEditorInterface *core = formWindow->core();
384 QDesignerWidgetFactoryInterface *wf = core->widgetFactory();
385
386 std::pair<QWidget *,QWidget *> rc{wf->createWidget(u"QLabel"_s, parent),
387 wf->createWidget(row.fieldClassName, parent)};
388 // Set up properties of the label
389 const QString objectNameProperty = u"objectName"_s;
390 QDesignerPropertySheetExtension *labelSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), rc.first);
391 int nameIndex = labelSheet->indexOf(objectNameProperty);
392 labelSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.labelName)));
393 labelSheet->setChanged(nameIndex, true);
394 formWindow->ensureUniqueObjectName(rc.first);
395 const int textIndex = labelSheet->indexOf(u"text"_s);
396 labelSheet->setProperty(textIndex, QVariant::fromValue(PropertySheetStringValue(row.labelText)));
397 labelSheet->setChanged(textIndex, true);
398 // Set up properties of the control
399 QDesignerPropertySheetExtension *controlSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), rc.second);
400 nameIndex = controlSheet->indexOf(objectNameProperty);
401 controlSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.fieldName)));
402 controlSheet->setChanged(nameIndex, true);
403 formWindow->ensureUniqueObjectName(rc.second);
404 return rc;
405}
406
407// Create a command sequence on the undo stack of the form window that creates
408// the widgets of the row and inserts them into the form layout.
409static void addFormLayoutRow(const FormLayoutRow &formLayoutRow, int row, QWidget *w,
410 QDesignerFormWindowInterface *formWindow)
411{
412 QFormLayout *formLayout = managedFormLayout(formWindow->core(), w);
413 Q_ASSERT(formLayout);
414 QUndoStack *undoStack = formWindow->commandHistory();
415 const QString macroName = QCoreApplication::translate("Command", "Add '%1' to '%2'").arg(formLayoutRow.labelText, formLayout->objectName());
416 undoStack->beginMacro(macroName);
417
418 // Create a list of widget insertion commands and pass them a cell position
419 const auto widgetPair = createWidgets(formLayoutRow, w, formWindow);
420
421 InsertWidgetCommand *labelCmd = new InsertWidgetCommand(formWindow);
422 labelCmd->init(widgetPair.first, false, row, 0);
423 undoStack->push(labelCmd);
424 InsertWidgetCommand *controlCmd = new InsertWidgetCommand(formWindow);
425 controlCmd->init(widgetPair.second, false, row, 1);
426 undoStack->push(controlCmd);
427 if (formLayoutRow.buddy) {
428 SetPropertyCommand *buddyCommand = new SetPropertyCommand(formWindow);
429 buddyCommand->init(widgetPair.first, buddyPropertyC, widgetPair.second->objectName());
430 undoStack->push(buddyCommand);
431 }
432 undoStack->endMacro();
433}
434
435// ---------------- FormLayoutMenu
438 m_separator1(new QAction(this)),
439 m_populateFormAction(new QAction(tr("Add form layout row..."), this)),
440 m_separator2(new QAction(this))
441{
445}
446
448{
449 switch (LayoutInfo::managedLayoutType(fw->core(), w)) {
450 case LayoutInfo::Form:
455 m_widget = w;
456 break;
457 default:
458 m_widget = nullptr;
459 break;
460 }
461}
462
464{
466 Q_ASSERT(m_widget && fw);
468
472
473 if (dialog.exec() != QDialog::Accepted)
474 return;
476}
477
486}
487
488QT_END_NAMESPACE
489
490#include "formlayoutmenu.moc"
friend class QWidget
Definition qpainter.h:421
static QStringList fieldWidgetClasses(QDesignerFormEditorInterface *core)
static const char * fieldWidgetBaseClasses[]
static constexpr auto buddyPropertyC
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static void addFormLayoutRow(const FormLayoutRow &formLayoutRow, int row, QWidget *w, QDesignerFormWindowInterface *formWindow)
static QString postFixFromClassName(QString className)
static QFormLayout * managedFormLayout(const QDesignerFormEditorInterface *core, const QWidget *w)
static QString prefixFromLabel(const QString &prefix)
static std::pair< QWidget *, QWidget * > createWidgets(const FormLayoutRow &row, QWidget *parent, QDesignerFormWindowInterface *formWindow)
static PrefixCharacterKind prefixCharacterKind(const QChar &c)