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 "widgetdatabase_p.h" |
30 | #include "widgetfactory_p.h" |
31 | #include "spacer_widget_p.h" |
32 | #include "abstractlanguage.h" |
33 | #include "pluginmanager_p.h" |
34 | #include "qdesigner_widgetbox_p.h" |
35 | #include "qdesigner_utils_p.h" |
36 | #include <QtDesigner/private/ui4_p.h> |
37 | |
38 | #include <QtDesigner/propertysheet.h> |
39 | #include <QtDesigner/qextensionmanager.h> |
40 | #include <QtDesigner/abstractformeditor.h> |
41 | |
42 | #include <QtUiPlugin/customwidget.h> |
43 | |
44 | #include <QtCore/qxmlstream.h> |
45 | |
46 | #include <QtCore/qscopedpointer.h> |
47 | #include <QtCore/qdebug.h> |
48 | #include <QtCore/qmetaobject.h> |
49 | #include <QtCore/qtextstream.h> |
50 | #include <QtCore/qcoreapplication.h> |
51 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | namespace { |
55 | enum { debugWidgetDataBase = 0 }; |
56 | } |
57 | |
58 | namespace qdesigner_internal { |
59 | |
60 | // ---------------------------------------------------------- |
61 | WidgetDataBaseItem::WidgetDataBaseItem(const QString &name, const QString &group) |
62 | : m_name(name), |
63 | m_group(group), |
64 | m_compat(0), |
65 | m_container(0), |
66 | m_custom(0), |
67 | m_promoted(0) |
68 | { |
69 | } |
70 | |
71 | QString WidgetDataBaseItem::name() const |
72 | { |
73 | return m_name; |
74 | } |
75 | |
76 | void WidgetDataBaseItem::setName(const QString &name) |
77 | { |
78 | m_name = name; |
79 | } |
80 | |
81 | QString WidgetDataBaseItem::group() const |
82 | { |
83 | return m_group; |
84 | } |
85 | |
86 | void WidgetDataBaseItem::setGroup(const QString &group) |
87 | { |
88 | m_group = group; |
89 | } |
90 | |
91 | QString WidgetDataBaseItem::toolTip() const |
92 | { |
93 | return m_toolTip; |
94 | } |
95 | |
96 | void WidgetDataBaseItem::setToolTip(const QString &toolTip) |
97 | { |
98 | m_toolTip = toolTip; |
99 | } |
100 | |
101 | QString WidgetDataBaseItem::whatsThis() const |
102 | { |
103 | return m_whatsThis; |
104 | } |
105 | |
106 | void WidgetDataBaseItem::setWhatsThis(const QString &whatsThis) |
107 | { |
108 | m_whatsThis = whatsThis; |
109 | } |
110 | |
111 | QString WidgetDataBaseItem::includeFile() const |
112 | { |
113 | return m_includeFile; |
114 | } |
115 | |
116 | void WidgetDataBaseItem::setIncludeFile(const QString &includeFile) |
117 | { |
118 | m_includeFile = includeFile; |
119 | } |
120 | |
121 | QIcon WidgetDataBaseItem::icon() const |
122 | { |
123 | return m_icon; |
124 | } |
125 | |
126 | void WidgetDataBaseItem::setIcon(const QIcon &icon) |
127 | { |
128 | m_icon = icon; |
129 | } |
130 | |
131 | bool WidgetDataBaseItem::isCompat() const |
132 | { |
133 | return m_compat; |
134 | } |
135 | |
136 | void WidgetDataBaseItem::setCompat(bool b) |
137 | { |
138 | m_compat = b; |
139 | } |
140 | |
141 | bool WidgetDataBaseItem::isContainer() const |
142 | { |
143 | return m_container; |
144 | } |
145 | |
146 | void WidgetDataBaseItem::setContainer(bool b) |
147 | { |
148 | m_container = b; |
149 | } |
150 | |
151 | bool WidgetDataBaseItem::isCustom() const |
152 | { |
153 | return m_custom; |
154 | } |
155 | |
156 | void WidgetDataBaseItem::setCustom(bool b) |
157 | { |
158 | m_custom = b; |
159 | } |
160 | |
161 | QString WidgetDataBaseItem::pluginPath() const |
162 | { |
163 | return m_pluginPath; |
164 | } |
165 | |
166 | void WidgetDataBaseItem::setPluginPath(const QString &path) |
167 | { |
168 | m_pluginPath = path; |
169 | } |
170 | |
171 | bool WidgetDataBaseItem::isPromoted() const |
172 | { |
173 | return m_promoted; |
174 | } |
175 | |
176 | void WidgetDataBaseItem::setPromoted(bool b) |
177 | { |
178 | m_promoted = b; |
179 | } |
180 | |
181 | QString WidgetDataBaseItem::extends() const |
182 | { |
183 | return m_extends; |
184 | } |
185 | |
186 | void WidgetDataBaseItem::setExtends(const QString &s) |
187 | { |
188 | m_extends = s; |
189 | } |
190 | |
191 | void WidgetDataBaseItem::setDefaultPropertyValues(const QList<QVariant> &list) |
192 | { |
193 | m_defaultPropertyValues = list; |
194 | } |
195 | |
196 | QList<QVariant> WidgetDataBaseItem::defaultPropertyValues() const |
197 | { |
198 | return m_defaultPropertyValues; |
199 | } |
200 | |
201 | QStringList WidgetDataBaseItem::fakeSlots() const |
202 | { |
203 | return m_fakeSlots; |
204 | } |
205 | |
206 | void WidgetDataBaseItem::setFakeSlots(const QStringList &fs) |
207 | { |
208 | m_fakeSlots = fs; |
209 | } |
210 | |
211 | QStringList WidgetDataBaseItem::fakeSignals() const |
212 | { |
213 | return m_fakeSignals; |
214 | } |
215 | |
216 | void WidgetDataBaseItem::setFakeSignals(const QStringList &fs) |
217 | { |
218 | m_fakeSignals = fs; |
219 | } |
220 | |
221 | QString WidgetDataBaseItem::addPageMethod() const |
222 | { |
223 | return m_addPageMethod; |
224 | } |
225 | |
226 | void WidgetDataBaseItem::setAddPageMethod(const QString &m) |
227 | { |
228 | m_addPageMethod = m; |
229 | } |
230 | |
231 | WidgetDataBaseItem *WidgetDataBaseItem::clone(const QDesignerWidgetDataBaseItemInterface *item) |
232 | { |
233 | WidgetDataBaseItem *rc = new WidgetDataBaseItem(item->name(), item->group()); |
234 | |
235 | rc->setToolTip(item->toolTip()); |
236 | rc->setWhatsThis(item->whatsThis()); |
237 | rc->setIncludeFile(item->includeFile()); |
238 | rc->setIcon(item->icon()); |
239 | rc->setCompat(item->isCompat()); |
240 | rc->setContainer(item->isContainer()); |
241 | rc->setCustom(item->isCustom() ); |
242 | rc->setPluginPath(item->pluginPath()); |
243 | rc->setPromoted(item->isPromoted()); |
244 | rc->setExtends(item->extends()); |
245 | rc->setDefaultPropertyValues(item->defaultPropertyValues()); |
246 | // container page method, fake slots and signals ignored here.y |
247 | return rc; |
248 | } |
249 | |
250 | // ---------------------------------------------------------- |
251 | WidgetDataBase::WidgetDataBase(QDesignerFormEditorInterface *core, QObject *parent) |
252 | : QDesignerWidgetDataBaseInterface(parent), |
253 | m_core(core) |
254 | { |
255 | #define DECLARE_LAYOUT(L, C) |
256 | #define DECLARE_COMPAT_WIDGET(W, C) DECLARE_WIDGET(W, C) |
257 | #define DECLARE_WIDGET(W, C) append(new WidgetDataBaseItem(QString::fromUtf8(#W))); |
258 | |
259 | #include <widgets.table> |
260 | |
261 | #undef DECLARE_COMPAT_WIDGET |
262 | #undef DECLARE_LAYOUT |
263 | #undef DECLARE_WIDGET |
264 | #undef DECLARE_WIDGET_1 |
265 | |
266 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "Line" ))); |
267 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "Spacer" ))); |
268 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QSplitter" ))); |
269 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QLayoutWidget" ))); |
270 | // QDesignerWidget is used as central widget and as container for tab widgets, etc. |
271 | WidgetDataBaseItem *designerWidgetItem = new WidgetDataBaseItem(QString::fromUtf8(str: "QDesignerWidget" )); |
272 | designerWidgetItem->setContainer(true); |
273 | append(item: designerWidgetItem); |
274 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QDesignerDialog" ))); |
275 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QDesignerMenu" ))); |
276 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QDesignerMenuBar" ))); |
277 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QDesignerDockWidget" ))); |
278 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QAction" ))); |
279 | append(item: new WidgetDataBaseItem(QString::fromUtf8(str: "QButtonGroup" ))); |
280 | |
281 | // ### remove me |
282 | // ### check the casts |
283 | |
284 | #if 0 // ### enable me after 4.1 |
285 | item(indexOfClassName(QStringLiteral("QToolBar" )))->setContainer(true); |
286 | #endif |
287 | |
288 | item(index: indexOfClassName(QStringLiteral("QTabWidget" )))->setContainer(true); |
289 | item(index: indexOfClassName(QStringLiteral("QGroupBox" )))->setContainer(true); |
290 | item(index: indexOfClassName(QStringLiteral("QScrollArea" )))->setContainer(true); |
291 | item(index: indexOfClassName(QStringLiteral("QStackedWidget" )))->setContainer(true); |
292 | item(index: indexOfClassName(QStringLiteral("QToolBox" )))->setContainer(true); |
293 | item(index: indexOfClassName(QStringLiteral("QFrame" )))->setContainer(true); |
294 | item(index: indexOfClassName(QStringLiteral("QLayoutWidget" )))->setContainer(true); |
295 | item(index: indexOfClassName(QStringLiteral("QDesignerWidget" )))->setContainer(true); |
296 | item(index: indexOfClassName(QStringLiteral("QDesignerDialog" )))->setContainer(true); |
297 | item(index: indexOfClassName(QStringLiteral("QSplitter" )))->setContainer(true); |
298 | item(index: indexOfClassName(QStringLiteral("QMainWindow" )))->setContainer(true); |
299 | item(index: indexOfClassName(QStringLiteral("QDockWidget" )))->setContainer(true); |
300 | item(index: indexOfClassName(QStringLiteral("QDesignerDockWidget" )))->setContainer(true); |
301 | item(index: indexOfClassName(QStringLiteral("QMdiArea" )))->setContainer(true); |
302 | item(index: indexOfClassName(QStringLiteral("QWizard" )))->setContainer(true); |
303 | item(index: indexOfClassName(QStringLiteral("QWizardPage" )))->setContainer(true); |
304 | |
305 | item(index: indexOfClassName(QStringLiteral("QWidget" )))->setContainer(true); |
306 | item(index: indexOfClassName(QStringLiteral("QDialog" )))->setContainer(true); |
307 | } |
308 | |
309 | WidgetDataBase::~WidgetDataBase() = default; |
310 | |
311 | QDesignerFormEditorInterface *WidgetDataBase::core() const |
312 | { |
313 | return m_core; |
314 | } |
315 | |
316 | int WidgetDataBase::indexOfObject(QObject *object, bool /*resolveName*/) const |
317 | { |
318 | QExtensionManager *mgr = m_core->extensionManager(); |
319 | QDesignerLanguageExtension *lang = qt_extension<QDesignerLanguageExtension*> (manager: mgr, object: m_core); |
320 | |
321 | QString id; |
322 | |
323 | if (lang) |
324 | id = lang->classNameOf(object); |
325 | |
326 | if (id.isEmpty()) |
327 | id = WidgetFactory::classNameOf(core: m_core,o: object); |
328 | |
329 | return QDesignerWidgetDataBaseInterface::indexOfClassName(className: id); |
330 | } |
331 | |
332 | static WidgetDataBaseItem *createCustomWidgetItem(const QDesignerCustomWidgetInterface *c, |
333 | const QDesignerCustomWidgetData &data) |
334 | { |
335 | WidgetDataBaseItem *item = new WidgetDataBaseItem(c->name(), c->group()); |
336 | item->setContainer(c->isContainer()); |
337 | item->setCustom(true); |
338 | item->setIcon(c->icon()); |
339 | item->setIncludeFile(c->includeFile()); |
340 | item->setToolTip(c->toolTip()); |
341 | item->setWhatsThis(c->whatsThis()); |
342 | item->setPluginPath(data.pluginPath()); |
343 | item->setAddPageMethod(data.xmlAddPageMethod()); |
344 | item->setExtends(data.xmlExtends()); |
345 | return item; |
346 | } |
347 | |
348 | void WidgetDataBase::loadPlugins() |
349 | { |
350 | typedef QMap<QString, int> NameIndexMap; |
351 | using ItemList = QList<QDesignerWidgetDataBaseItemInterface *>; |
352 | using NameSet = QSet<QString>; |
353 | // 1) create a map of existing custom classes |
354 | NameIndexMap existingCustomClasses; |
355 | NameSet nonCustomClasses; |
356 | const int count = m_items.size(); |
357 | for (int i = 0; i < count; i++) { |
358 | const QDesignerWidgetDataBaseItemInterface* item = m_items[i]; |
359 | if (item->isCustom() && !item->isPromoted()) |
360 | existingCustomClasses.insert(akey: item->name(), avalue: i); |
361 | else |
362 | nonCustomClasses.insert(value: item->name()); |
363 | } |
364 | // 2) create a list plugins |
365 | ItemList pluginList; |
366 | const QDesignerPluginManager *pm = m_core->pluginManager(); |
367 | const auto &customWidgets = pm->registeredCustomWidgets(); |
368 | for (QDesignerCustomWidgetInterface* c : customWidgets) |
369 | pluginList += createCustomWidgetItem(c, data: pm->customWidgetData(w: c)); |
370 | |
371 | // 3) replace custom classes or add new ones, remove them from existingCustomClasses, |
372 | // leaving behind deleted items |
373 | unsigned replacedPlugins = 0; |
374 | unsigned addedPlugins = 0; |
375 | unsigned removedPlugins = 0; |
376 | if (!pluginList.isEmpty()) { |
377 | for (QDesignerWidgetDataBaseItemInterface *pluginItem : qAsConst(t&: pluginList)) { |
378 | const QString pluginName = pluginItem->name(); |
379 | NameIndexMap::iterator existingIt = existingCustomClasses.find(akey: pluginName); |
380 | if (existingIt == existingCustomClasses.end()) { |
381 | // Add new class. |
382 | if (nonCustomClasses.contains(value: pluginName)) { |
383 | designerWarning(message: tr(s: "A custom widget plugin whose class name (%1) matches that of an existing class has been found." ).arg(a: pluginName)); |
384 | } else { |
385 | append(item: pluginItem); |
386 | addedPlugins++; |
387 | } |
388 | } else { |
389 | // replace existing info |
390 | const int existingIndex = existingIt.value(); |
391 | delete m_items[existingIndex]; |
392 | m_items[existingIndex] = pluginItem; |
393 | existingCustomClasses.erase(it: existingIt); |
394 | replacedPlugins++; |
395 | |
396 | } |
397 | } |
398 | } |
399 | // 4) remove classes that have not been matched. The stored indexes become invalid while deleting. |
400 | if (!existingCustomClasses.isEmpty()) { |
401 | NameIndexMap::const_iterator cend = existingCustomClasses.constEnd(); |
402 | for (NameIndexMap::const_iterator it = existingCustomClasses.constBegin();it != cend; ++it ) { |
403 | const int index = indexOfClassName(className: it.key()); |
404 | if (index != -1) { |
405 | remove(index); |
406 | removedPlugins++; |
407 | } |
408 | } |
409 | } |
410 | if (debugWidgetDataBase) |
411 | qDebug() << "WidgetDataBase::loadPlugins(): " << addedPlugins << " added, " << replacedPlugins << " replaced, " << removedPlugins << "deleted." ; |
412 | } |
413 | |
414 | void WidgetDataBase::remove(int index) |
415 | { |
416 | Q_ASSERT(index < m_items.size()); |
417 | delete m_items.takeAt(i: index); |
418 | } |
419 | |
420 | QList<QVariant> WidgetDataBase::defaultPropertyValues(const QString &name) |
421 | { |
422 | WidgetFactory *factory = qobject_cast<WidgetFactory *>(object: m_core->widgetFactory()); |
423 | Q_ASSERT(factory); |
424 | // Create non-widgets, widgets in order |
425 | QObject* object = factory->createObject(className: name, parent: nullptr); |
426 | if (!object) |
427 | object = factory->createWidget(className: name, parentWidget: nullptr); |
428 | if (!object) { |
429 | qDebug() << "** WARNING Factory failed to create " << name; |
430 | return {}; |
431 | } |
432 | // Get properties from sheet. |
433 | QVariantList result; |
434 | if (const QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: m_core->extensionManager(), object)) { |
435 | const int propertyCount = sheet->count(); |
436 | for (int i = 0; i < propertyCount; ++i) { |
437 | result.append(t: sheet->property(index: i)); |
438 | } |
439 | } |
440 | delete object; |
441 | return result; |
442 | } |
443 | |
444 | void WidgetDataBase::grabDefaultPropertyValues() |
445 | { |
446 | const int itemCount = count(); |
447 | for (int i = 0; i < itemCount; ++i) { |
448 | QDesignerWidgetDataBaseItemInterface *dbItem = item(index: i); |
449 | const auto default_prop_values = defaultPropertyValues(name: dbItem->name()); |
450 | dbItem->setDefaultPropertyValues(default_prop_values); |
451 | } |
452 | } |
453 | |
454 | void WidgetDataBase::grabStandardWidgetBoxIcons() |
455 | { |
456 | // At this point, grab the default icons for the non-custom widgets from |
457 | // the widget box. They will show up in the object inspector. |
458 | if (const QDesignerWidgetBox *wb = qobject_cast<const QDesignerWidgetBox *>(object: m_core->widgetBox())) { |
459 | const QString qWidgetClass = QStringLiteral("QWidget" ); |
460 | const int itemCount = count(); |
461 | for (int i = 0; i < itemCount; ++i) { |
462 | QDesignerWidgetDataBaseItemInterface *dbItem = item(index: i); |
463 | if (!dbItem->isCustom() && dbItem->icon().isNull()) { |
464 | // Careful not to catch the layout icons when looking for |
465 | // QWidget |
466 | const QString name = dbItem->name(); |
467 | if (name == qWidgetClass) { |
468 | dbItem->setIcon(wb->iconForWidget(className: name, QStringLiteral("Containers" ))); |
469 | } else { |
470 | dbItem->setIcon(wb->iconForWidget(className: name)); |
471 | } |
472 | } |
473 | } |
474 | } |
475 | } |
476 | |
477 | // --------------------- Functions relevant generation of new forms based on widgets (apart from the standard templates) |
478 | |
479 | enum { NewFormWidth = 400, NewFormHeight = 300 }; |
480 | |
481 | // Check if class is suitable to generate a form from |
482 | static inline bool isExistingTemplate(const QString &className) |
483 | { |
484 | return className == QStringLiteral("QWidget" ) || className == QStringLiteral("QDialog" ) || className == QStringLiteral("QMainWindow" ); |
485 | } |
486 | |
487 | // Check if class is suitable to generate a form from |
488 | static inline bool suitableForNewForm(const QString &className) |
489 | { |
490 | if (className.isEmpty()) // Missing custom widget information |
491 | return false; |
492 | if (className == QStringLiteral("QSplitter" )) |
493 | return false; |
494 | if (className.startsWith(QStringLiteral("QDesigner" )) || className.startsWith(QStringLiteral("QLayout" ))) |
495 | return false; |
496 | return true; |
497 | } |
498 | |
499 | // Return a list of widget classes from which new forms can be generated. |
500 | // Suitable for 'New form' wizards in integrations. |
501 | QStringList WidgetDataBase::formWidgetClasses(const QDesignerFormEditorInterface *core) |
502 | { |
503 | static QStringList rc; |
504 | if (rc.isEmpty()) { |
505 | const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); |
506 | const int widgetCount = wdb->count(); |
507 | for (int i = 0; i < widgetCount; i++) { |
508 | const QDesignerWidgetDataBaseItemInterface *item = wdb->item(index: i); |
509 | if (item->isContainer() && !item->isCustom() && !item->isPromoted()) { |
510 | const QString name = item->name(); // Standard Widgets: no existing templates |
511 | if (!isExistingTemplate(className: name) && suitableForNewForm(className: name)) |
512 | rc += name; |
513 | } |
514 | } |
515 | } |
516 | return rc; |
517 | } |
518 | |
519 | // Return a list of custom widget classes from which new forms can be generated. |
520 | // Suitable for 'New form' wizards in integrations. |
521 | QStringList WidgetDataBase::customFormWidgetClasses(const QDesignerFormEditorInterface *core) |
522 | { |
523 | QStringList rc; |
524 | const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); |
525 | const int widgetCount = wdb->count(); |
526 | for (int i = 0; i < widgetCount; i++) { // Custom widgets: check name and base class. |
527 | const QDesignerWidgetDataBaseItemInterface *item = wdb->item(index: i); |
528 | if (item->isContainer() && item->isCustom() && !item->isPromoted()) { |
529 | if (suitableForNewForm(className: item->name()) && suitableForNewForm(className: item->extends())) |
530 | rc += item->name(); |
531 | } |
532 | } |
533 | return rc; |
534 | } |
535 | |
536 | // Get XML for a new form from the widget box. Change objectName/geometry |
537 | // properties to be suitable for new forms |
538 | static QString xmlFromWidgetBox(const QDesignerFormEditorInterface *core, const QString &className, const QString &objectName) |
539 | { |
540 | using PropertyList = QList<DomProperty *>; |
541 | |
542 | QDesignerWidgetBoxInterface::Widget widget; |
543 | const bool found = QDesignerWidgetBox::findWidget(wbox: core->widgetBox(), className, category: QString(), widgetData: &widget); |
544 | if (!found) |
545 | return QString(); |
546 | QScopedPointer<DomUI> domUI(QDesignerWidgetBox::xmlToUi(name: className, xml: widget.domXml(), insertFakeTopLevel: false)); |
547 | if (domUI.isNull()) |
548 | return QString(); |
549 | domUI->setAttributeVersion(QStringLiteral("4.0" )); |
550 | DomWidget *domWidget = domUI->elementWidget(); |
551 | if (!domWidget) |
552 | return QString(); |
553 | // Properties: Remove the "objectName" property in favour of the name attribute and check geometry. |
554 | domWidget->setAttributeName(objectName); |
555 | const QString geometryProperty = QStringLiteral("geometry" ); |
556 | const QString objectNameProperty = QStringLiteral("objectName" ); |
557 | PropertyList properties = domWidget->elementProperty(); |
558 | for (PropertyList::iterator it = properties.begin(); it != properties.end(); ) { |
559 | DomProperty *property = *it; |
560 | if (property->attributeName() == objectNameProperty) { // remove "objectName" |
561 | it = properties.erase(it); |
562 | delete property; |
563 | } else { |
564 | if (property->attributeName() == geometryProperty) { // Make sure form is at least 400, 300 |
565 | if (DomRect *geom = property->elementRect()) { |
566 | if (geom->elementWidth() < NewFormWidth) |
567 | geom->setElementWidth(NewFormWidth); |
568 | if (geom->elementHeight() < NewFormHeight) |
569 | geom->setElementHeight(NewFormHeight); |
570 | } |
571 | } |
572 | ++it; |
573 | } |
574 | } |
575 | // Add a window title property |
576 | DomString *windowTitleString = new DomString; |
577 | windowTitleString->setText(objectName); |
578 | DomProperty *windowTitleProperty = new DomProperty; |
579 | windowTitleProperty->setAttributeName(QStringLiteral("windowTitle" )); |
580 | windowTitleProperty->setElementString(windowTitleString); |
581 | properties.push_back(t: windowTitleProperty); |
582 | // ------ |
583 | domWidget->setElementProperty(properties); |
584 | // Embed in in DomUI and get string. Omit the version number. |
585 | domUI->setElementClass(objectName); |
586 | |
587 | QString rc; |
588 | { // Serialize domUI |
589 | QXmlStreamWriter writer(&rc); |
590 | writer.setAutoFormatting(true); |
591 | writer.setAutoFormattingIndent(1); |
592 | writer.writeStartDocument(); |
593 | domUI->write(writer); |
594 | writer.writeEndDocument(); |
595 | } |
596 | return rc; |
597 | } |
598 | |
599 | // Generate default standard ui new form xml based on the class passed on as similarClassName. |
600 | static QString generateNewFormXML(const QString &className, const QString &similarClassName, const QString &name) |
601 | { |
602 | QString rc; |
603 | QTextStream str(&rc); |
604 | str << R"(<ui version="4.0"><class>)" << name << "</class>" |
605 | << R"(<widget class=")" << className << R"(" name=")" << name << R"(">)" |
606 | << R"(<property name="geometry" ><rect><x>0</x><y>0</y><width>)" |
607 | << NewFormWidth << "</width><height>" << NewFormHeight << "</height></rect></property>" |
608 | << R"(<property name="windowTitle"><string>)" << name << "</string></property>\n" ; |
609 | |
610 | if (similarClassName == QLatin1String("QMainWindow" )) { |
611 | str << R"(<widget class="QWidget" name="centralwidget"/>)" ; |
612 | } else if (similarClassName == QLatin1String("QWizard" )) { |
613 | str << R"(<widget class="QWizardPage" name="wizardPage1"/><widget class="QWizardPage" name="wizardPage2"/>)" ; |
614 | } else if (similarClassName == QLatin1String("QDockWidget" )) { |
615 | str << R"(<widget class="QWidget" name="dockWidgetContents"/>)" ; |
616 | } |
617 | str << "</widget></ui>\n" ; |
618 | return rc; |
619 | } |
620 | |
621 | // Generate a form template using a class name obtained from formWidgetClasses(), customFormWidgetClasses(). |
622 | QString WidgetDataBase::formTemplate(const QDesignerFormEditorInterface *core, const QString &className, const QString &objectName) |
623 | { |
624 | // How to find suitable XML for a class: |
625 | // 1) Look in widget box (as all the required centralwidgets, tab widget pages, etc. should be there). |
626 | const QString widgetBoxXml = xmlFromWidgetBox(core, className, objectName); |
627 | if (!widgetBoxXml.isEmpty()) |
628 | return widgetBoxXml; |
629 | // 2) If that fails, only custom main windows, custom dialogs and unsupported Qt Widgets should |
630 | // be left over. Generate something that is similar to the default templates. Find a similar class. |
631 | const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); |
632 | QString similarClass = QStringLiteral("QWidget" ); |
633 | const int index = wdb->indexOfClassName(className); |
634 | if (index != -1) { |
635 | const QDesignerWidgetDataBaseItemInterface *item = wdb->item(index); |
636 | similarClass = item->isCustom() ? item->extends() : item->name(); |
637 | } |
638 | // Generate standard ui based on the class passed on as baseClassName. |
639 | const QString rc = generateNewFormXML(className, similarClassName: similarClass, name: objectName); |
640 | return rc; |
641 | } |
642 | |
643 | // Set a fixed size on a XML template |
644 | QString WidgetDataBase::scaleFormTemplate(const QString &xml, const QSize &size, bool fixed) |
645 | { |
646 | QScopedPointer<DomUI> domUI(QDesignerWidgetBox::xmlToUi(QStringLiteral("Form" ), xml, insertFakeTopLevel: false)); |
647 | if (!domUI) |
648 | return QString(); |
649 | DomWidget *domWidget = domUI->elementWidget(); |
650 | if (!domWidget) |
651 | return QString(); |
652 | // Properties: Find/Ensure the geometry, minimum and maximum sizes properties |
653 | const QString geometryPropertyName = QStringLiteral("geometry" ); |
654 | const QString minimumSizePropertyName = QStringLiteral("minimumSize" ); |
655 | const QString maximumSizePropertyName = QStringLiteral("maximumSize" ); |
656 | DomProperty *geomProperty = nullptr; |
657 | DomProperty *minimumSizeProperty = nullptr; |
658 | DomProperty *maximumSizeProperty = nullptr; |
659 | |
660 | auto properties = domWidget->elementProperty(); |
661 | for (DomProperty *p : properties) { |
662 | const QString name = p->attributeName(); |
663 | if (name == geometryPropertyName) { |
664 | geomProperty = p; |
665 | } else { |
666 | if (name == minimumSizePropertyName) { |
667 | minimumSizeProperty = p; |
668 | } else { |
669 | if (name == maximumSizePropertyName) |
670 | maximumSizeProperty = p; |
671 | } |
672 | } |
673 | } |
674 | if (!geomProperty) { |
675 | geomProperty = new DomProperty; |
676 | geomProperty->setAttributeName(geometryPropertyName); |
677 | geomProperty->setElementRect(new DomRect); |
678 | properties.push_front(t: geomProperty); |
679 | } |
680 | if (fixed) { |
681 | if (!minimumSizeProperty) { |
682 | minimumSizeProperty = new DomProperty; |
683 | minimumSizeProperty->setAttributeName(minimumSizePropertyName); |
684 | minimumSizeProperty->setElementSize(new DomSize); |
685 | properties.push_back(t: minimumSizeProperty); |
686 | } |
687 | if (!maximumSizeProperty) { |
688 | maximumSizeProperty = new DomProperty; |
689 | maximumSizeProperty->setAttributeName(maximumSizePropertyName); |
690 | maximumSizeProperty->setElementSize(new DomSize); |
691 | properties.push_back(t: maximumSizeProperty); |
692 | } |
693 | } |
694 | // Set values of geometry, minimum and maximum sizes properties |
695 | const int width = size.width(); |
696 | const int height = size.height(); |
697 | if (DomRect *geom = geomProperty->elementRect()) { |
698 | geom->setElementWidth(width); |
699 | geom->setElementHeight(height); |
700 | } |
701 | if (fixed) { |
702 | if (DomSize *s = minimumSizeProperty->elementSize()) { |
703 | s->setElementWidth(width); |
704 | s->setElementHeight(height); |
705 | } |
706 | if (DomSize *s = maximumSizeProperty->elementSize()) { |
707 | s->setElementWidth(width); |
708 | s->setElementHeight(height); |
709 | } |
710 | } |
711 | // write back |
712 | domWidget->setElementProperty(properties); |
713 | |
714 | QString rc; |
715 | { // serialize domUI |
716 | QXmlStreamWriter writer(&rc); |
717 | writer.setAutoFormatting(true); |
718 | writer.setAutoFormattingIndent(1); |
719 | writer.writeStartDocument(); |
720 | domUI->write(writer); |
721 | writer.writeEndDocument(); |
722 | } |
723 | |
724 | return rc; |
725 | } |
726 | |
727 | // ---- free functions |
728 | QDESIGNER_SHARED_EXPORT IncludeSpecification includeSpecification(QString includeFile) |
729 | { |
730 | const bool global = !includeFile.isEmpty() && |
731 | includeFile[0] == QLatin1Char('<') && |
732 | includeFile[includeFile.size() - 1] == QLatin1Char('>'); |
733 | if (global) { |
734 | includeFile.remove(i: includeFile.size() - 1, len: 1); |
735 | includeFile.remove(i: 0, len: 1); |
736 | } |
737 | return IncludeSpecification(includeFile, global ? IncludeGlobal : IncludeLocal); |
738 | } |
739 | |
740 | QDESIGNER_SHARED_EXPORT QString buildIncludeFile(QString includeFile, IncludeType includeType) { |
741 | if (includeType == IncludeGlobal && !includeFile.isEmpty()) { |
742 | includeFile.append(c: QLatin1Char('>')); |
743 | includeFile.insert(i: 0, c: QLatin1Char('<')); |
744 | } |
745 | return includeFile; |
746 | } |
747 | |
748 | |
749 | /* Appends a derived class to the database inheriting the data of the base class. Used |
750 | for custom and promoted widgets. |
751 | |
752 | Depending on whether an entry exists, the existing or a newly created entry is |
753 | returned. A return value of 0 indicates that the base class could not be found. */ |
754 | |
755 | QDESIGNER_SHARED_EXPORT QDesignerWidgetDataBaseItemInterface * |
756 | appendDerived(QDesignerWidgetDataBaseInterface *db, |
757 | const QString &className, const QString &group, |
758 | const QString &baseClassName, |
759 | const QString &includeFile, |
760 | bool promoted, bool custom) |
761 | { |
762 | if (debugWidgetDataBase) |
763 | qDebug() << "appendDerived " << className << " derived from " << baseClassName; |
764 | // Check. |
765 | if (className.isEmpty() || baseClassName.isEmpty()) { |
766 | qWarning(msg: "** WARNING %s called with an empty class names: '%s' extends '%s'." , |
767 | Q_FUNC_INFO, className.toUtf8().constData(), baseClassName.toUtf8().constData()); |
768 | return nullptr; |
769 | } |
770 | // Check whether item already exists. |
771 | QDesignerWidgetDataBaseItemInterface *derivedItem = nullptr; |
772 | const int existingIndex = db->indexOfClassName(className); |
773 | if ( existingIndex != -1) |
774 | derivedItem = db->item(index: existingIndex); |
775 | if (derivedItem) { |
776 | // Check the existing item for base class mismatch. This will likely |
777 | // happen when loading a file written by an instance with missing plugins. |
778 | // In that case, just warn and ignore the file properties. |
779 | // |
780 | // An empty base class indicates that it is not known (for example, for custom plugins). |
781 | // In this case, the widget DB is later updated once the widget is created |
782 | // by DOM (by querying the metaobject). Suppress the warning. |
783 | const QString existingBaseClass = derivedItem->extends(); |
784 | if (existingBaseClass.isEmpty() || baseClassName == existingBaseClass) |
785 | return derivedItem; |
786 | |
787 | // Warn about mismatches |
788 | designerWarning(message: QCoreApplication::translate(context: "WidgetDataBase" , |
789 | key: "The file contains a custom widget '%1' whose base class (%2)" |
790 | " differs from the current entry in the widget database (%3)." |
791 | " The widget database is left unchanged." ). |
792 | arg(a1: className, a2: baseClassName, a3: existingBaseClass)); |
793 | return derivedItem; |
794 | } |
795 | // Create this item, inheriting its base properties |
796 | const int baseIndex = db->indexOfClassName(className: baseClassName); |
797 | if (baseIndex == -1) { |
798 | if (debugWidgetDataBase) |
799 | qDebug() << "appendDerived failed due to missing base class" ; |
800 | return nullptr; |
801 | } |
802 | const QDesignerWidgetDataBaseItemInterface *baseItem = db->item(index: baseIndex); |
803 | derivedItem = WidgetDataBaseItem::clone(item: baseItem); |
804 | // Sort of hack: If base class is QWidget, we most likely |
805 | // do not want to inherit the container attribute. |
806 | static const QString qWidgetName = QStringLiteral("QWidget" ); |
807 | if (baseItem->name() == qWidgetName) |
808 | derivedItem->setContainer(false); |
809 | // set new props |
810 | derivedItem->setName(className); |
811 | derivedItem->setGroup(group); |
812 | derivedItem->setCustom(custom); |
813 | derivedItem->setPromoted(promoted); |
814 | derivedItem->setExtends(baseClassName); |
815 | derivedItem->setIncludeFile(includeFile); |
816 | db->append(item: derivedItem); |
817 | return derivedItem; |
818 | } |
819 | |
820 | /* Return a list of database items to which a class can be promoted to. */ |
821 | |
822 | QDESIGNER_SHARED_EXPORT WidgetDataBaseItemList |
823 | promotionCandidates(const QDesignerWidgetDataBaseInterface *db, |
824 | const QString &baseClassName) |
825 | { |
826 | WidgetDataBaseItemList rc; |
827 | // find existing promoted widgets deriving from base. |
828 | const int count = db->count(); |
829 | for (int i = 0; i < count; ++i) { |
830 | QDesignerWidgetDataBaseItemInterface *item = db->item(index: i); |
831 | if (item->isPromoted() && item->extends() == baseClassName) { |
832 | rc.push_back(t: item); |
833 | } |
834 | } |
835 | return rc; |
836 | } |
837 | } // namespace qdesigner_internal |
838 | |
839 | QT_END_NAMESPACE |
840 | |