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