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