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 auto *p = attributes.value(attribute)) { \ |
471 | QUiTranslatableStringValue strVal; \ |
472 | const QString text = convertTranslatable(p, 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 | if (0) { |
495 | #if QT_CONFIG(tabwidget) |
496 | } else if (QTabWidget *tabWidget = qobject_cast<QTabWidget*>(object: parentWidget)) { |
497 | const DomPropertyHash attributes = propertyMap(properties: ui_widget->elementAttribute()); |
498 | const int i = tabWidget->count() - 1; |
499 | TRANSLATE_SUBWIDGET_PROP(tabWidget, QFormBuilderStrings::titleAttribute, |
500 | setTabText, PROP_TABPAGETEXT); |
501 | #if QT_CONFIG(tooltip) |
502 | TRANSLATE_SUBWIDGET_PROP(tabWidget, QFormBuilderStrings::toolTipAttribute, |
503 | setTabToolTip, PROP_TABPAGETOOLTIP); |
504 | # endif |
505 | #if QT_CONFIG(whatsthis) |
506 | TRANSLATE_SUBWIDGET_PROP(tabWidget, QFormBuilderStrings::whatsThisAttribute, |
507 | setTabWhatsThis, PROP_TABPAGEWHATSTHIS); |
508 | # endif |
509 | #endif |
510 | #if QT_CONFIG(toolbox) |
511 | } else if (QToolBox *toolBox = qobject_cast<QToolBox*>(object: parentWidget)) { |
512 | const DomPropertyHash attributes = propertyMap(properties: ui_widget->elementAttribute()); |
513 | const int i = toolBox->count() - 1; |
514 | TRANSLATE_SUBWIDGET_PROP(toolBox, QFormBuilderStrings::labelAttribute, |
515 | setItemText, PROP_TOOLITEMTEXT); |
516 | #if QT_CONFIG(tooltip) |
517 | TRANSLATE_SUBWIDGET_PROP(toolBox, QFormBuilderStrings::toolTipAttribute, |
518 | setItemToolTip, PROP_TOOLITEMTOOLTIP); |
519 | # endif |
520 | #endif |
521 | } |
522 | |
523 | return true; |
524 | } |
525 | |
526 | #ifdef QFORMINTERNAL_NAMESPACE |
527 | } |
528 | #endif |
529 | |
530 | class QUiLoaderPrivate |
531 | { |
532 | public: |
533 | #ifdef QFORMINTERNAL_NAMESPACE |
534 | QFormInternal::FormBuilderPrivate builder; |
535 | #else |
536 | FormBuilderPrivate builder; |
537 | #endif |
538 | |
539 | void setupWidgetMap() const; |
540 | }; |
541 | |
542 | void QUiLoaderPrivate::setupWidgetMap() const |
543 | { |
544 | if (!g_widgets()->isEmpty()) |
545 | return; |
546 | |
547 | #define DECLARE_WIDGET(a, b) g_widgets()->insert(QLatin1String(#a), true); |
548 | #define DECLARE_LAYOUT(a, b) |
549 | |
550 | #include "widgets.table" |
551 | |
552 | #undef DECLARE_WIDGET |
553 | #undef DECLARE_WIDGET_1 |
554 | #undef DECLARE_LAYOUT |
555 | } |
556 | |
557 | /*! |
558 | \class QUiLoader |
559 | \inmodule QtUiTools |
560 | |
561 | \brief The QUiLoader class enables standalone applications to |
562 | dynamically create user interfaces at run-time using the |
563 | information stored in UI files or specified in plugin paths. |
564 | |
565 | In addition, you can customize or create your own user interface by |
566 | deriving your own loader class. |
567 | |
568 | If you have a custom component or an application that embeds \QD, you can |
569 | also use the QFormBuilder class provided by the QtDesigner module to create |
570 | user interfaces from UI files. |
571 | |
572 | The QUiLoader class provides a collection of functions allowing you to |
573 | create widgets based on the information stored in UI files (created |
574 | with \QD) or available in the specified plugin paths. The specified plugin |
575 | paths can be retrieved using the pluginPaths() function. Similarly, the |
576 | contents of a UI file can be retrieved using the load() function. For |
577 | example: |
578 | |
579 | \snippet quiloader/mywidget.cpp 0 |
580 | |
581 | \if !defined(qtforpython) |
582 | By including the user interface in the form's resources (\c myform.qrc), we |
583 | ensure that it will be present at run-time: |
584 | |
585 | \quotefile quiloader/mywidget.qrc |
586 | \endif |
587 | |
588 | The availableWidgets() function returns a QStringList with the class names |
589 | of the widgets available in the specified plugin paths. To create these |
590 | widgets, simply use the createWidget() function. For example: |
591 | |
592 | \snippet quiloader/main.cpp 0 |
593 | |
594 | To make a custom widget available to the loader, you can use the |
595 | addPluginPath() function; to remove all available widgets, you can call |
596 | the clearPluginPaths() function. |
597 | |
598 | The createAction(), createActionGroup(), createLayout(), and createWidget() |
599 | functions are used internally by the QUiLoader class whenever it has to |
600 | create an action, action group, layout, or widget respectively. For that |
601 | reason, you can subclass the QUiLoader class and reimplement these |
602 | functions to intervene the process of constructing a user interface. For |
603 | example, you might want to have a list of the actions created when loading |
604 | a form or creating a custom widget. |
605 | |
606 | For a complete example using the QUiLoader class, see the |
607 | \l{Calculator Builder}. |
608 | |
609 | \sa {Qt UI Tools}, QFormBuilder |
610 | */ |
611 | |
612 | /*! |
613 | Creates a form loader with the given \a parent. |
614 | */ |
615 | QUiLoader::QUiLoader(QObject *parent) |
616 | : QObject(parent), d_ptr(new QUiLoaderPrivate) |
617 | { |
618 | Q_D(QUiLoader); |
619 | |
620 | #ifndef QT_NO_DATASTREAM |
621 | static int metaTypeId = 0; |
622 | if (!metaTypeId) { |
623 | metaTypeId = qRegisterMetaType<QUiTranslatableStringValue>(typeName: "QUiTranslatableStringValue"); |
624 | } |
625 | #endif // QT_NO_DATASTREAM |
626 | d->builder.loader = this; |
627 | |
628 | #if QT_CONFIG(library) |
629 | QStringList paths; |
630 | const QStringList &libraryPaths = QApplication::libraryPaths(); |
631 | for (const QString &path : libraryPaths) { |
632 | QString libPath = path; |
633 | libPath += QDir::separator(); |
634 | libPath += QStringLiteral("designer"); |
635 | paths.append(t: libPath); |
636 | } |
637 | |
638 | d->builder.setPluginPath(paths); |
639 | #endif // QT_CONFIG(library) |
640 | } |
641 | |
642 | /*! |
643 | Destroys the loader. |
644 | */ |
645 | QUiLoader::~QUiLoader() = default; |
646 | |
647 | /*! |
648 | Loads a form from the given \a device and creates a new widget with the |
649 | given \a parentWidget to hold its contents. |
650 | |
651 | \sa createWidget(), errorString() |
652 | */ |
653 | QWidget *QUiLoader::load(QIODevice *device, QWidget *parentWidget) |
654 | { |
655 | Q_D(QUiLoader); |
656 | // QXmlStreamReader will report errors on open failure. |
657 | if (!device->isOpen()) |
658 | device->open(mode: QIODevice::ReadOnly|QIODevice::Text); |
659 | return d->builder.load(dev: device, parentWidget); |
660 | } |
661 | |
662 | /*! |
663 | Returns a list naming the paths in which the loader will search when |
664 | locating custom widget plugins. |
665 | |
666 | \sa addPluginPath(), clearPluginPaths() |
667 | */ |
668 | QStringList QUiLoader::pluginPaths() const |
669 | { |
670 | Q_D(const QUiLoader); |
671 | return d->builder.pluginPaths(); |
672 | } |
673 | |
674 | /*! |
675 | Clears the list of paths in which the loader will search when locating |
676 | plugins. |
677 | |
678 | \sa addPluginPath(), pluginPaths() |
679 | */ |
680 | void QUiLoader::clearPluginPaths() |
681 | { |
682 | Q_D(QUiLoader); |
683 | d->builder.clearPluginPaths(); |
684 | } |
685 | |
686 | /*! |
687 | Adds the given \a path to the list of paths in which the loader will search |
688 | when locating plugins. |
689 | |
690 | \sa pluginPaths(), clearPluginPaths() |
691 | */ |
692 | void QUiLoader::addPluginPath(const QString &path) |
693 | { |
694 | Q_D(QUiLoader); |
695 | d->builder.addPluginPath(pluginPath: path); |
696 | } |
697 | |
698 | /*! |
699 | Creates a new widget with the given \a parent and \a name using the class |
700 | specified by \a className. You can use this function to create any of the |
701 | widgets returned by the availableWidgets() function. |
702 | |
703 | The function is also used internally by the QUiLoader class whenever it |
704 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
705 | function to intervene process of constructing a user interface or widget. |
706 | However, in your implementation, ensure that you call QUiLoader's version |
707 | first. |
708 | |
709 | \sa availableWidgets(), load() |
710 | */ |
711 | QWidget *QUiLoader::createWidget(const QString &className, QWidget *parent, const QString &name) |
712 | { |
713 | Q_D(QUiLoader); |
714 | return d->builder.defaultCreateWidget(className, parent, name); |
715 | } |
716 | |
717 | /*! |
718 | Creates a new layout with the given \a parent and \a name using the class |
719 | specified by \a className. |
720 | |
721 | The function is also used internally by the QUiLoader class whenever it |
722 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
723 | function to intervene process of constructing a user interface or widget. |
724 | However, in your implementation, ensure that you call QUiLoader's version |
725 | first. |
726 | |
727 | \sa createWidget(), load() |
728 | */ |
729 | QLayout *QUiLoader::createLayout(const QString &className, QObject *parent, const QString &name) |
730 | { |
731 | Q_D(QUiLoader); |
732 | return d->builder.defaultCreateLayout(className, parent, name); |
733 | } |
734 | |
735 | /*! |
736 | Creates a new action group with the given \a parent and \a name. |
737 | |
738 | The function is also used internally by the QUiLoader class whenever it |
739 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
740 | function to intervene process of constructing a user interface or widget. |
741 | However, in your implementation, ensure that you call QUiLoader's version |
742 | first. |
743 | |
744 | \sa createAction(), createWidget(), load() |
745 | */ |
746 | QActionGroup *QUiLoader::createActionGroup(QObject *parent, const QString &name) |
747 | { |
748 | Q_D(QUiLoader); |
749 | return d->builder.defaultCreateActionGroup(parent, name); |
750 | } |
751 | |
752 | /*! |
753 | Creates a new action with the given \a parent and \a name. |
754 | |
755 | The function is also used internally by the QUiLoader class whenever it |
756 | creates a widget. Hence, you can subclass QUiLoader and reimplement this |
757 | function to intervene process of constructing a user interface or widget. |
758 | However, in your implementation, ensure that you call QUiLoader's version |
759 | first. |
760 | |
761 | \sa createActionGroup(), createWidget(), load() |
762 | */ |
763 | QAction *QUiLoader::createAction(QObject *parent, const QString &name) |
764 | { |
765 | Q_D(QUiLoader); |
766 | return d->builder.defaultCreateAction(parent, name); |
767 | } |
768 | |
769 | /*! |
770 | Returns a list naming all available widgets that can be built using the |
771 | createWidget() function, i.e all the widgets specified within the given |
772 | plugin paths. |
773 | |
774 | \sa pluginPaths(), createWidget() |
775 | |
776 | */ |
777 | QStringList QUiLoader::availableWidgets() const |
778 | { |
779 | Q_D(const QUiLoader); |
780 | |
781 | d->setupWidgetMap(); |
782 | widget_map available = *g_widgets(); |
783 | |
784 | const auto &customWidgets = d->builder.customWidgets(); |
785 | for (QDesignerCustomWidgetInterface *plugin : customWidgets) |
786 | available.insert(key: plugin->name(), value: true); |
787 | |
788 | return available.keys(); |
789 | } |
790 | |
791 | |
792 | /*! |
793 | \since 4.5 |
794 | Returns a list naming all available layouts that can be built using the |
795 | createLayout() function |
796 | |
797 | \sa createLayout() |
798 | */ |
799 | |
800 | QStringList QUiLoader::availableLayouts() const |
801 | { |
802 | QStringList rc; |
803 | #define DECLARE_WIDGET(a, b) |
804 | #define DECLARE_LAYOUT(a, b) rc.push_back(QLatin1String(#a)); |
805 | |
806 | #include "widgets.table" |
807 | |
808 | #undef DECLARE_WIDGET |
809 | #undef DECLARE_LAYOUT |
810 | return rc; |
811 | } |
812 | |
813 | /*! |
814 | Sets the working directory of the loader to \a dir. The loader will look |
815 | for other resources, such as icons and resource files, in paths relative to |
816 | this directory. |
817 | |
818 | \sa workingDirectory() |
819 | */ |
820 | |
821 | void QUiLoader::setWorkingDirectory(const QDir &dir) |
822 | { |
823 | Q_D(QUiLoader); |
824 | d->builder.setWorkingDirectory(dir); |
825 | } |
826 | |
827 | /*! |
828 | Returns the working directory of the loader. |
829 | |
830 | \sa setWorkingDirectory() |
831 | */ |
832 | |
833 | QDir QUiLoader::workingDirectory() const |
834 | { |
835 | Q_D(const QUiLoader); |
836 | return d->builder.workingDirectory(); |
837 | } |
838 | /*! |
839 | \since 4.5 |
840 | |
841 | If \a enabled is true, user interfaces loaded by this loader will |
842 | automatically retranslate themselves upon receiving a language change |
843 | event. Otherwise, the user interfaces will not be retranslated. |
844 | |
845 | \sa isLanguageChangeEnabled() |
846 | */ |
847 | |
848 | void QUiLoader::setLanguageChangeEnabled(bool enabled) |
849 | { |
850 | Q_D(QUiLoader); |
851 | d->builder.dynamicTr = enabled; |
852 | } |
853 | |
854 | /*! |
855 | \since 4.5 |
856 | |
857 | Returns true if dynamic retranslation on language change is enabled; |
858 | returns false otherwise. |
859 | |
860 | \sa setLanguageChangeEnabled() |
861 | */ |
862 | |
863 | bool QUiLoader::isLanguageChangeEnabled() const |
864 | { |
865 | Q_D(const QUiLoader); |
866 | return d->builder.dynamicTr; |
867 | } |
868 | |
869 | /*! |
870 | \internal |
871 | \since 4.5 |
872 | |
873 | If \a enabled is true, user interfaces loaded by this loader will be |
874 | translated. Otherwise, the user interfaces will not be translated. |
875 | |
876 | \note This is orthogonal to languageChangeEnabled. |
877 | |
878 | \sa isLanguageChangeEnabled(), setLanguageChangeEnabled() |
879 | */ |
880 | |
881 | void QUiLoader::setTranslationEnabled(bool enabled) |
882 | { |
883 | Q_D(QUiLoader); |
884 | d->builder.trEnabled = enabled; |
885 | } |
886 | |
887 | /*! |
888 | \internal |
889 | \since 4.5 |
890 | |
891 | Returns true if translation is enabled; returns false otherwise. |
892 | |
893 | \sa setTranslationEnabled() |
894 | */ |
895 | |
896 | bool QUiLoader::isTranslationEnabled() const |
897 | { |
898 | Q_D(const QUiLoader); |
899 | return d->builder.trEnabled; |
900 | } |
901 | |
902 | /*! |
903 | Returns a human-readable description of the last error occurred in load(). |
904 | |
905 | \since 5.0 |
906 | \sa load() |
907 | */ |
908 | |
909 | QString QUiLoader::errorString() const |
910 | { |
911 | Q_D(const QUiLoader); |
912 | return d->builder.errorString(); |
913 | } |
914 | |
915 | QT_END_NAMESPACE |
916 | |
917 | #include "quiloader.moc" |
918 |
Definitions
- g_widgets
- operator<<
- operator>>
- translate
- TranslatingTextBuilder
- TranslatingTextBuilder
- idBased
- loadText
- toNativeValue
- qUiItemRoles
- recursiveReTranslate
- reTranslateWidgetItem
- reTranslateTableItem
- TranslationWatcher
- TranslationWatcher
- eventFilter
- FormBuilderPrivate
- FormBuilderPrivate
- defaultCreateWidget
- defaultCreateLayout
- defaultCreateAction
- defaultCreateActionGroup
- createWidget
- createLayout
- createActionGroup
- createAction
- convertTranslatable
- applyProperties
- create
- create
- addItem
- QUiLoaderPrivate
- setupWidgetMap
- QUiLoader
- ~QUiLoader
- load
- pluginPaths
- clearPluginPaths
- addPluginPath
- createWidget
- createLayout
- createActionGroup
- createAction
- availableWidgets
- availableLayouts
- setWorkingDirectory
- workingDirectory
- setLanguageChangeEnabled
- isLanguageChangeEnabled
- setTranslationEnabled
- isTranslationEnabled
Learn to use CMake with our Intro Training
Find out more