1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "formpreviewview.h"
5#include "messagemodel.h"
6
7#include <quiloader.h>
8
9#include <QtWidgets/QApplication>
10#include <QtWidgets/QFontComboBox>
11#include <QtWidgets/QFrame>
12#include <QtWidgets/QGridLayout>
13#include <QtWidgets/QListWidget>
14#include <QtWidgets/QMdiArea>
15#include <QtWidgets/QMdiSubWindow>
16#include <QtWidgets/QMenu>
17#include <QtWidgets/QStackedLayout>
18#include <QtWidgets/QStackedWidget>
19#include <QtWidgets/QTableWidget>
20#include <QtWidgets/QTabWidget>
21#include <QtWidgets/QToolBox>
22#include <QtWidgets/QTreeWidget>
23#include <QtWidgets/QScrollArea>
24
25#include <QtGui/QAction>
26
27#include <QtCore/QDebug>
28#include <QtCore/QTime>
29
30QT_BEGIN_NAMESPACE
31
32#if defined(Q_CC_SUN) || defined(Q_CC_HPACC) || defined(Q_CC_XLC)
33size_t qHash(const QUiTranslatableStringValue &tsv)
34#else
35static size_t qHash(const QUiTranslatableStringValue &tsv)
36#endif
37{
38 return qHash(key: tsv.value()) ^ qHash(key: tsv.qualifier());
39}
40
41static bool operator==(const QUiTranslatableStringValue &tsv1, const QUiTranslatableStringValue &tsv2)
42{
43 return tsv1.value() == tsv2.value() && tsv1.qualifier() == tsv2.qualifier();
44}
45
46#define INSERT_TARGET(_tsv, _type, _target, _prop) \
47 do { \
48 target.type = _type; \
49 target.target._target; \
50 target.prop._prop; \
51 (*targets)[qvariant_cast<QUiTranslatableStringValue>(_tsv)].append(target); \
52 } while (0)
53
54static void registerTreeItem(QTreeWidgetItem *item, TargetsHash *targets)
55{
56 const QUiItemRolePair *irs = QFormInternal::qUiItemRoles;
57
58 int cnt = item->columnCount();
59 for (int i = 0; i < cnt; ++i) {
60 for (unsigned j = 0; irs[j].shadowRole >= 0; j++) {
61 QVariant v = item->data(column: i, role: irs[j].shadowRole);
62 if (v.isValid()) {
63 TranslatableEntry target;
64 target.prop.treeIndex.column = i;
65 INSERT_TARGET(v, TranslatableTreeWidgetItem, treeWidgetItem = item, treeIndex.index = j);
66 }
67 }
68 }
69
70 cnt = item->childCount();
71 for (int j = 0; j < cnt; ++j)
72 registerTreeItem(item: item->child(index: j), targets);
73}
74
75#define REGISTER_ITEM_CORE(item, propType, targetName) \
76 const QUiItemRolePair *irs = QFormInternal::qUiItemRoles; \
77 for (unsigned j = 0; irs[j].shadowRole >= 0; j++) { \
78 QVariant v = item->data(irs[j].shadowRole); \
79 if (v.isValid()) \
80 INSERT_TARGET(v, propType, targetName = item, index = j); \
81 }
82
83static void registerListItem(QListWidgetItem *item, TargetsHash *targets)
84{
85 TranslatableEntry target;
86 REGISTER_ITEM_CORE(item, TranslatableListWidgetItem, listWidgetItem);
87}
88
89static void registerTableItem(QTableWidgetItem *item, TargetsHash *targets)
90{
91 if (!item)
92 return;
93
94 TranslatableEntry target;
95 REGISTER_ITEM_CORE(item, TranslatableTableWidgetItem, tableWidgetItem);
96}
97
98#define REGISTER_SUBWIDGET_PROP(mainWidget, propType, propName) \
99 do { \
100 QVariant v = mainWidget->widget(i)->property(propName); \
101 if (v.isValid()) \
102 INSERT_TARGET(v, propType, object = mainWidget, index = i); \
103 } while (0)
104
105static void buildTargets(QObject *o, TargetsHash *targets)
106{
107 TranslatableEntry target;
108
109 const auto propNames = o->dynamicPropertyNames();
110 for (const QByteArray &prop : propNames) {
111 if (prop.startsWith(PROP_GENERIC_PREFIX)) {
112 const QByteArray propName = prop.mid(index: sizeof(PROP_GENERIC_PREFIX) - 1);
113 INSERT_TARGET(o->property(prop),
114 TranslatableProperty, object = o, name = qstrdup(propName.data()));
115 }
116 }
117 if (0) {
118#ifndef QT_NO_TABWIDGET
119 } else if (QTabWidget *tabw = qobject_cast<QTabWidget*>(object: o)) {
120 const int cnt = tabw->count();
121 for (int i = 0; i < cnt; ++i) {
122 REGISTER_SUBWIDGET_PROP(tabw, TranslatableTabPageText, PROP_TABPAGETEXT);
123# ifndef QT_NO_TOOLTIP
124 REGISTER_SUBWIDGET_PROP(tabw, TranslatableTabPageToolTip, PROP_TABPAGETOOLTIP);
125# endif
126# ifndef QT_NO_WHATSTHIS
127 REGISTER_SUBWIDGET_PROP(tabw, TranslatableTabPageWhatsThis, PROP_TABPAGEWHATSTHIS);
128# endif
129 }
130#endif
131#ifndef QT_NO_TOOLBOX
132 } else if (QToolBox *toolw = qobject_cast<QToolBox*>(object: o)) {
133 const int cnt = toolw->count();
134 for (int i = 0; i < cnt; ++i) {
135 REGISTER_SUBWIDGET_PROP(toolw, TranslatableToolItemText, PROP_TOOLITEMTEXT);
136# ifndef QT_NO_TOOLTIP
137 REGISTER_SUBWIDGET_PROP(toolw, TranslatableToolItemToolTip, PROP_TOOLITEMTOOLTIP);
138# endif
139 }
140#endif
141#ifndef QT_NO_COMBOBOX
142 } else if (QComboBox *combow = qobject_cast<QComboBox*>(object: o)) {
143 if (!qobject_cast<QFontComboBox*>(object: o)) {
144 const int cnt = combow->count();
145 for (int i = 0; i < cnt; ++i) {
146 const QVariant v = combow->itemData(index: i, role: Qt::DisplayPropertyRole);
147 if (v.isValid())
148 INSERT_TARGET(v, TranslatableComboBoxItem, comboBox = combow, index = i);
149 }
150 }
151#endif
152#ifndef QT_NO_LISTWIDGET
153 } else if (QListWidget *listw = qobject_cast<QListWidget*>(object: o)) {
154 const int cnt = listw->count();
155 for (int i = 0; i < cnt; ++i)
156 registerListItem(item: listw->item(row: i), targets);
157#endif
158#ifndef QT_NO_TABLEWIDGET
159 } else if (QTableWidget *tablew = qobject_cast<QTableWidget*>(object: o)) {
160 const int row_cnt = tablew->rowCount();
161 const int col_cnt = tablew->columnCount();
162 for (int j = 0; j < col_cnt; ++j)
163 registerTableItem(item: tablew->horizontalHeaderItem(column: j), targets);
164 for (int i = 0; i < row_cnt; ++i) {
165 registerTableItem(item: tablew->verticalHeaderItem(row: i), targets);
166 for (int j = 0; j < col_cnt; ++j)
167 registerTableItem(item: tablew->item(row: i, column: j), targets);
168 }
169#endif
170#ifndef QT_NO_TREEWIDGET
171 } else if (QTreeWidget *treew = qobject_cast<QTreeWidget*>(object: o)) {
172 if (QTreeWidgetItem *item = treew->headerItem())
173 registerTreeItem(item, targets);
174 const int cnt = treew->topLevelItemCount();
175 for (int i = 0; i < cnt; ++i)
176 registerTreeItem(item: treew->topLevelItem(index: i), targets);
177#endif
178 }
179 for (QObject *co : o->children())
180 buildTargets(o: co, targets);
181}
182
183static void destroyTargets(TargetsHash *targets)
184{
185 for (const auto &targetList : std::as_const(t&: *targets))
186 for (const TranslatableEntry &target : targetList)
187 if (target.type == TranslatableProperty)
188 delete target.prop.name;
189 targets->clear();
190}
191
192static void retranslateTarget(const TranslatableEntry &target, const QString &text)
193{
194 switch (target.type) {
195 case TranslatableProperty:
196 target.target.object->setProperty(name: target.prop.name, value: text);
197 break;
198#ifndef QT_NO_TABWIDGET
199 case TranslatableTabPageText:
200 target.target.tabWidget->setTabText(index: target.prop.index, text);
201 break;
202# ifndef QT_NO_TOOLTIP
203 case TranslatableTabPageToolTip:
204 target.target.tabWidget->setTabToolTip(index: target.prop.index, tip: text);
205 break;
206# endif
207# ifndef QT_NO_WHATSTHIS
208 case TranslatableTabPageWhatsThis:
209 target.target.tabWidget->setTabWhatsThis(index: target.prop.index, text);
210 break;
211# endif
212#endif // QT_NO_TABWIDGET
213#ifndef QT_NO_TOOLBOX
214 case TranslatableToolItemText:
215 target.target.toolBox->setItemText(index: target.prop.index, text);
216 break;
217# ifndef QT_NO_TOOLTIP
218 case TranslatableToolItemToolTip:
219 target.target.toolBox->setItemToolTip(index: target.prop.index, toolTip: text);
220 break;
221# endif
222#endif // QT_NO_TOOLBOX
223#ifndef QT_NO_COMBOBOX
224 case TranslatableComboBoxItem:
225 target.target.comboBox->setItemText(index: target.prop.index, text);
226 break;
227#endif
228#ifndef QT_NO_LISTWIDGET
229 case TranslatableListWidgetItem:
230 target.target.listWidgetItem->setData(role: target.prop.index, value: text);
231 break;
232#endif
233#ifndef QT_NO_TABLEWIDGET
234 case TranslatableTableWidgetItem:
235 target.target.tableWidgetItem->setData(role: target.prop.index, value: text);
236 break;
237#endif
238#ifndef QT_NO_TREEWIDGET
239 case TranslatableTreeWidgetItem:
240 target.target.treeWidgetItem->setData(column: target.prop.treeIndex.column, role: target.prop.treeIndex.index, value: text);
241 break;
242#endif
243 }
244}
245
246static void retranslateTargets(
247 const QList<TranslatableEntry> &targets, const QUiTranslatableStringValue &tsv,
248 const DataModel *dataModel, const QString &className)
249{
250 QString sourceText = QString::fromUtf8(ba: tsv.value());
251 QString text;
252 if (MessageItem *msg = dataModel->findMessage(
253 context: className, sourcetext: sourceText, comment: QString::fromUtf8(ba: tsv.qualifier())))
254 text = msg->translation();
255 if (text.isEmpty() && !tsv.value().isEmpty())
256 text = QLatin1Char('#') + sourceText;
257
258 for (const TranslatableEntry &target : targets)
259 retranslateTarget(target, text);
260}
261
262static void bringToFront(QWidget *w)
263{
264 for (; QWidget *pw = w->parentWidget(); w = pw) {
265#ifndef QT_NO_STACKEDWIDGET
266 if (QStackedWidget *stack = qobject_cast<QStackedWidget *>(object: pw)) {
267#ifndef QT_NO_TABWIDGET
268 // Updating QTabWidget's embedded QStackedWidget does not update its
269 // QTabBar, so handle tab widgets explicitly.
270 if (QTabWidget *tab = qobject_cast<QTabWidget *>(object: stack->parent()))
271 tab->setCurrentWidget(w);
272 else
273#endif
274 stack->setCurrentWidget(w);
275 continue;
276 }
277#endif
278#ifndef QT_NO_TOOLBOX
279 if (QScrollArea *sv = qobject_cast<QScrollArea *>(object: pw)) {
280 if (QToolBox *tb = qobject_cast<QToolBox *>(object: sv->parent()))
281 tb->setCurrentWidget(w);
282 }
283#endif
284 }
285}
286
287static void highlightTreeWidgetItem(QTreeWidgetItem *item, int col, bool on)
288{
289 QVariant br = item->data(column: col, role: Qt::BackgroundRole + 500);
290 QVariant fr = item->data(column: col, role: Qt::ForegroundRole + 500);
291 if (on) {
292 if (!br.isValid() && !fr.isValid()) {
293 item->setData(column: col, role: Qt::BackgroundRole + 500, value: item->data(column: col, role: Qt::BackgroundRole));
294 item->setData(column: col, role: Qt::ForegroundRole + 500, value: item->data(column: col, role: Qt::ForegroundRole));
295 QPalette pal = qApp->palette();
296 item->setData(column: col, role: Qt::BackgroundRole, value: pal.color(cr: QPalette::Dark));
297 item->setData(column: col, role: Qt::ForegroundRole, value: pal.color(cr: QPalette::Light));
298 }
299 } else {
300 if (br.isValid() || fr.isValid()) {
301 item->setData(column: col, role: Qt::BackgroundRole, value: br);
302 item->setData(column: col, role: Qt::ForegroundRole, value: fr);
303 item->setData(column: col, role: Qt::BackgroundRole + 500, value: QVariant());
304 item->setData(column: col, role: Qt::ForegroundRole + 500, value: QVariant());
305 }
306 }
307}
308
309template <class T>
310static void highlightWidgetItem(T *item, bool on)
311{
312 QVariant br = item->data(Qt::BackgroundRole + 500);
313 QVariant fr = item->data(Qt::ForegroundRole + 500);
314 if (on) {
315 if (!br.isValid() && !fr.isValid()) {
316 item->setData(Qt::BackgroundRole + 500, item->data(Qt::BackgroundRole));
317 item->setData(Qt::ForegroundRole + 500, item->data(Qt::ForegroundRole));
318 QPalette pal = qApp->palette();
319 item->setData(Qt::BackgroundRole, pal.color(cr: QPalette::Dark));
320 item->setData(Qt::ForegroundRole, pal.color(cr: QPalette::Light));
321 }
322 } else {
323 if (br.isValid() || fr.isValid()) {
324 item->setData(Qt::BackgroundRole, br);
325 item->setData(Qt::ForegroundRole, fr);
326 item->setData(Qt::BackgroundRole + 500, QVariant());
327 item->setData(Qt::ForegroundRole + 500, QVariant());
328 }
329 }
330}
331
332#define AUTOFILL_BACKUP_PROP "_q_linguist_autoFillBackup"
333#define PALETTE_BACKUP_PROP "_q_linguist_paletteBackup"
334#define FONT_BACKUP_PROP "_q_linguist_fontBackup"
335
336static void highlightWidget(QWidget *w, bool on);
337
338static void highlightAction(QAction *a, bool on)
339{
340 QVariant bak = a->property(FONT_BACKUP_PROP);
341 if (on) {
342 if (!bak.isValid()) {
343 QFont fnt = qApp->font();
344 a->setProperty(FONT_BACKUP_PROP, value: QVariant::fromValue(value: a->font().resolve(fnt)));
345 fnt.setBold(true);
346 fnt.setItalic(true);
347 a->setFont(fnt);
348 }
349 } else {
350 if (bak.isValid()) {
351 a->setFont(qvariant_cast<QFont>(v: bak));
352 a->setProperty(FONT_BACKUP_PROP, value: QVariant());
353 }
354 }
355 for (QObject *o : a->associatedObjects()) {
356 if (QWidget *w = qobject_cast<QWidget *>(o))
357 highlightWidget(w, on);
358 }
359}
360
361static void highlightWidget(QWidget *w, bool on)
362{
363 QVariant bak = w->property(PALETTE_BACKUP_PROP);
364 if (on) {
365 if (!bak.isValid()) {
366 QPalette pal = qApp->palette();
367 for (QObject *co : w->children())
368 if (QWidget *cw = qobject_cast<QWidget *>(o: co))
369 cw->setPalette(cw->palette().resolve(other: pal));
370 w->setProperty(PALETTE_BACKUP_PROP, value: QVariant::fromValue(value: w->palette().resolve(other: pal)));
371 w->setProperty(AUTOFILL_BACKUP_PROP, value: QVariant::fromValue(value: w->autoFillBackground()));
372 QColor col1 = pal.color(cr: QPalette::Dark);
373 QColor col2 = pal.color(cr: QPalette::Light);
374 pal.setColor(acr: QPalette::Base, acolor: col1);
375 pal.setColor(acr: QPalette::Window, acolor: col1);
376 pal.setColor(acr: QPalette::Button, acolor: col1);
377 pal.setColor(acr: QPalette::Text, acolor: col2);
378 pal.setColor(acr: QPalette::WindowText, acolor: col2);
379 pal.setColor(acr: QPalette::ButtonText, acolor: col2);
380 pal.setColor(acr: QPalette::BrightText, acolor: col2);
381 w->setPalette(pal);
382 w->setAutoFillBackground(true);
383 }
384 } else {
385 if (bak.isValid()) {
386 w->setPalette(qvariant_cast<QPalette>(v: bak));
387 w->setAutoFillBackground(qvariant_cast<bool>(v: w->property(AUTOFILL_BACKUP_PROP)));
388 w->setProperty(PALETTE_BACKUP_PROP, value: QVariant());
389 w->setProperty(AUTOFILL_BACKUP_PROP, value: QVariant());
390 }
391 }
392 if (QMenu *m = qobject_cast<QMenu *>(object: w))
393 if (m->menuAction())
394 highlightAction(a: m->menuAction(), on);
395}
396
397static void highlightTarget(const TranslatableEntry &target, bool on)
398{
399 switch (target.type) {
400 case TranslatableProperty:
401 if (QAction *a = qobject_cast<QAction *>(object: target.target.object)) {
402 highlightAction(a, on);
403 } else if (QWidget *w = qobject_cast<QWidget *>(o: target.target.object)) {
404 bringToFront(w);
405 highlightWidget(w, on);
406 }
407 break;
408#ifndef QT_NO_COMBOBOX
409 case TranslatableComboBoxItem:
410 static_cast<QComboBox *>(target.target.object)->setCurrentIndex(target.prop.index);
411 goto frontAndHighlight;
412#endif
413#ifndef QT_NO_TABWIDGET
414 case TranslatableTabPageText:
415 static_cast<QTabWidget *>(target.target.object)->setCurrentIndex(target.prop.index);
416 goto frontAndHighlight;
417# ifndef QT_NO_TOOLTIP
418 case TranslatableTabPageToolTip:
419# endif
420# ifndef QT_NO_WHATSTHIS
421 case TranslatableTabPageWhatsThis:
422# endif
423#endif // QT_NO_TABWIDGET
424#ifndef QT_NO_TOOLBOX
425 case TranslatableToolItemText:
426# ifndef QT_NO_TOOLTIP
427 case TranslatableToolItemToolTip:
428# endif
429#endif // QT_NO_TOOLBOX
430#if !defined(QT_NO_COMBOBOX) || !defined(QT_NO_TABWIDGET)
431 frontAndHighlight:
432#endif
433 bringToFront(w: static_cast<QWidget *>(target.target.object));
434 highlightWidget(w: static_cast<QWidget *>(target.target.object), on);
435 break;
436#ifndef QT_NO_LISTWIDGET
437 case TranslatableListWidgetItem:
438 bringToFront(w: target.target.listWidgetItem->listWidget());
439 highlightWidgetItem(item: target.target.listWidgetItem, on);
440 break;
441#endif
442#ifndef QT_NO_TABLEWIDGET
443 case TranslatableTableWidgetItem:
444 bringToFront(w: target.target.tableWidgetItem->tableWidget());
445 highlightWidgetItem(item: target.target.tableWidgetItem, on);
446 break;
447#endif
448#ifndef QT_NO_TREEWIDGET
449 case TranslatableTreeWidgetItem:
450 bringToFront(w: target.target.treeWidgetItem->treeWidget());
451 highlightTreeWidgetItem(item: target.target.treeWidgetItem, col: target.prop.treeIndex.column, on);
452 break;
453#endif
454 }
455}
456
457static void highlightTargets(const QList<TranslatableEntry> &targets, bool on)
458{
459 for (const TranslatableEntry &target : targets)
460 highlightTarget(target, on);
461}
462
463FormPreviewView::FormPreviewView(QWidget *parent, MultiDataModel *dataModel)
464 : QMainWindow(parent), m_form(0), m_dataModel(dataModel)
465{
466 m_mdiSubWindow = new QMdiSubWindow;
467 m_mdiSubWindow->setWindowFlags(m_mdiSubWindow->windowFlags() & ~Qt::WindowSystemMenuHint);
468 m_mdiArea = new QMdiArea(this);
469 m_mdiArea->addSubWindow(widget: m_mdiSubWindow);
470 setCentralWidget(m_mdiArea);
471 m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
472 m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
473}
474
475void FormPreviewView::setSourceContext(int model, MessageItem *messageItem)
476{
477 if (model < 0 || !messageItem) {
478 m_lastModel = -1;
479 return;
480 }
481
482 QDir dir = QFileInfo(m_dataModel->srcFileName(model)).dir();
483 QString fileName = QDir::cleanPath(path: dir.absoluteFilePath(fileName: messageItem->fileName()));
484 if (m_lastFormName != fileName) {
485 delete m_form;
486 m_form = 0;
487 m_lastFormName.clear();
488 m_highlights.clear();
489 destroyTargets(targets: &m_targets);
490
491 static QUiLoader *uiLoader;
492 if (!uiLoader) {
493 uiLoader = new QUiLoader(this);
494 uiLoader->setLanguageChangeEnabled(true);
495 uiLoader->setTranslationEnabled(false);
496 }
497
498 QFile file(fileName);
499 if (!file.open(flags: QIODevice::ReadOnly)) {
500 qDebug() << "CANNOT OPEN FORM" << fileName;
501 m_mdiSubWindow->hide();
502 return;
503 }
504 m_form = uiLoader->load(device: &file, parentWidget: m_mdiSubWindow);
505 if (!m_form) {
506 qDebug() << "CANNOT LOAD FORM" << fileName;
507 m_mdiSubWindow->hide();
508 return;
509 }
510 file.close();
511 buildTargets(o: m_form, targets: &m_targets);
512
513 setToolTip(fileName);
514
515 m_form->setWindowFlags(Qt::Widget);
516 m_form->setWindowModality(Qt::NonModal);
517 m_form->setFocusPolicy(Qt::NoFocus);
518 m_form->show(); // needed, otherwide the Qt::NoFocus is not propagated.
519 m_mdiSubWindow->setWidget(m_form);
520 m_mdiSubWindow->setWindowTitle(m_form->windowTitle());
521 m_mdiSubWindow->show();
522 m_mdiArea->cascadeSubWindows();
523 m_lastFormName = fileName;
524 m_lastClassName = messageItem->context();
525 m_lastModel = -1;
526 } else {
527 highlightTargets(targets: m_highlights, on: false);
528 }
529 QUiTranslatableStringValue tsv;
530 tsv.setValue(messageItem->text().toUtf8());
531 tsv.setQualifier(messageItem->comment().toUtf8());
532 m_highlights = m_targets.value(key: tsv);
533 if (m_lastModel != model) {
534 for (auto it = m_targets.cbegin(), end = m_targets.cend(); it != end; ++it)
535 retranslateTargets(targets: *it, tsv: it.key(), dataModel: m_dataModel->model(i: model), className: m_lastClassName);
536 m_lastModel = model;
537 } else {
538 retranslateTargets(targets: m_highlights, tsv, dataModel: m_dataModel->model(i: model), className: m_lastClassName);
539 }
540 highlightTargets(targets: m_highlights, on: true);
541}
542
543QT_END_NAMESPACE
544

source code of qttools/src/linguist/linguist/formpreviewview.cpp