1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | |
5 | #include "quiloader.h" |
6 | #include "quiloader_p.h" |
7 | |
8 | #include <QtUiPlugin/customwidget.h> |
9 | |
10 | #include <formbuilder.h> |
11 | #include <formbuilderextra_p.h> |
12 | #include <textbuilder_p.h> |
13 | #include <ui4_p.h> |
14 | |
15 | #include <QtWidgets/qapplication.h> |
16 | #include <QtWidgets/qlayout.h> |
17 | #include <QtWidgets/qwidget.h> |
18 | #include <QtWidgets/qtabwidget.h> |
19 | #include <QtWidgets/qtreewidget.h> |
20 | #include <QtWidgets/qlistwidget.h> |
21 | #include <QtWidgets/qtablewidget.h> |
22 | #include <QtWidgets/qtoolbox.h> |
23 | #include <QtWidgets/qcombobox.h> |
24 | #include <QtWidgets/qfontcombobox.h> |
25 | |
26 | #include <QtGui/qaction.h> |
27 | #include <QtGui/qactiongroup.h> |
28 | |
29 | #include <QtCore/qdebug.h> |
30 | #include <QtCore/qdatastream.h> |
31 | #include <QtCore/qmap.h> |
32 | #include <QtCore/qdir.h> |
33 | #include <QtCore/qlibraryinfo.h> |
34 | |
35 | QT_BEGIN_NAMESPACE |
36 | |
37 | typedef QMap<QString, bool> widget_map; |
38 | Q_GLOBAL_STATIC(widget_map, g_widgets) |
39 | |
40 | class QUiLoader; |
41 | class QUiLoaderPrivate; |
42 | |
43 | #ifndef QT_NO_DATASTREAM |
44 | // QUiTranslatableStringValue must be streamable since they become part of the QVariant-based |
45 | // mime data when dragging items in views with QAbstractItemView::InternalMove. |
46 | QDataStream &operator<<(QDataStream &out, const QUiTranslatableStringValue &s) |
47 | { |
48 | out << s.qualifier() << s.value(); |
49 | return out; |
50 | } |
51 | |
52 | QDataStream &operator>>(QDataStream &in, QUiTranslatableStringValue &s) |
53 | { |
54 | QByteArray qualifier, value; |
55 | in >> qualifier >> value; |
56 | s.setQualifier(qualifier); |
57 | s.setValue(value); |
58 | return in; |
59 | } |
60 | #endif // QT_NO_DATASTREAM |
61 | |
62 | QString QUiTranslatableStringValue::translate(const QByteArray &className, bool idBased) const |
63 | { |
64 | return idBased |
65 | ? qtTrId(id: m_qualifier.constData()) |
66 | : QCoreApplication::translate(context: className.constData(), key: m_value.constData(), disambiguation: m_qualifier.constData()); |
67 | } |
68 | |
69 | #ifdef QFORMINTERNAL_NAMESPACE |
70 | namespace QFormInternal |
71 | { |
72 | #endif |
73 | |
74 | class TranslatingTextBuilder : public QTextBuilder |
75 | { |
76 | public: |
77 | explicit TranslatingTextBuilder(bool idBased, bool trEnabled, const QByteArray &className) : |
78 | m_idBased(idBased), m_trEnabled(trEnabled), m_className(className) {} |
79 | |
80 | QVariant loadText(const DomProperty *icon) const override; |
81 | |
82 | QVariant toNativeValue(const QVariant &value) const override; |
83 | |
84 | bool idBased() const { return m_idBased; } |
85 | |
86 | private: |
87 | bool m_idBased; |
88 | bool m_trEnabled; |
89 | QByteArray m_className; |
90 | }; |
91 | |
92 | QVariant TranslatingTextBuilder::loadText(const DomProperty *text) const |
93 | { |
94 | const DomString *str = text->elementString(); |
95 | if (!str) |
96 | return QVariant(); |
97 | if (str->hasAttributeNotr()) { |
98 | const QString notr = str->attributeNotr(); |
99 | if (notr == QStringLiteral("true" ) || notr == QStringLiteral("yes" )) |
100 | return QVariant::fromValue(value: str->text()); |
101 | } |
102 | QUiTranslatableStringValue strVal; |
103 | strVal.setValue(str->text().toUtf8()); |
104 | if (m_idBased) |
105 | strVal.setQualifier(str->attributeId().toUtf8()); |
106 | else if (str->hasAttributeComment()) |
107 | strVal.setQualifier(str->attributeComment().toUtf8()); |
108 | return QVariant::fromValue(value: strVal); |
109 | } |
110 | |
111 | QVariant TranslatingTextBuilder::toNativeValue(const QVariant &value) const |
112 | { |
113 | if (value.canConvert<QUiTranslatableStringValue>()) { |
114 | QUiTranslatableStringValue tsv = qvariant_cast<QUiTranslatableStringValue>(v: value); |
115 | if (!m_trEnabled) |
116 | return QString::fromUtf8(utf8: tsv.value().constData()); |
117 | return QVariant::fromValue(value: tsv.translate(className: m_className, idBased: m_idBased)); |
118 | } |
119 | if (value.canConvert<QString>()) |
120 | return QVariant::fromValue(value: qvariant_cast<QString>(v: value)); |
121 | return value; |
122 | } |
123 | |
124 | // This is "exported" to linguist |
125 | const QUiItemRolePair qUiItemRoles[] = { |
126 | { .realRole: Qt::DisplayRole, .shadowRole: Qt::DisplayPropertyRole }, |
127 | #if QT_CONFIG(tooltip) |
128 | { .realRole: Qt::ToolTipRole, .shadowRole: Qt::ToolTipPropertyRole }, |
129 | #endif |
130 | #if QT_CONFIG(statustip) |
131 | { .realRole: Qt::StatusTipRole, .shadowRole: Qt::StatusTipPropertyRole }, |
132 | #endif |
133 | #if QT_CONFIG(whatsthis) |
134 | { .realRole: Qt::WhatsThisRole, .shadowRole: Qt::WhatsThisPropertyRole }, |
135 | #endif |
136 | { .realRole: -1 , .shadowRole: -1 } |
137 | }; |
138 | |
139 | static void recursiveReTranslate(QTreeWidgetItem *item, const QByteArray &class_name, bool idBased) |
140 | { |
141 | const QUiItemRolePair *irs = qUiItemRoles; |
142 | |
143 | int cnt = item->columnCount(); |
144 | for (int i = 0; i < cnt; ++i) { |
145 | for (unsigned j = 0; irs[j].shadowRole >= 0; j++) { |
146 | QVariant v = item->data(column: i, role: irs[j].shadowRole); |
147 | if (v.isValid()) { |
148 | QUiTranslatableStringValue tsv = qvariant_cast<QUiTranslatableStringValue>(v); |
149 | item->setData(column: i, role: irs[j].realRole, value: tsv.translate(className: class_name, idBased)); |
150 | } |
151 | } |
152 | } |
153 | |
154 | cnt = item->childCount(); |
155 | for (int i = 0; i < cnt; ++i) |
156 | recursiveReTranslate(item: item->child(index: i), class_name, idBased); |
157 | } |
158 | |
159 | template<typename T> |
160 | static void reTranslateWidgetItem(T *item, const QByteArray &class_name, bool idBased) |
161 | { |
162 | const QUiItemRolePair *irs = qUiItemRoles; |
163 | |
164 | for (unsigned j = 0; irs[j].shadowRole >= 0; j++) { |
165 | QVariant v = item->data(irs[j].shadowRole); |
166 | if (v.isValid()) { |
167 | QUiTranslatableStringValue tsv = qvariant_cast<QUiTranslatableStringValue>(v); |
168 | item->setData(irs[j].realRole, tsv.translate(className: class_name, idBased)); |
169 | } |
170 | } |
171 | } |
172 | |
173 | static void reTranslateTableItem(QTableWidgetItem *item, const QByteArray &class_name, bool idBased) |
174 | { |
175 | if (item) |
176 | reTranslateWidgetItem(item, class_name, idBased); |
177 | } |
178 | |
179 | #define RETRANSLATE_SUBWIDGET_PROP(mainWidget, setter, propName) \ |
180 | do { \ |
181 | QVariant v = mainWidget->widget(i)->property(propName); \ |
182 | if (v.isValid()) { \ |
183 | QUiTranslatableStringValue tsv = qvariant_cast<QUiTranslatableStringValue>(v); \ |
184 | mainWidget->setter(i, tsv.translate(m_className, m_idBased)); \ |
185 | } \ |
186 | } while (0) |
187 | |
188 | class TranslationWatcher: public QObject |
189 | { |
190 | Q_OBJECT |
191 | |
192 | public: |
193 | explicit TranslationWatcher(QObject *parent, const QByteArray &className, bool idBased): |
194 | QObject(parent), |
195 | m_className(className), |
196 | m_idBased(idBased) |
197 | { |
198 | } |
199 | |
200 | bool eventFilter(QObject *o, QEvent *event) override |
201 | { |
202 | if (event->type() == QEvent::LanguageChange) { |
203 | const auto &dynamicPropertyNames = o->dynamicPropertyNames(); |
204 | for (const QByteArray &prop : dynamicPropertyNames) { |
205 | if (prop.startsWith(PROP_GENERIC_PREFIX)) { |
206 | const QByteArray propName = prop.mid(index: sizeof(PROP_GENERIC_PREFIX) - 1); |
207 | const QUiTranslatableStringValue tsv = |
208 | qvariant_cast<QUiTranslatableStringValue>(v: o->property(name: prop)); |
209 | o->setProperty(name: propName, value: tsv.translate(className: m_className, idBased: m_idBased)); |
210 | } |
211 | } |
212 | if (0) { |
213 | #if QT_CONFIG(tabwidget) |
214 | } else if (QTabWidget *tabw = qobject_cast<QTabWidget*>(object: o)) { |
215 | const int cnt = tabw->count(); |
216 | for (int i = 0; i < cnt; ++i) { |
217 | RETRANSLATE_SUBWIDGET_PROP(tabw, setTabText, PROP_TABPAGETEXT); |
218 | #if QT_CONFIG(tooltip) |
219 | RETRANSLATE_SUBWIDGET_PROP(tabw, setTabToolTip, PROP_TABPAGETOOLTIP); |
220 | # endif |
221 | #if QT_CONFIG(whatsthis) |
222 | RETRANSLATE_SUBWIDGET_PROP(tabw, setTabWhatsThis, PROP_TABPAGEWHATSTHIS); |
223 | # endif |
224 | } |
225 | #endif |
226 | #if QT_CONFIG(listwidget) |
227 | } else if (QListWidget *listw = qobject_cast<QListWidget*>(object: o)) { |
228 | const int cnt = listw->count(); |
229 | for (int i = 0; i < cnt; ++i) |
230 | reTranslateWidgetItem(item: listw->item(row: i), class_name: m_className, idBased: m_idBased); |
231 | #endif |
232 | #if QT_CONFIG(treewidget) |
233 | } else if (QTreeWidget *treew = qobject_cast<QTreeWidget*>(object: o)) { |
234 | if (QTreeWidgetItem *item = treew->headerItem()) |
235 | recursiveReTranslate(item, class_name: m_className, idBased: m_idBased); |
236 | const int cnt = treew->topLevelItemCount(); |
237 | for (int i = 0; i < cnt; ++i) { |
238 | QTreeWidgetItem *item = treew->topLevelItem(index: i); |
239 | recursiveReTranslate(item, class_name: m_className, idBased: m_idBased); |
240 | } |
241 | #endif |
242 | #if QT_CONFIG(tablewidget) |
243 | } else if (QTableWidget *tablew = qobject_cast<QTableWidget*>(object: o)) { |
244 | const int row_cnt = tablew->rowCount(); |
245 | const int col_cnt = tablew->columnCount(); |
246 | for (int j = 0; j < col_cnt; ++j) |
247 | reTranslateTableItem(item: tablew->horizontalHeaderItem(column: j), class_name: m_className, idBased: m_idBased); |
248 | for (int i = 0; i < row_cnt; ++i) { |
249 | reTranslateTableItem(item: tablew->verticalHeaderItem(row: i), class_name: m_className, idBased: m_idBased); |
250 | for (int j = 0; j < col_cnt; ++j) |
251 | reTranslateTableItem(item: tablew->item(row: i, column: j), class_name: m_className, idBased: m_idBased); |
252 | } |
253 | #endif |
254 | #if QT_CONFIG(combobox) |
255 | } else if (QComboBox *combow = qobject_cast<QComboBox*>(object: o)) { |
256 | if (!qobject_cast<QFontComboBox*>(object: o)) { |
257 | const int cnt = combow->count(); |
258 | for (int i = 0; i < cnt; ++i) { |
259 | const QVariant v = combow->itemData(index: i, role: Qt::DisplayPropertyRole); |
260 | if (v.isValid()) { |
261 | QUiTranslatableStringValue tsv = qvariant_cast<QUiTranslatableStringValue>(v); |
262 | combow->setItemText(index: i, text: tsv.translate(className: m_className, idBased: m_idBased)); |
263 | } |
264 | } |
265 | } |
266 | #endif |
267 | #if QT_CONFIG(toolbox) |
268 | } else if (QToolBox *toolw = qobject_cast<QToolBox*>(object: o)) { |
269 | const int cnt = toolw->count(); |
270 | for (int i = 0; i < cnt; ++i) { |
271 | RETRANSLATE_SUBWIDGET_PROP(toolw, setItemText, PROP_TOOLITEMTEXT); |
272 | #if QT_CONFIG(tooltip) |
273 | RETRANSLATE_SUBWIDGET_PROP(toolw, setItemToolTip, PROP_TOOLITEMTOOLTIP); |
274 | # endif |
275 | } |
276 | #endif |
277 | } |
278 | } |
279 | return false; |
280 | } |
281 | |
282 | private: |
283 | QByteArray m_className; |
284 | bool m_idBased; |
285 | }; |
286 | |
287 | class FormBuilderPrivate: public QFormBuilder |
288 | { |
289 | friend class QT_PREPEND_NAMESPACE(QUiLoader); |
290 | friend class QT_PREPEND_NAMESPACE(QUiLoaderPrivate); |
291 | using ParentClass = QFormBuilder; |
292 | |
293 | public: |
294 | QUiLoader *loader = nullptr; |
295 | |
296 | bool dynamicTr = false; |
297 | bool trEnabled = true; |
298 | |
299 | FormBuilderPrivate() = default; |
300 | |
301 | QWidget *defaultCreateWidget(const QString &className, QWidget *parent, const QString &name) |
302 | { |
303 | return ParentClass::createWidget(widgetName: className, parentWidget: parent, name); |
304 | } |
305 | |
306 | QLayout *defaultCreateLayout(const QString &className, QObject *parent, const QString &name) |
307 | { |
308 | return ParentClass::createLayout(layoutName: className, parent, name); |
309 | } |
310 | |
311 | QAction *defaultCreateAction(QObject *parent, const QString &name) |
312 | { |
313 | return ParentClass::createAction(parent, name); |
314 | } |
315 | |
316 | QActionGroup *defaultCreateActionGroup(QObject *parent, const QString &name) |
317 | { |
318 | return ParentClass::createActionGroup(parent, name); |
319 | } |
320 | |
321 | QWidget *createWidget(const QString &className, QWidget *parent, const QString &name) override |
322 | { |
323 | if (QWidget *widget = loader->createWidget(className, parent, name)) { |
324 | widget->setObjectName(name); |
325 | return widget; |
326 | } |
327 | |
328 | return nullptr; |
329 | } |
330 | |
331 | QLayout *createLayout(const QString &className, QObject *parent, const QString &name) override |
332 | { |
333 | if (QLayout *layout = loader->createLayout(className, parent, name)) { |
334 | layout->setObjectName(name); |
335 | return layout; |
336 | } |
337 | |
338 | return nullptr; |
339 | } |
340 | |
341 | QActionGroup *createActionGroup(QObject *parent, const QString &name) override |
342 | { |
343 | if (QActionGroup *actionGroup = loader->createActionGroup(parent, name)) { |
344 | actionGroup->setObjectName(name); |
345 | return actionGroup; |
346 | } |
347 | |
348 | return nullptr; |
349 | } |
350 | |
351 | QAction *createAction(QObject *parent, const QString &name) override |
352 | { |
353 | if (QAction *action = loader->createAction(parent, name)) { |
354 | action->setObjectName(name); |
355 | return action; |
356 | } |
357 | |
358 | return nullptr; |
359 | } |
360 | |
361 | void applyProperties(QObject *o, const QList<DomProperty*> &properties) override; |
362 | QWidget *create(DomUI *ui, QWidget *parentWidget) override; |
363 | QWidget *create(DomWidget *ui_widget, QWidget *parentWidget) override; |
364 | bool addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; |
365 | |
366 | private: |
367 | QByteArray m_class; |
368 | TranslationWatcher *m_trwatch = nullptr; |
369 | bool m_idBased = false; |
370 | }; |
371 | |
372 | static QString convertTranslatable(const DomProperty *p, const QByteArray &className, |
373 | bool idBased, QUiTranslatableStringValue *strVal) |
374 | { |
375 | if (p->kind() != DomProperty::String) |
376 | return QString(); |
377 | const DomString *dom_str = p->elementString(); |
378 | if (!dom_str) |
379 | return QString(); |
380 | if (dom_str->hasAttributeNotr()) { |
381 | const QString notr = dom_str->attributeNotr(); |
382 | if (notr == QStringLiteral("yes" ) || notr == QStringLiteral("true" )) |
383 | return QString(); |
384 | } |
385 | strVal->setValue(dom_str->text().toUtf8()); |
386 | strVal->setQualifier(idBased ? dom_str->attributeId().toUtf8() : dom_str->attributeComment().toUtf8()); |
387 | if (strVal->value().isEmpty() && strVal->qualifier().isEmpty()) |
388 | return QString(); |
389 | return strVal->translate(className, idBased); |
390 | } |
391 | |
392 | void FormBuilderPrivate::applyProperties(QObject *o, const QList<DomProperty*> &properties) |
393 | { |
394 | QFormBuilder::applyProperties(o, properties); |
395 | |
396 | if (!m_trwatch) |
397 | m_trwatch = new TranslationWatcher(o, m_class, m_idBased); |
398 | |
399 | if (properties.isEmpty()) |
400 | return; |
401 | |
402 | // Unlike string item roles, string properties are not loaded via the textBuilder |
403 | // (as they are "shadowed" by the property sheets in designer). So do the initial |
404 | // translation here. |
405 | bool anyTrs = false; |
406 | for (const DomProperty *p : properties) { |
407 | QUiTranslatableStringValue strVal; |
408 | const QString text = convertTranslatable(p, className: m_class, idBased: m_idBased, strVal: &strVal); |
409 | if (text.isEmpty()) |
410 | continue; |
411 | const QByteArray name = p->attributeName().toUtf8(); |
412 | if (dynamicTr) { |
413 | const QByteArray dynname = QByteArray(PROP_GENERIC_PREFIX + name); |
414 | o->setProperty(name: dynname, value: QVariant::fromValue(value: strVal)); |
415 | anyTrs = trEnabled; |
416 | } |
417 | if (p->elementString()->text() != text) |
418 | o->setProperty(name, value: text); |
419 | } |
420 | if (anyTrs) |
421 | o->installEventFilter(filterObj: m_trwatch); |
422 | } |
423 | |
424 | QWidget *FormBuilderPrivate::create(DomUI *ui, QWidget *parentWidget) |
425 | { |
426 | m_class = ui->elementClass().toUtf8(); |
427 | m_trwatch = nullptr; |
428 | m_idBased = ui->attributeIdbasedtr(); |
429 | setTextBuilder(new TranslatingTextBuilder(m_idBased, trEnabled, m_class)); |
430 | return QFormBuilder::create(ui, parentWidget); |
431 | } |
432 | |
433 | QWidget *FormBuilderPrivate::create(DomWidget *ui_widget, QWidget *parentWidget) |
434 | { |
435 | QWidget *w = QFormBuilder::create(ui_widget, parentWidget); |
436 | if (w == nullptr) |
437 | return nullptr; |
438 | |
439 | if (0) { |
440 | #if QT_CONFIG(tabwidget) |
441 | } else if (qobject_cast<QTabWidget*>(object: w)) { |
442 | #endif |
443 | #if QT_CONFIG(listwidget) |
444 | } else if (qobject_cast<QListWidget*>(object: w)) { |
445 | #endif |
446 | #if QT_CONFIG(treewidget) |
447 | } else if (qobject_cast<QTreeWidget*>(object: w)) { |
448 | #endif |
449 | #if QT_CONFIG(tablewidget) |
450 | } else if (qobject_cast<QTableWidget*>(object: w)) { |
451 | #endif |
452 | #if QT_CONFIG(combobox) |
453 | } else if (qobject_cast<QComboBox*>(object: w)) { |
454 | if (qobject_cast<QFontComboBox*>(object: w)) |
455 | return w; |
456 | #endif |
457 | #if QT_CONFIG(toolbox) |
458 | } else if (qobject_cast<QToolBox*>(object: w)) { |
459 | #endif |
460 | } else { |
461 | return w; |
462 | } |
463 | if (dynamicTr && trEnabled) |
464 | w->installEventFilter(filterObj: m_trwatch); |
465 | return w; |
466 | } |
467 | |
468 | #define TRANSLATE_SUBWIDGET_PROP(mainWidget, attribute, setter, propName) \ |
469 | do { \ |
470 | if (const DomProperty *p##attribute = attributes.value(strings.attribute)) { \ |
471 | QUiTranslatableStringValue strVal; \ |
472 | const QString text = convertTranslatable(p##attribute, m_class, m_idBased, &strVal); \ |
473 | if (!text.isEmpty()) { \ |
474 | if (dynamicTr) \ |
475 | mainWidget->widget(i)->setProperty(propName, QVariant::fromValue(strVal)); \ |
476 | mainWidget->setter(i, text); \ |
477 | } \ |
478 | } \ |
479 | } while (0) |
480 | |
481 | bool FormBuilderPrivate::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) |
482 | { |
483 | if (parentWidget == nullptr) |
484 | return true; |
485 | |
486 | if (!ParentClass::addItem(ui_widget, widget, parentWidget)) |
487 | return false; |
488 | |
489 | // Check special cases. First: Custom container |
490 | const QString className = QLatin1String(parentWidget->metaObject()->className()); |
491 | if (!d->customWidgetAddPageMethod(className).isEmpty()) |
492 | return true; |
493 | |
494 | const QFormBuilderStrings &strings = QFormBuilderStrings::instance(); |
495 | |
496 | if (0) { |
497 | #if QT_CONFIG(tabwidget) |
498 | } else if (QTabWidget *tabWidget = qobject_cast<QTabWidget*>(object: parentWidget)) { |
499 | const DomPropertyHash attributes = propertyMap(properties: ui_widget->elementAttribute()); |
500 | const int i = tabWidget->count() - 1; |
501 | TRANSLATE_SUBWIDGET_PROP(tabWidget, titleAttribute, setTabText, PROP_TABPAGETEXT); |
502 | #if QT_CONFIG(tooltip) |
503 | TRANSLATE_SUBWIDGET_PROP(tabWidget, toolTipAttribute, setTabToolTip, PROP_TABPAGETOOLTIP); |
504 | # endif |
505 | #if QT_CONFIG(whatsthis) |
506 | TRANSLATE_SUBWIDGET_PROP(tabWidget, whatsThisAttribute, setTabWhatsThis, PROP_TABPAGEWHATSTHIS); |
507 | # endif |
508 | #endif |
509 | #if QT_CONFIG(toolbox) |
510 | } else if (QToolBox *toolBox = qobject_cast<QToolBox*>(object: parentWidget)) { |
511 | const DomPropertyHash attributes = propertyMap(properties: ui_widget->elementAttribute()); |
512 | const int i = toolBox->count() - 1; |
513 | TRANSLATE_SUBWIDGET_PROP(toolBox, labelAttribute, setItemText, PROP_TOOLITEMTEXT); |
514 | #if QT_CONFIG(tooltip) |
515 | TRANSLATE_SUBWIDGET_PROP(toolBox, toolTipAttribute, setItemToolTip, PROP_TOOLITEMTOOLTIP); |
516 | # endif |
517 | #endif |
518 | } |
519 | |
520 | return true; |
521 | } |
522 | |
523 | #ifdef QFORMINTERNAL_NAMESPACE |
524 | } |
525 | #endif |
526 | |
527 | class QUiLoaderPrivate |
528 | { |
529 | public: |
530 | #ifdef QFORMINTERNAL_NAMESPACE |
531 | QFormInternal::FormBuilderPrivate builder; |
532 | #else |
533 | FormBuilderPrivate builder; |
534 | #endif |
535 | |
536 | void setupWidgetMap() const; |
537 | }; |
538 | |
539 | void QUiLoaderPrivate::setupWidgetMap() const |
540 | { |
541 | if (!g_widgets()->isEmpty()) |
542 | return; |
543 | |
544 | #define DECLARE_WIDGET(a, b) g_widgets()->insert(QLatin1String(#a), true); |
545 | #define DECLARE_LAYOUT(a, b) |
546 | |
547 | #include "widgets.table" |
548 | |
549 | #undef DECLARE_WIDGET |
550 | #undef DECLARE_WIDGET_1 |
551 | #undef DECLARE_LAYOUT |
552 | } |
553 | |
554 | /*! |
555 | \class QUiLoader |
556 | \inmodule QtUiTools |
557 | |
558 | \brief The QUiLoader class enables standalone applications to |
559 | dynamically create user interfaces at run-time using the |
560 | information stored in UI files or specified in plugin paths. |
561 | |
562 | In addition, you can customize or create your own user interface by |
563 | deriving your own loader class. |
564 | |
565 | If you have a custom component or an application that embeds \QD, you can |
566 | also use the QFormBuilder class provided by the QtDesigner module to create |
567 | user interfaces from UI files. |
568 | |
569 | The QUiLoader class provides a collection of functions allowing you to |
570 | create widgets based on the information stored in UI files (created |
571 | with \QD) or available in the specified plugin paths. The specified plugin |
572 | paths can be retrieved using the pluginPaths() function. Similarly, the |
573 | contents of a UI file can be retrieved using the load() function. For |
574 | example: |
575 | |
576 | \snippet quiloader/mywidget.cpp 0 |
577 | |
578 | \if !defined(qtforpython) |
579 | By including the user interface in the form's resources (\c myform.qrc), we |
580 | ensure that it will be present at run-time: |
581 | |
582 | \quotefile quiloader/mywidget.qrc |
583 | \endif |
584 | |
585 | The availableWidgets() function returns a QStringList with the class names |
586 | of the widgets available in the specified plugin paths. To create these |
587 | widgets, simply use the createWidget() function. For example: |
588 | |
589 | \snippet quiloader/main.cpp 0 |
590 | |
591 | To make a custom widget available to the loader, you can use the |
592 | addPluginPath() function; to remove all available widgets, you can call |
593 | the clearPluginPaths() function. |
594 | |
595 | The createAction(), createActionGroup(), createLayout(), and createWidget() |
596 | functions are used internally by the QUiLoader class whenever it has to |
597 | create an action, action group, layout, or widget respectively. For that |
598 | reason, you can subclass the QUiLoader class and reimplement these |
599 | functions to intervene the process of constructing a user interface. For |
600 | example, you might want to have a list of the actions created when loading |
601 | a form or creating a custom widget. |
602 | |
603 | For a complete example using the QUiLoader class, see the |
604 | \l{Calculator Builder}. |
605 | |
606 | \sa {Qt UI Tools}, QFormBuilder |
607 | */ |
608 | |
609 | /*! |
610 | Creates a form loader with the given \a parent. |
611 | */ |
612 | QUiLoader::QUiLoader(QObject *parent) |
613 | : QObject(parent), d_ptr(new QUiLoaderPrivate) |
614 | { |
615 | Q_D(QUiLoader); |
616 | |
617 | #ifndef QT_NO_DATASTREAM |
618 | static int metaTypeId = 0; |
619 | if (!metaTypeId) { |
620 | metaTypeId = qRegisterMetaType<QUiTranslatableStringValue>(typeName: "QUiTranslatableStringValue" ); |
621 | } |
622 | #endif // QT_NO_DATASTREAM |
623 | d->builder.loader = this; |
624 | |
625 | #if QT_CONFIG(library) |
626 | QStringList paths; |
627 | const QStringList &libraryPaths = QApplication::libraryPaths(); |
628 | for (const QString &path : libraryPaths) { |
629 | QString libPath = path; |
630 | libPath += QDir::separator(); |
631 | libPath += QStringLiteral("designer" ); |
632 | paths.append(t: libPath); |
633 | } |
634 | |
635 | d->builder.setPluginPath(paths); |
636 | #endif // QT_CONFIG(library) |
637 | } |
638 | |
639 | /*! |
640 | Destroys the loader. |
641 | */ |
642 | QUiLoader::~QUiLoader() = default; |
643 | |
644 | /*! |
645 | Loads a form from the given \a device and creates a new widget with the |
646 | given \a parentWidget to hold its contents. |
647 | |
648 | \sa createWidget(), errorString() |
649 | */ |
650 | QWidget *QUiLoader::load(QIODevice *device, QWidget *parentWidget) |
651 | { |
652 | Q_D(QUiLoader); |
653 | // QXmlStreamReader will report errors on open failure. |
654 | if (!device->isOpen()) |
655 | device->open(mode: QIODevice::ReadOnly|QIODevice::Text); |
656 | return d->builder.load(dev: device, parentWidget); |
657 | } |
658 | |
659 | /*! |
660 | Returns a list naming the paths in which the loader will search when |
661 | locating custom widget plugins. |
662 | |
663 | \sa addPluginPath(), clearPluginPaths() |
664 | */ |
665 | QStringList QUiLoader::pluginPaths() const |
666 | { |
667 | Q_D(const QUiLoader); |
668 | return d->builder.pluginPaths(); |
669 | } |
670 | |
671 | /*! |
672 | Clears the list of paths in which the loader will search when locating |
673 | plugins. |
674 | |
675 | \sa addPluginPath(), pluginPaths() |
676 | */ |
677 | void QUiLoader::clearPluginPaths() |
678 | { |
679 | Q_D(QUiLoader); |
680 | d->builder.clearPluginPaths(); |
681 | } |
682 | |
683 | /*! |
684 | Adds the given \a path to the list of paths in which the loader will search |
685 | when locating plugins. |
686 | |
687 | \sa pluginPaths(), clearPluginPaths() |
688 | */ |
689 | void QUiLoader::addPluginPath(const QString &path) |
690 | { |
691 | Q_D(QUiLoader); |
692 | d->builder.addPluginPath(pluginPath: path); |
693 | } |
694 | |
695 | /*! |
696 | Creates a new widget with the given \a parent and \a name using the class |
697 | specified by \a className. You can use this function to create any of the |
698 | widgets returned by the availableWidgets() function. |
699 | |
700 | The function is also used internally by the QUiLoader class whenever it |
701 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
702 | function to intervene process of constructing a user interface or widget. |
703 | However, in your implementation, ensure that you call QUiLoader's version |
704 | first. |
705 | |
706 | \sa availableWidgets(), load() |
707 | */ |
708 | QWidget *QUiLoader::createWidget(const QString &className, QWidget *parent, const QString &name) |
709 | { |
710 | Q_D(QUiLoader); |
711 | return d->builder.defaultCreateWidget(className, parent, name); |
712 | } |
713 | |
714 | /*! |
715 | Creates a new layout with the given \a parent and \a name using the class |
716 | specified by \a className. |
717 | |
718 | The function is also used internally by the QUiLoader class whenever it |
719 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
720 | function to intervene process of constructing a user interface or widget. |
721 | However, in your implementation, ensure that you call QUiLoader's version |
722 | first. |
723 | |
724 | \sa createWidget(), load() |
725 | */ |
726 | QLayout *QUiLoader::createLayout(const QString &className, QObject *parent, const QString &name) |
727 | { |
728 | Q_D(QUiLoader); |
729 | return d->builder.defaultCreateLayout(className, parent, name); |
730 | } |
731 | |
732 | /*! |
733 | Creates a new action group with the given \a parent and \a name. |
734 | |
735 | The function is also used internally by the QUiLoader class whenever it |
736 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
737 | function to intervene process of constructing a user interface or widget. |
738 | However, in your implementation, ensure that you call QUiLoader's version |
739 | first. |
740 | |
741 | \sa createAction(), createWidget(), load() |
742 | */ |
743 | QActionGroup *QUiLoader::createActionGroup(QObject *parent, const QString &name) |
744 | { |
745 | Q_D(QUiLoader); |
746 | return d->builder.defaultCreateActionGroup(parent, name); |
747 | } |
748 | |
749 | /*! |
750 | Creates a new action with the given \a parent and \a name. |
751 | |
752 | The function is also used internally by the QUiLoader class whenever it |
753 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
754 | function to intervene process of constructing a user interface or widget. |
755 | However, in your implementation, ensure that you call QUiLoader's version |
756 | first. |
757 | |
758 | \sa createActionGroup(), createWidget(), load() |
759 | */ |
760 | QAction *QUiLoader::createAction(QObject *parent, const QString &name) |
761 | { |
762 | Q_D(QUiLoader); |
763 | return d->builder.defaultCreateAction(parent, name); |
764 | } |
765 | |
766 | /*! |
767 | Returns a list naming all available widgets that can be built using the |
768 | createWidget() function, i.e all the widgets specified within the given |
769 | plugin paths. |
770 | |
771 | \sa pluginPaths(), createWidget() |
772 | |
773 | */ |
774 | QStringList QUiLoader::availableWidgets() const |
775 | { |
776 | Q_D(const QUiLoader); |
777 | |
778 | d->setupWidgetMap(); |
779 | widget_map available = *g_widgets(); |
780 | |
781 | const auto &customWidgets = d->builder.customWidgets(); |
782 | for (QDesignerCustomWidgetInterface *plugin : customWidgets) |
783 | available.insert(key: plugin->name(), value: true); |
784 | |
785 | return available.keys(); |
786 | } |
787 | |
788 | |
789 | /*! |
790 | \since 4.5 |
791 | Returns a list naming all available layouts that can be built using the |
792 | createLayout() function |
793 | |
794 | \sa createLayout() |
795 | */ |
796 | |
797 | QStringList QUiLoader::availableLayouts() const |
798 | { |
799 | QStringList rc; |
800 | #define DECLARE_WIDGET(a, b) |
801 | #define DECLARE_LAYOUT(a, b) rc.push_back(QLatin1String(#a)); |
802 | |
803 | #include "widgets.table" |
804 | |
805 | #undef DECLARE_WIDGET |
806 | #undef DECLARE_LAYOUT |
807 | return rc; |
808 | } |
809 | |
810 | /*! |
811 | Sets the working directory of the loader to \a dir. The loader will look |
812 | for other resources, such as icons and resource files, in paths relative to |
813 | this directory. |
814 | |
815 | \sa workingDirectory() |
816 | */ |
817 | |
818 | void QUiLoader::setWorkingDirectory(const QDir &dir) |
819 | { |
820 | Q_D(QUiLoader); |
821 | d->builder.setWorkingDirectory(dir); |
822 | } |
823 | |
824 | /*! |
825 | Returns the working directory of the loader. |
826 | |
827 | \sa setWorkingDirectory() |
828 | */ |
829 | |
830 | QDir QUiLoader::workingDirectory() const |
831 | { |
832 | Q_D(const QUiLoader); |
833 | return d->builder.workingDirectory(); |
834 | } |
835 | /*! |
836 | \since 4.5 |
837 | |
838 | If \a enabled is true, user interfaces loaded by this loader will |
839 | automatically retranslate themselves upon receiving a language change |
840 | event. Otherwise, the user interfaces will not be retranslated. |
841 | |
842 | \sa isLanguageChangeEnabled() |
843 | */ |
844 | |
845 | void QUiLoader::setLanguageChangeEnabled(bool enabled) |
846 | { |
847 | Q_D(QUiLoader); |
848 | d->builder.dynamicTr = enabled; |
849 | } |
850 | |
851 | /*! |
852 | \since 4.5 |
853 | |
854 | Returns true if dynamic retranslation on language change is enabled; |
855 | returns false otherwise. |
856 | |
857 | \sa setLanguageChangeEnabled() |
858 | */ |
859 | |
860 | bool QUiLoader::isLanguageChangeEnabled() const |
861 | { |
862 | Q_D(const QUiLoader); |
863 | return d->builder.dynamicTr; |
864 | } |
865 | |
866 | /*! |
867 | \internal |
868 | \since 4.5 |
869 | |
870 | If \a enabled is true, user interfaces loaded by this loader will be |
871 | translated. Otherwise, the user interfaces will not be translated. |
872 | |
873 | \note This is orthogonal to languageChangeEnabled. |
874 | |
875 | \sa isLanguageChangeEnabled(), setLanguageChangeEnabled() |
876 | */ |
877 | |
878 | void QUiLoader::setTranslationEnabled(bool enabled) |
879 | { |
880 | Q_D(QUiLoader); |
881 | d->builder.trEnabled = enabled; |
882 | } |
883 | |
884 | /*! |
885 | \internal |
886 | \since 4.5 |
887 | |
888 | Returns true if translation is enabled; returns false otherwise. |
889 | |
890 | \sa setTranslationEnabled() |
891 | */ |
892 | |
893 | bool QUiLoader::isTranslationEnabled() const |
894 | { |
895 | Q_D(const QUiLoader); |
896 | return d->builder.trEnabled; |
897 | } |
898 | |
899 | /*! |
900 | Returns a human-readable description of the last error occurred in load(). |
901 | |
902 | \since 5.0 |
903 | \sa load() |
904 | */ |
905 | |
906 | QString QUiLoader::errorString() const |
907 | { |
908 | Q_D(const QUiLoader); |
909 | return d->builder.errorString(); |
910 | } |
911 | |
912 | QT_END_NAMESPACE |
913 | |
914 | #include "quiloader.moc" |
915 | |