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