Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4/* TRANSLATOR MainWindow
5
6 This is the application's main window.
7*/
8
9#include "mainwindow.h"
10
13#include "errorsview.h"
14#include "finddialog.h"
15#include <helpclient.h>
17#ifndef Q_OS_WASM
19#endif // Q_OS_WASM
20#include "globals.h"
21#include "messageeditor.h"
22#include "messagemodel.h"
23#include "phrasebookbox.h"
24#include "phraseview.h"
25#include "printout.h"
26#include "sourcecodeview.h"
27#include "statistics.h"
30#include "validator.h"
31
32#include <QAction>
33#include <QApplication>
34#include <QBitmap>
35#include <QCloseEvent>
36#include <QDebug>
37#include <QDesktopServices>
38#include <QDockWidget>
39#include <QFile>
40#include <QFileDialog>
41#include <QFileInfo>
42#include <QHeaderView>
43#include <QInputDialog>
44#include <QItemDelegate>
45#include <QLabel>
46#include <QLayout>
47#include <QLibraryInfo>
48#include <QMenu>
49#include <QMenuBar>
50#include <QMessageBox>
51#include <QMimeData>
52#include <QRegularExpression>
53#include <QScreen>
54#include <QShortcut>
55#include <QSettings>
56#include <QSortFilterProxyModel>
57#include <QStackedWidget>
58#include <QStatusBar>
59#include <QTextStream>
60#include <QToolBar>
61#include <QUrl>
62#include <QWhatsThis>
63
64#if QT_CONFIG(printsupport)
65#include <QPrintDialog>
66#include <QPrinter>
67#endif
68
69using namespace Qt::Literals::StringLiterals;
70namespace {
71
72static bool hasUiFormPreview(const QString &fileName)
73{
74 return fileName.endsWith(".ui"_L1) || fileName.endsWith(".jui"_L1);
75}
76
77#ifndef Q_OS_WASM
78static bool hasQmlFormPreview(const QString &fileName, bool qmlPreviewChecked)
79{
80 return fileName.endsWith(QLatin1String(".qml")) && qmlPreviewChecked;
81}
82#endif // Q_OS_WASM
83
84static const int MessageMS = 2500;
85
86} // namespace
87
88QT_BEGIN_NAMESPACE
89
90class GroupItemDelegate : public QItemDelegate
91{
92public:
93 GroupItemDelegate(QObject *parent, MultiDataModel *model)
94 : QItemDelegate(parent), m_dataModel(model)
95 {
96 }
97
98 void paint(QPainter *painter, const QStyleOptionViewItem &option,
99 const QModelIndex &index) const override
100 {
101 const QAbstractItemModel *model = index.model();
102 Q_ASSERT(model);
103
104 if (!model->parent(index).isValid()) {
105 if (index.column() - 1 == m_dataModel->modelCount()) {
106 QStyleOptionViewItem opt = option;
107 opt.font.setBold(true);
108 QItemDelegate::paint(painter, opt, index);
109 return;
110 }
111 }
112 QItemDelegate::paint(painter, option, index);
113 }
114
115private:
116 MultiDataModel *m_dataModel;
117};
118
119static const QVariant &pxObsolete()
120{
121 static const QVariant v = createMarkIcon(TranslationMarks::ObsoleteMark);
122 return v;
123}
124
125
127{
128public:
129 SortedMessagesModel(QObject *parent, MultiDataModel *model, TranslationType translationType)
130 : QSortFilterProxyModel(parent), m_dataModel(model), m_translationType(translationType)
131 {
132 }
133
134 QVariant headerData(int section, Qt::Orientation orientation, int role) const override
135 {
136 if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
137 if (m_translationType == TEXTBASED) {
138 switch (section - m_dataModel->modelCount()) {
139 case 0:
140 return QString();
141 case 1:
142 return MainWindow::tr("Source text");
143 }
144 } else {
145 switch (section - m_dataModel->modelCount()) {
146 case 0: return QString();
147 case 1:
148 return MainWindow::tr("ID");
149 case 2:
150 return MainWindow::tr("Source text");
151 }
152 }
153 }
154 if (role == Qt::DecorationRole && orientation == Qt::Horizontal && section - 1 < m_dataModel->modelCount())
155 return pxObsolete();
156
157 return QVariant();
158 }
159
160private:
161 MultiDataModel *m_dataModel;
162 TranslationType m_translationType;
163};
164
166{
167public:
168 SortedGroupsModel(QObject *parent, MultiDataModel *model, TranslationType translationType)
169 : QSortFilterProxyModel(parent), m_dataModel(model), m_translationType(translationType)
170 {
171 }
172
173 QVariant headerData(int section, Qt::Orientation orientation, int role) const override
174 {
175 if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
176 switch (section - m_dataModel->modelCount()) {
177 case 0: return QString();
178 case 1:
179 return m_translationType == TEXTBASED ? MainWindow::tr("Context")
180 : MainWindow::tr("Label");
181 case 2:
182 return MainWindow::tr("Items");
183 }
184
185 if (role == Qt::DecorationRole && orientation == Qt::Horizontal && section - 1 < m_dataModel->modelCount())
186 return pxObsolete();
187
188 return QVariant();
189 }
190
191private:
192 MultiDataModel *m_dataModel;
193 TranslationType m_translationType;
194};
195
196class FocusWatcher : public QObject
197{
198public:
199 FocusWatcher(MessageEditor *msgedit, QObject *parent) : QObject(parent), m_messageEditor(msgedit) {}
200
201protected:
202 bool eventFilter(QObject *object, QEvent *event) override;
203
204private:
205 MessageEditor *m_messageEditor;
206};
207
208bool FocusWatcher::eventFilter(QObject *, QEvent *event)
209{
210 if (event->type() == QEvent::FocusIn)
211 m_messageEditor->setEditorFocusForModel(-1);
212 return false;
213}
214
216 : QMainWindow(0, Qt::Window),
218#if QT_CONFIG(printsupport)
219 m_printer(0),
220#endif // QT_CONFIG(printsupport)
221 m_findWhere(DataModel::NoLocation),
222 m_translationSettingsDialog(0),
223 m_settingCurrentMessage(false),
224 m_fileActiveModel(-1),
225 m_editActiveModel(-1),
226 m_statistics(0),
227 m_recentFiles(10)
228{
229 setUnifiedTitleAndToolBarOnMac(true);
230 m_ui.setupUi(this);
231
232#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
233 setWindowIcon(QPixmap(":/images/appicon.png"_L1));
234#endif
235
236 m_dataModel = new MultiDataModel(this);
237 m_idBasedMessageModel = new MessageModel(IDBASED, this, m_dataModel);
238 m_textBasedMessageModel = new MessageModel(TEXTBASED, this, m_dataModel);
239
240 // Set up the context/label dock widget
241 m_contextAndLabelDock = new QDockWidget(this);
242 m_contextAndLabelDock->setObjectName("ContextLabelDockWidget");
243 m_contextAndLabelDock->setAllowedAreas(Qt::AllDockWidgetAreas);
244 m_contextAndLabelDock->setWindowTitle(tr("Context/Label"));
245 m_contextAndLabelDock->setAcceptDrops(true);
246 m_contextAndLabelDock->installEventFilter(this);
247
248 m_contextAndLabelView = new QTabWidget(this);
249
250 QWidget* dockContent = new QWidget(this);
251 QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, dockContent);
252 layout->addWidget(m_contextAndLabelView);
253
254 m_contextAndLabelDock->setWidget(dockContent);
255
256 // context view
257 m_sortedContextsModel = new SortedGroupsModel(this, m_dataModel, TEXTBASED);
258 m_sortedContextsModel->setSortRole(MessageModel::SortRole);
259 m_sortedContextsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
260 m_sortedContextsModel->setSourceModel(m_textBasedMessageModel);
261
262 m_contextView = new QTreeView(this);
263 m_contextView->setRootIsDecorated(false);
264 m_contextView->setItemsExpandable(false);
265 m_contextView->setUniformRowHeights(true);
266 m_contextView->setAlternatingRowColors(true);
267 m_contextView->setAllColumnsShowFocus(true);
268 m_contextView->setItemDelegate(new GroupItemDelegate(this, m_dataModel));
269 m_contextView->setSortingEnabled(true);
270 m_contextView->setWhatsThis(tr("This panel lists the source contexts."));
271 m_contextView->setModel(m_sortedContextsModel);
272 m_contextView->header()->setSectionsMovable(false);
273 m_contextView->setColumnHidden(0, true);
274 m_contextView->header()->setStretchLastSection(false);
275
276 m_contextAndLabelView->addTab(m_contextView, "Text Based"_L1);
277
278 // label view
279 m_sortedLabelsModel = new SortedGroupsModel(this, m_dataModel, IDBASED);
280 m_sortedLabelsModel->setSortRole(MessageModel::SortRole);
281 m_sortedLabelsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
282 m_sortedLabelsModel->setSourceModel(m_idBasedMessageModel);
283
284 m_labelView = new QTreeView(this);
285 m_labelView->setRootIsDecorated(false);
286 m_labelView->setItemsExpandable(false);
287 m_labelView->setUniformRowHeights(true);
288 m_labelView->setAlternatingRowColors(true);
289 m_labelView->setAllColumnsShowFocus(true);
290 m_labelView->setItemDelegate(new GroupItemDelegate(this, m_dataModel));
291 m_labelView->setSortingEnabled(true);
292 m_labelView->setWhatsThis(tr("This panel lists the source labels."));
293 m_labelView->setModel(m_sortedLabelsModel);
294 m_labelView->header()->setSectionsMovable(false);
295 m_labelView->setColumnHidden(0, true);
296 m_labelView->header()->setStretchLastSection(false);
297
298 m_contextAndLabelView->addTab(m_labelView, "ID Based"_L1);
299
300 // Set up the messages dock widget
301 m_messagesDock = new QDockWidget(this);
302 m_messagesDock->setObjectName("StringsDockWidget");
303 m_messagesDock->setAllowedAreas(Qt::AllDockWidgetAreas);
304 m_messagesDock->setWindowTitle(tr("Strings"));
305 m_messagesDock->setAcceptDrops(true);
306 m_messagesDock->installEventFilter(this);
307
308 m_sortedTextBasedMessagesModel = new SortedMessagesModel(this, m_dataModel, TEXTBASED);
309 m_sortedTextBasedMessagesModel->setSortRole(MessageModel::SortRole);
310 m_sortedTextBasedMessagesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
311 m_sortedTextBasedMessagesModel->setSortLocaleAware(true);
312 m_sortedTextBasedMessagesModel->setSourceModel(m_textBasedMessageModel);
313
314 m_sortedIdBasedMessagesModel = new SortedMessagesModel(this, m_dataModel, IDBASED);
315 m_sortedIdBasedMessagesModel->setSortRole(MessageModel::SortRole);
316 m_sortedIdBasedMessagesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
317 m_sortedIdBasedMessagesModel->setSortLocaleAware(true);
318 m_sortedIdBasedMessagesModel->setSourceModel(m_idBasedMessageModel);
319
320 m_messageView = new QTreeView(m_messagesDock);
321 m_messageView->setSortingEnabled(true);
322 m_messageView->setRootIsDecorated(false);
323 m_messageView->setUniformRowHeights(true);
324 m_messageView->setAllColumnsShowFocus(true);
325 m_messageView->setItemsExpandable(false);
326 m_messageView->setModel(m_sortedTextBasedMessagesModel);
327 m_messageView->header()->setSectionsMovable(false);
328 m_messageView->setColumnHidden(0, true);
329
330 m_messagesDock->setWidget(m_messageView);
331
332 // Set up main message view
333 m_messageEditor = new MessageEditor(m_dataModel, this);
334 m_messageEditor->setAcceptDrops(true);
335 m_messageEditor->installEventFilter(this);
336 // We can't call setCentralWidget(m_messageEditor), since it is already called in m_ui.setupUi()
337 QBoxLayout *lout = new QBoxLayout(QBoxLayout::TopToBottom, m_ui.centralwidget);
338 lout->addWidget(m_messageEditor);
339 lout->setContentsMargins(QMargins());
340 m_ui.centralwidget->setLayout(lout);
341
342 // Set up the phrases & guesses dock widget
343 m_phrasesDock = new QDockWidget(this);
344 m_phrasesDock->setObjectName("PhrasesDockwidget");
345 m_phrasesDock->setAllowedAreas(Qt::AllDockWidgetAreas);
346 m_phrasesDock->setWindowTitle(tr("Phrases and guesses"));
347
348 m_phraseView = new PhraseView(m_dataModel, &m_phraseDict, this);
349 m_phrasesDock->setWidget(m_phraseView);
350
351 // Set up source code and form preview dock widget
352 m_sourceAndFormDock = new QDockWidget(this);
353 m_sourceAndFormDock->setObjectName("SourceAndFormDock");
354 m_sourceAndFormDock->setAllowedAreas(Qt::AllDockWidgetAreas);
355 m_sourceAndFormDock->setWindowTitle(tr("Sources and Forms"));
356 m_sourceAndFormView = new QStackedWidget(this);
357 m_sourceAndFormDock->setWidget(m_sourceAndFormView);
358 m_uiFormPreviewView = new UiFormPreviewView(0, m_dataModel);
359 m_sourceCodeView = new SourceCodeView(0);
360#ifndef Q_OS_WASM
361 m_qmlFormPreviewView = new QmlFormPreviewView(m_dataModel);
362 m_sourceAndFormView->addWidget(m_qmlFormPreviewView);
363#endif // Q_OS_WASM
364 m_sourceAndFormView->addWidget(m_sourceCodeView);
365 m_sourceAndFormView->addWidget(m_uiFormPreviewView);
366
367 // Set up errors dock widget
368 m_errorsDock = new QDockWidget(this);
369 m_errorsDock->setObjectName("ErrorsDockWidget");
370 m_errorsDock->setAllowedAreas(Qt::AllDockWidgetAreas);
371 m_errorsDock->setWindowTitle(tr("Warnings"));
372 m_errorsView = new ErrorsView(m_dataModel, this);
373 m_errorsDock->setWidget(m_errorsView);
374
375 // Arrange dock widgets
376 setDockNestingEnabled(true);
377 setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
378 setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
379 setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
380 setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
381 addDockWidget(Qt::LeftDockWidgetArea, m_contextAndLabelDock);
382 addDockWidget(Qt::TopDockWidgetArea, m_messagesDock);
383 addDockWidget(Qt::BottomDockWidgetArea, m_phrasesDock);
384 addDockWidget(Qt::TopDockWidgetArea, m_sourceAndFormDock);
385 m_sourceAndFormDock->hide();
386 addDockWidget(Qt::BottomDockWidgetArea, m_errorsDock);
387 //tabifyDockWidget(m_errorsDock, m_sourceAndFormDock);
388 //tabifyDockWidget(m_sourceCodeDock, m_phrasesDock);
389
390 // Allow phrases doc to intercept guesses shortcuts
391 m_messageEditor->installEventFilter(m_phraseView);
392
393 // Set up shortcuts for the dock widgets
394 QShortcut *contextShortcut = new QShortcut(QKeySequence(Qt::Key_F6), this);
395 connect(contextShortcut, &QShortcut::activated,
396 this, &MainWindow::showContextDock);
397 QShortcut *messagesShortcut = new QShortcut(QKeySequence(Qt::Key_F7), this);
398 connect(messagesShortcut, &QShortcut::activated,
399 this, &MainWindow::showMessagesDock);
400 QShortcut *errorsShortcut = new QShortcut(QKeySequence(Qt::Key_F8), this);
401 connect(errorsShortcut, &QShortcut::activated,
402 this, &MainWindow::showErrorDock);
403 QShortcut *sourceCodeShortcut = new QShortcut(QKeySequence(Qt::Key_F9), this);
404 connect(sourceCodeShortcut, &QShortcut::activated,
405 this, &MainWindow::showSourceCodeDock);
406 QShortcut *phrasesShortcut = new QShortcut(QKeySequence(Qt::Key_F10), this);
407 connect(phrasesShortcut, &QShortcut::activated,
408 this, &MainWindow::showPhrasesDock);
409
410 connect(m_phraseView, &PhraseView::phraseSelected,
411 m_messageEditor, &MessageEditor::setTranslation);
412 connect(m_phraseView, &PhraseView::setCurrentMessageFromGuess,
413 this, &MainWindow::setCurrentMessageFromGuess);
414 connect(m_contextView->selectionModel(), &QItemSelectionModel::currentRowChanged,
415 this, &MainWindow::selectedContextChanged);
416 connect(m_labelView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
417 &MainWindow::selectedLabelChanged);
418 connect(m_contextView->selectionModel(), &QItemSelectionModel::currentColumnChanged,
419 this, &MainWindow::updateLatestModel);
420 connect(m_labelView->selectionModel(), &QItemSelectionModel::currentColumnChanged, this,
421 &MainWindow::updateLatestModel);
422 connect(m_messageEditor, &MessageEditor::activeModelChanged,
423 this, &MainWindow::updateActiveModel);
424 connect(m_contextAndLabelView, &QTabWidget::currentChanged, this,
425 &MainWindow::contextAndLabelTabChanged);
426
427 m_translateDialog = new TranslateDialog(this);
428 m_batchTranslateDialog = new BatchTranslationDialog(m_dataModel, this);
429 m_findDialog = new FindDialog(this);
430
431 setupMenuBar();
432 setupToolBars();
433
434 m_progressLabel = new QLabel();
435 statusBar()->addPermanentWidget(m_progressLabel);
436 m_modifiedLabel = new QLabel(tr(" MOD ", "status bar: file(s) modified"));
437 statusBar()->addPermanentWidget(m_modifiedLabel);
438 m_contextAndLabelView->setCurrentWidget(m_contextView);
439 contextAndLabelTabChanged();
440
441 modelCountChanged();
442 initViewHeaders();
443 resetSorting();
444
445 connect(m_dataModel, &MultiDataModel::modifiedChanged,
446 this, &QWidget::setWindowModified);
447 connect(m_dataModel, &MultiDataModel::modifiedChanged,
448 m_modifiedLabel, &QWidget::setVisible);
449 connect(m_dataModel, &MultiDataModel::multiGroupDataChanged, this, &MainWindow::updateProgress);
450 connect(m_dataModel, &MultiDataModel::messageDataChanged, this,
451 &MainWindow::maybeUpdateStatistics);
452 connect(m_dataModel, &MultiDataModel::translationChanged, this,
453 &MainWindow::translationChanged);
454 connect(m_dataModel, &MultiDataModel::languageChanged,
455 this, &MainWindow::updatePhraseDict);
456
457 setWindowModified(m_dataModel->isModified());
458 m_modifiedLabel->setVisible(m_dataModel->isModified());
459
460 connect(m_messageView, &QAbstractItemView::clicked,
461 this, &MainWindow::toggleFinished);
462 connect(m_messageView, &QAbstractItemView::activated,
463 m_messageEditor, &MessageEditor::setEditorFocus);
464 connect(m_contextView, &QAbstractItemView::activated,
465 m_messageView, qOverload<>(&QWidget::setFocus));
466 connect(m_labelView, &QAbstractItemView::activated, m_messageView,
467 qOverload<>(&QWidget::setFocus));
468 connect(m_messageEditor, &MessageEditor::translationChanged,
469 this, &MainWindow::updateTranslation);
470 connect(m_messageEditor, &MessageEditor::translatorCommentChanged,
471 this, &MainWindow::updateTranslatorComment);
472 connect(m_findDialog, &FindDialog::findNext,
473 this, &MainWindow::findNext);
474 connect(m_translateDialog, &TranslateDialog::requestMatchUpdate,
475 this, &MainWindow::updateTranslateHit);
476 connect(m_translateDialog, &TranslateDialog::activated,
477 this, &MainWindow::translate);
478
479 QSize as(screen()->size());
480 as -= QSize(30, 30);
481 resize(QSize(1000, 800).boundedTo(as));
482 show();
484 m_statistics = 0;
485
486 connect(m_ui.actionLengthVariants, &QAction::toggled,
487 m_messageEditor, &MessageEditor::setLengthVariants);
488 m_messageEditor->setLengthVariants(m_ui.actionLengthVariants->isChecked());
489 m_messageEditor->setVisualizeWhitespace(m_ui.actionVisualizeWhitespace->isChecked());
490
491 m_focusWatcher = new FocusWatcher(m_messageEditor, this);
492 m_contextView->installEventFilter(m_focusWatcher);
493 m_labelView->installEventFilter(m_focusWatcher);
494 m_messageView->installEventFilter(m_focusWatcher);
495 m_messageEditor->installEventFilter(m_focusWatcher);
496 m_sourceAndFormView->installEventFilter(m_focusWatcher);
497 m_phraseView->installEventFilter(m_focusWatcher);
498 m_errorsView->installEventFilter(m_focusWatcher);
499}
500
502{
504 qDeleteAll(m_phraseBooks);
505 delete m_dataModel;
506 delete m_statistics;
507#if QT_CONFIG(printsupport)
508 delete m_printer;
509#endif
510}
511
512void MainWindow::initViewHeaders()
513{
514 m_contextView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
515 m_contextView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
516 m_labelView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
517 m_labelView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
518 // last visible column auto-stretches
519}
520
521void MainWindow::modelCountChanged()
522{
523 int mc = m_dataModel->modelCount();
524
525 for (int i = 1; i < mc + 1; ++i) {
526 m_contextView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
527 m_contextView->header()->resizeSection(i, 24);
528
529 m_labelView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
530 m_labelView->header()->resizeSection(i, 24);
531
532 m_messageView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
533 m_messageView->header()->resizeSection(i, 24);
534 }
535 for (int i = mc + 1; i < m_messageView->header()->count(); i++)
536 m_messageView->header()->setSectionResizeMode(i, QHeaderView::Stretch);
537
538 if (!mc) {
539 selectedMessageChanged(QModelIndex(), QModelIndex());
540 doUpdateLatestModel(-1);
541 } else {
542 QTreeView *view = qobject_cast<QTreeView *>(m_contextAndLabelView->currentWidget());
543 if (!view->currentIndex().isValid()) {
544 // Ensure that something is selected
545 view->setCurrentIndex(m_activeSortedGroupsModel->index(0, 0));
546 } else {
547 // Plug holes that turn up in the selection due to inserting columns
548 view->selectionModel()->select(view->currentIndex(),
549 QItemSelectionModel::SelectCurrent
550 | QItemSelectionModel::Rows);
551 m_messageView->selectionModel()->select(m_messageView->currentIndex(),
552 QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
553 }
554 // Field insertions/removals are automatic, but not the re-fill
555 m_messageEditor->showMessage(m_currentIndex);
556 if (mc == 1)
557 doUpdateLatestModel(0);
558 else if (m_currentIndex.model() >= mc)
559 doUpdateLatestModel(mc - 1);
560 }
561
562 m_contextView->setUpdatesEnabled(true);
563 m_labelView->setUpdatesEnabled(true);
564 m_messageView->setUpdatesEnabled(true);
565
566 updateProgress();
567 updateCaption();
568
569 m_ui.actionFind->setEnabled(m_dataModel->contextCount() > 0 || m_dataModel->labelCount() > 0);
570 m_ui.actionFindNext->setEnabled(false);
571 m_ui.actionFindPrev->setEnabled(false);
572
573 m_uiFormPreviewView->setSourceContext(-1, 0);
574#ifndef Q_OS_WASM
575 m_qmlFormPreviewView->setSourceContext(-1, 0);
576#endif // Q_OS_WASM
577 updateVisibleColumns();
578}
579
581 OpenedFile(DataModel *_dataModel, bool _readWrite, bool _langGuessed)
582 { dataModel = _dataModel; readWrite = _readWrite; langGuessed = _langGuessed; }
586};
587
588bool MainWindow::openFiles(const QStringList &names)
589{
590 if (names.isEmpty())
591 return false;
592
593 bool waitCursor = false;
594 statusBar()->showMessage(tr("Loading..."));
595 qApp->processEvents();
596
597 QList<OpenedFile> opened;
598 bool closeOld = false;
599 for (QString name : names) {
600 if (!waitCursor) {
601 QApplication::setOverrideCursor(Qt::WaitCursor);
602 waitCursor = true;
603 }
604
605 bool readWrite = m_globalReadWrite;
606 if (name.startsWith(u'=')) {
607 name.remove(0, 1);
608 readWrite = false;
609 }
610 QFileInfo fi(name);
611 if (fi.exists()) // Make the loader error out instead of reading stdin
612 name = fi.canonicalFilePath();
613 if (m_dataModel->isFileLoaded(name) >= 0)
614 closeOld = true;
615
616 bool langGuessed;
617 DataModel *dm = new DataModel(m_dataModel);
618 if (!dm->load(name, &langGuessed, this)) {
619 delete dm;
620 continue;
621 }
622 if (opened.isEmpty()) {
623 if (!m_dataModel->isWellMergeable(dm)) {
624 QApplication::restoreOverrideCursor();
625 waitCursor = false;
626 switch (QMessageBox::information(this, tr("Loading File - Qt Linguist"),
627 tr("The file '%1' does not seem to be related to the currently open file(s) '%2'.\n\n"
628 "Close the open file(s) first?")
629 .arg(DataModel::prettifyPlainFileName(name), m_dataModel->condensedSrcFileNames(true)),
630 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
631 {
632 case QMessageBox::Cancel:
633 delete dm;
634 return false;
635 case QMessageBox::Yes:
636 closeOld = true;
637 break;
638 default:
639 break;
640 }
641 }
642 } else {
643 if (!opened.first().dataModel->isWellMergeable(dm)) {
644 QApplication::restoreOverrideCursor();
645 waitCursor = false;
646 switch (QMessageBox::information(this, tr("Loading File - Qt Linguist"),
647 tr("The file '%1' does not seem to be related to the file '%2'"
648 " which is being loaded as well.\n\n"
649 "Skip loading the first named file?")
650 .arg(DataModel::prettifyPlainFileName(name), opened.first().dataModel->srcFileName(true)),
651 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
652 {
653 case QMessageBox::Cancel:
654 delete dm;
655 for (const OpenedFile &op : std::as_const(opened))
656 delete op.dataModel;
657 return false;
658 case QMessageBox::Yes:
659 delete dm;
660 continue;
661 default:
662 break;
663 }
664 }
665 }
666 opened.append(OpenedFile(dm, readWrite, langGuessed));
667 }
668
669 if (closeOld) {
670 if (waitCursor) {
671 QApplication::restoreOverrideCursor();
672 waitCursor = false;
673 }
674 if (!closeAll()) {
675 for (const OpenedFile &op : std::as_const(opened))
676 delete op.dataModel;
677 return false;
678 }
679 }
680
681 for (const OpenedFile &op : std::as_const(opened)) {
682 if (op.langGuessed) {
683 if (waitCursor) {
684 QApplication::restoreOverrideCursor();
685 waitCursor = false;
686 }
687 if (!m_translationSettingsDialog)
688 m_translationSettingsDialog = new TranslationSettingsDialog(this);
689 m_translationSettingsDialog->setDataModel(op.dataModel);
690 m_translationSettingsDialog->exec();
691 }
692 }
693
694 if (!waitCursor)
695 QApplication::setOverrideCursor(Qt::WaitCursor);
696 m_contextView->setUpdatesEnabled(false);
697 m_labelView->setUpdatesEnabled(false);
698 m_messageView->setUpdatesEnabled(false);
699 int totalCount = 0;
700 for (const OpenedFile &op : std::as_const(opened)) {
701 m_phraseDict.append(QHash<QString, QList<Phrase *> >());
702 m_dataModel->append(op.dataModel, op.readWrite);
703 if (op.readWrite)
704 updatePhraseDictInternal(m_phraseDict.size() - 1);
705 totalCount += op.dataModel->messageCount();
706 }
707 statusBar()->showMessage(tr("%n translation unit(s) loaded.", 0, totalCount), MessageMS);
708 modelCountChanged();
709 m_recentFiles.addFiles(m_dataModel->srcFileNames());
710
711 revalidate();
712 QApplication::restoreOverrideCursor();
713 return true;
714}
715
716void MainWindow::open()
717{
718 m_globalReadWrite = true;
719 pickTranslationFiles();
720}
721
722void MainWindow::openAux()
723{
724 m_globalReadWrite = false;
725 pickTranslationFiles();
726}
727
728void MainWindow::closeFile()
729{
730 int model = m_currentIndex.model();
731 if (model >= 0 && maybeSave(model)) {
732 m_phraseDict.removeAt(model);
733 m_contextView->setUpdatesEnabled(false);
734 m_labelView->setUpdatesEnabled(false);
735 m_messageView->setUpdatesEnabled(false);
736 m_dataModel->close(model);
737 modelCountChanged();
738 }
739}
740
741bool MainWindow::closeAll()
742{
743 if (maybeSaveAll()) {
744 m_phraseDict.clear();
745 m_contextView->setUpdatesEnabled(false);
746 m_labelView->setUpdatesEnabled(false);
747 m_messageView->setUpdatesEnabled(false);
748 m_dataModel->closeAll();
749 modelCountChanged();
750 initViewHeaders();
751 m_recentFiles.closeGroup();
752 return true;
753 }
754 return false;
755}
756
757static QString fileFilters(bool allFirst)
758{
759 static const QString pattern("%1 (*.%2);;"_L1);
760 QStringList allExtensions;
761 QString filter;
762 for (const Translator::FileFormat &format : std::as_const(Translator::registeredFileFormats())) {
763 if (format.fileType == Translator::FileFormat::TranslationSource && format.priority >= 0) {
764 filter.append(pattern.arg(format.description(), format.extension));
765 allExtensions.append("*."_L1 + format.extension);
766 }
767 }
768 QString allFilter = QObject::tr("Translation files (%1);;").arg(allExtensions.join(u' '));
769 if (allFirst)
770 filter.prepend(allFilter);
771 else
772 filter.append(allFilter);
773 filter.append(QObject::tr("All files (*)"));
774 return filter;
775}
776
777void MainWindow::pickTranslationFiles()
778{
779 QString dir;
780 if (!m_recentFiles.isEmpty())
781 dir = QFileInfo(m_recentFiles.lastOpenedFile()).path();
782
783 QString varFilt;
784 if (m_dataModel->modelCount()) {
785 QFileInfo mainFile(m_dataModel->srcFileName(0));
786 QString mainFileBase = mainFile.baseName();
787 int pos = mainFileBase.indexOf(u'_');
788 if (pos > 0)
789 varFilt = tr("Related files (%1);;")
790 .arg(mainFileBase.left(pos) + "_*."_L1 + mainFile.completeSuffix());
791 }
792
793#ifndef Q_OS_WASM
794 openFiles(QFileDialog::getOpenFileNames(this, tr("Open Translation Files"), dir,
795 varFilt + fileFilters(true)));
796#else
797 const auto fileOpenCompleted = [this](const QString &fileName, const QByteArray &fileContent) {
798 const QString copyFileName =
799 QDir::tempPath() + QLatin1String("/") + QFileInfo(fileName).fileName();
800 QFile tsFile(copyFileName);
801 if (tsFile.open(QIODevice::WriteOnly)) {
802 tsFile.write(fileContent);
803 tsFile.close();
804 openFiles({ copyFileName });
805 m_wasmFileMap[copyFileName] = std::move(fileName);
806 }
807 };
808
809 QFileDialog::getOpenFileContent(varFilt + fileFilters(true), fileOpenCompleted);
810#endif // Q_OS_WASM
811}
812
813void MainWindow::saveInternal(int model)
814{
815 QApplication::setOverrideCursor(Qt::WaitCursor);
816 if (m_dataModel->save(model, this)) {
817 updateCaption();
818#ifdef Q_OS_WASM
819 QString wasmFileName = m_dataModel->model(model)->srcFileName();
820 if (const auto itr = m_wasmFileMap.find(wasmFileName); itr != m_wasmFileMap.end()) {
821 QFile tsFile(wasmFileName);
822 if (tsFile.open(QIODevice::ReadOnly)) {
823
824 QByteArray content = tsFile.readAll();
825 QFileDialog::saveFileContent(content, itr.value(), this);
826 }
827 }
828#endif // Q_OS_WASM
829 statusBar()->showMessage(tr("File saved."), MessageMS);
830 }
831 QApplication::restoreOverrideCursor();
832}
833
834void MainWindow::saveAll()
835{
836 for (int i = 0; i < m_dataModel->modelCount(); ++i)
837 if (m_dataModel->isModelWritable(i))
838 saveInternal(i);
839 m_recentFiles.closeGroup();
840}
841
842void MainWindow::save()
843{
844 if (m_currentIndex.model() < 0) {
845 QMessageBox::warning(this, tr("Qt Linguist"), tr("Please select a file to be saved."));
846 return;
847 }
848
849 saveInternal(m_currentIndex.model());
850}
851
852void MainWindow::saveAs()
853{
854#ifdef Q_OS_WASM
855 QMessageBox::warning(this, tr("Qt Linguist"),
856 tr("This function is not available on WebAssembly"));
857 return;
858#endif // Q_OS_WASM
859
860 if (m_currentIndex.model() < 0)
861 return;
862
863 QString newFilename = QFileDialog::getSaveFileName(
864 this, QString(), m_dataModel->srcFileName(m_currentIndex.model()), fileFilters(false));
865 if (!newFilename.isEmpty()) {
866 if (m_dataModel->saveAs(m_currentIndex.model(), newFilename, this)) {
867 updateCaption();
868 statusBar()->showMessage(tr("File saved."), MessageMS);
869 m_recentFiles.addFiles(m_dataModel->srcFileNames());
870 }
871 }
872}
873
874void MainWindow::releaseAs()
875{
876 if (m_currentIndex.model() < 0)
877 return;
878
879 QFileInfo oldFile(m_dataModel->srcFileName(m_currentIndex.model()));
880 QString newFilename = oldFile.path() + "/"_L1 + oldFile.completeBaseName() + ".qm"_L1;
881
882 newFilename = QFileDialog::getSaveFileName(this, tr("Release"), newFilename,
883 tr("Qt message files for released applications (*.qm)\nAll files (*)"));
884 if (!newFilename.isEmpty()) {
885 if (m_dataModel->release(m_currentIndex.model(), newFilename, false, false, SaveEverything, this))
886 statusBar()->showMessage(tr("File created."), MessageMS);
887 }
888}
889
890void MainWindow::releaseInternal(int model)
891{
892 QFileInfo oldFile(m_dataModel->srcFileName(model));
893 QString newFilename = oldFile.path() + u'/' + oldFile.completeBaseName() + ".qm"_L1;
894
895 if (!newFilename.isEmpty()) {
896 if (m_dataModel->release(model, newFilename, false, false, SaveEverything, this))
897 statusBar()->showMessage(tr("File created."), MessageMS);
898 }
899}
900
901// No-question
902void MainWindow::release()
903{
904 if (m_currentIndex.model() < 0)
905 return;
906
907 releaseInternal(m_currentIndex.model());
908}
909
910void MainWindow::releaseAll()
911{
912 for (int i = 0; i < m_dataModel->modelCount(); ++i)
913 if (m_dataModel->isModelWritable(i))
914 releaseInternal(i);
915}
916
917#if QT_CONFIG(printsupport)
918QPrinter *MainWindow::printer()
919{
920 if (!m_printer)
921 m_printer = new QPrinter;
922 return m_printer;
923}
924
925void MainWindow::print()
926{
927 int pageNum = 0;
928 QPrintDialog dlg(printer(), this);
929 if (dlg.exec()) {
930 QApplication::setOverrideCursor(Qt::WaitCursor);
931 printer()->setDocName(m_dataModel->condensedSrcFileNames(true));
932 statusBar()->showMessage(tr("Printing..."));
933 PrintOut pout(printer());
934
935 auto printGroupItem = [&pout, &pageNum, this](int index, TranslationType type) {
936 MultiGroupItem *mg = m_dataModel->multiGroupItem(index, type);
937 pout.vskip();
938 pout.setRule(PrintOut::ThickRule);
939 pout.setGuide(mg->group());
940 if (type == IDBASED)
941 pout.addBox(100, tr("Label: %1").arg(mg->group()), PrintOut::Strong);
942 else
943 pout.addBox(100, tr("Context: %1").arg(mg->group()), PrintOut::Strong);
944 pout.flushLine();
945 pout.addBox(4);
946 pout.addBox(92, mg->comment(), PrintOut::Emphasis);
947 pout.flushLine();
948 pout.setRule(PrintOut::ThickRule);
949
950 for (int j = 0; j < mg->messageCount(); ++j) {
951 pout.setRule(PrintOut::ThinRule);
952 bool printedSrc = false;
953 QString comment;
954 for (int k = 0; k < m_dataModel->modelCount(); ++k) {
955 if (const MessageItem *m = mg->messageItem(k, j)) {
956 if (!printedSrc) {
957 pout.addBox(40, m->text());
958 pout.addBox(4);
959 comment = m->comment();
960 printedSrc = true;
961 } else {
962 pout.addBox(44); // Maybe put the name of the translation here
963 }
964 if (m->message().isPlural() && m_dataModel->language(k) != QLocale::C) {
965 QStringList transls = m->translations();
966 pout.addBox(40, transls.join(u'\n'));
967 } else {
968 pout.addBox(40, m->translation());
969 }
970 pout.addBox(4);
971 QString type;
972 switch (m->message().type()) {
973 case TranslatorMessage::Finished:
974 type = tr("finished");
975 break;
976 case TranslatorMessage::Unfinished:
977 type = m->danger() ? tr("unresolved") : "unfinished"_L1;
978 break;
979 case TranslatorMessage::Obsolete:
980 case TranslatorMessage::Vanished:
981 type = tr("obsolete");
982 break;
983 }
984 pout.addBox(12, type, PrintOut::Normal, Qt::AlignRight);
985 pout.flushLine();
986 }
987 }
988 if (!comment.isEmpty()) {
989 pout.addBox(4);
990 pout.addBox(92, comment, PrintOut::Emphasis);
991 pout.flushLine(true);
992 }
993
994 if (pout.pageNum() != pageNum) {
995 pageNum = pout.pageNum();
996 statusBar()->showMessage(tr("Printing... (page %1)").arg(pageNum));
997 }
998 }
999 };
1000
1001 for (int i = 0; i < m_dataModel->labelCount(); ++i)
1002 printGroupItem(i, IDBASED);
1003 for (int i = 0; i < m_dataModel->contextCount(); ++i)
1004 printGroupItem(i, TEXTBASED);
1005 pout.flushLine(true);
1006 QApplication::restoreOverrideCursor();
1007 statusBar()->showMessage(tr("Printing completed"), MessageMS);
1008 } else {
1009 statusBar()->showMessage(tr("Printing aborted"), MessageMS);
1010 }
1011}
1012
1013#endif // QT_CONFIG(printsupport)
1014
1015bool MainWindow::searchItem(DataModel::FindLocation where, const QString &searchWhat)
1016{
1017 if ((m_findWhere & where) == 0)
1018 return false;
1019
1020 QString text = searchWhat;
1021
1022 if (m_findOptions.testFlag(FindDialog::IgnoreAccelerators))
1023 // FIXME: This removes too much. The proper solution might be too slow, though.
1024 text.remove(u'&');
1025
1026 if (m_findOptions.testFlag(FindDialog::UseRegExp))
1027 return m_findDialog->getRegExp().match(text).hasMatch();
1028 else
1029 return text.indexOf(m_findText, 0, m_findOptions.testFlag(FindDialog::MatchCase)
1030 ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0;
1031}
1032
1033void MainWindow::findAgain(FindDirection direction)
1034{
1035 if (m_dataModel->contextCount() == 0 && m_dataModel->labelCount() == 0)
1036 return;
1037
1038 const QModelIndex &startIndex = m_messageView->currentIndex();
1039 QModelIndex index = (direction == FindNext
1040 ? nextMessage(startIndex)
1041 : prevMessage(startIndex));
1042
1043 while (index.isValid()) {
1044 QModelIndex realIndex = m_activeSortedMessagesModel->mapToSource(index);
1045 MultiDataIndex dataIndex = m_activeMessageModel->dataIndex(realIndex, -1);
1046 bool hadMessage = false;
1047 for (int i = 0; i < m_dataModel->modelCount(); ++i) {
1048 if (MessageItem *m = m_dataModel->messageItem(dataIndex, i)) {
1049 if (m_findStatusFilter != -1 && m_findStatusFilter != m->type())
1050 continue;
1051
1052 if (m_findOptions.testFlag(FindDialog::SkipObsolete)
1053 && m->isObsolete())
1054 continue;
1055
1056 bool found = true;
1057 do {
1058 if (!hadMessage) {
1059 if (searchItem(DataModel::SourceText, m->text()))
1060 break;
1061 if (searchItem(DataModel::SourceText, m->pluralText()))
1062 break;
1063 if (searchItem(DataModel::Comments, m->comment()))
1064 break;
1065 if (searchItem(DataModel::Comments, m->extraComment()))
1066 break;
1067 }
1068 const auto translations = m->translations();
1069 for (const QString &trans : translations)
1070 if (searchItem(DataModel::Translations, trans))
1071 goto didfind;
1072 if (searchItem(DataModel::Comments, m->translatorComment()))
1073 break;
1074 found = false;
1075 // did not find the search string in this message
1076 } while (0);
1077 if (found) {
1078 didfind:
1079 setCurrentMessage(realIndex, i);
1080
1081 // determine whether the search wrapped
1082 const QModelIndex &c1 =
1083 m_activeSortedGroupsModel
1084 ->mapFromSource(
1085 m_activeSortedMessagesModel->mapToSource(startIndex))
1086 .parent();
1087 const QModelIndex &c2 =
1088 m_activeSortedGroupsModel->mapFromSource(realIndex).parent();
1089 const QModelIndex &m = m_activeSortedMessagesModel->mapFromSource(realIndex);
1090
1091 if (c2.row() < c1.row() || (c1.row() == c2.row() && m.row() <= startIndex.row()))
1092 statusBar()->showMessage(tr("Search wrapped."), MessageMS);
1093
1094 m_findDialog->hide();
1095 return;
1096 }
1097 hadMessage = true;
1098 }
1099 }
1100
1101 // since we don't search startIndex at the beginning, only now we have searched everything
1102 if (index == startIndex)
1103 break;
1104
1105 index = (direction == FindNext
1106 ? nextMessage(index)
1107 : prevMessage(index));
1108 }
1109
1110 qApp->beep();
1111 QMessageBox::warning(m_findDialog, tr("Qt Linguist"),
1112 tr("Cannot find the string '%1'.").arg(m_findText));
1113}
1114
1115void MainWindow::showBatchTranslateDialog()
1116{
1117 m_activeMessageModel->blockSignals(true);
1118 m_batchTranslateDialog->setPhraseBooks(m_phraseBooks, m_currentIndex.model());
1119 if (m_batchTranslateDialog->exec() != QDialog::Accepted)
1120 m_activeMessageModel->blockSignals(false);
1121 // else signal finished() calls refreshItemViews()
1122}
1123
1124void MainWindow::showTranslateDialog()
1125{
1126 m_latestCaseSensitivity = -1;
1127 QModelIndex idx = m_messageView->currentIndex();
1128 QModelIndex idx2 =
1129 m_activeSortedMessagesModel->index(idx.row(), m_currentIndex.model() + 1, idx.parent());
1130 m_messageView->setCurrentIndex(idx2);
1131 QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
1132 m_translateDialog->setWindowTitle(tr("Search And Translate in '%1' - Qt Linguist").arg(fn));
1133 m_translateDialog->exec();
1134}
1135
1136void MainWindow::updateTranslateHit(bool &hit)
1137{
1138 MessageItem *m;
1139 hit = (m = m_dataModel->messageItem(m_currentIndex))
1140 && !m->isObsolete()
1141 && m->compare(m_translateDialog->findText(), false, m_translateDialog->caseSensitivity());
1142}
1143
1144void MainWindow::translate(int mode)
1145{
1146 QString findText = m_translateDialog->findText();
1147 QString replaceText = m_translateDialog->replaceText();
1148 bool markFinished = m_translateDialog->markFinished();
1149 Qt::CaseSensitivity caseSensitivity = m_translateDialog->caseSensitivity();
1150
1151 int translatedCount = 0;
1152
1153 if (mode == TranslateDialog::TranslateAll) {
1154 auto setTranslations = [this, &translatedCount, &findText, &caseSensitivity, &replaceText,
1155 markFinished](TranslationType type) {
1156 for (MultiDataModelIterator it(type, m_dataModel, m_currentIndex.model()); it.isValid();
1157 ++it) {
1158 MessageItem *m = it.current();
1159 if (m && !m->isObsolete() && m->compare(findText, false, caseSensitivity)) {
1160 if (!translatedCount)
1161 m_activeMessageModel->blockSignals(true);
1162 m_dataModel->setTranslation(it, replaceText);
1163 m_dataModel->setFinished(it, markFinished);
1164 ++translatedCount;
1165 }
1166 }
1167 };
1168
1169 setTranslations(TEXTBASED);
1170 setTranslations(IDBASED);
1171 if (translatedCount) {
1172 refreshItemViews();
1173 QMessageBox::warning(m_translateDialog, tr("Translate - Qt Linguist"),
1174 tr("Translated %n entry(s)", 0, translatedCount));
1175 }
1176 } else {
1177 if (mode == TranslateDialog::Translate) {
1178 m_dataModel->setTranslation(m_currentIndex, replaceText);
1179 m_dataModel->setFinished(m_currentIndex, markFinished);
1180 }
1181
1182 const QModelIndex firstIndex = firstMessage();
1183 if (findText != m_latestFindText || caseSensitivity != m_latestCaseSensitivity) {
1184 m_latestFindText = findText;
1185 m_latestCaseSensitivity = caseSensitivity;
1186 m_searchIndex = firstIndex;
1187 m_hitCount = 0;
1188 }
1189
1190 forever {
1191 QModelIndex realIndex = m_activeSortedMessagesModel->mapToSource(m_searchIndex);
1192 MultiDataIndex dataIndex = m_activeMessageModel->dataIndex(realIndex, m_currentIndex.model());
1193 m_searchIndex = nextMessage(m_searchIndex);
1194 if (MessageItem *m = m_dataModel->messageItem(dataIndex)) {
1195 if (!m->isObsolete() && m->compare(findText, false, caseSensitivity)) {
1196 setCurrentMessage(realIndex, m_currentIndex.model());
1197 ++translatedCount;
1198 ++m_hitCount;
1199 break;
1200 }
1201 }
1202 if (m_searchIndex == firstIndex) {
1203 if (QMessageBox::question(
1204 m_translateDialog, tr("Translate - Qt Linguist"),
1205 tr("No more occurrences of '%1'. Start over?").arg(findText),
1206 QMessageBox::Yes | QMessageBox::No)
1207 != QMessageBox::Yes) {
1208 m_searchIndex = prevMessage(m_searchIndex);
1209 return;
1210 }
1211 }
1212 }
1213 }
1214
1215 if (!translatedCount) {
1216 qApp->beep();
1217 QMessageBox::warning(m_translateDialog, tr("Translate - Qt Linguist"),
1218 tr("Cannot find the string '%1'.").arg(findText));
1219 }
1220}
1221
1222void MainWindow::newPhraseBook()
1223{
1224 QString name = QFileDialog::getSaveFileName(this, tr("Create New Phrase Book"),
1225 m_phraseBookDir, tr("Qt phrase books (*.qph)\nAll files (*)"));
1226 if (!name.isEmpty()) {
1227 PhraseBook pb;
1228 if (!m_translationSettingsDialog)
1229 m_translationSettingsDialog = new TranslationSettingsDialog(this);
1230 m_translationSettingsDialog->setPhraseBook(&pb);
1231 if (!m_translationSettingsDialog->exec())
1232 return;
1233 m_phraseBookDir = QFileInfo(name).absolutePath();
1234 if (savePhraseBook(&name, pb)) {
1235 if (doOpenPhraseBook(name))
1236 statusBar()->showMessage(tr("Phrase book created."), MessageMS);
1237 }
1238 }
1239}
1240
1241bool MainWindow::isPhraseBookOpen(const QString &name)
1242{
1243 for (const PhraseBook *pb : std::as_const(m_phraseBooks)) {
1244 if (pb->fileName() == name)
1245 return true;
1246 }
1247
1248 return false;
1249}
1250
1251void MainWindow::openPhraseBook()
1252{
1253 QString name = QFileDialog::getOpenFileName(this, tr("Open Phrase Book"),
1254 m_phraseBookDir, tr("Qt phrase books (*.qph);;All files (*)"));
1255
1256 if (!name.isEmpty()) {
1257 m_phraseBookDir = QFileInfo(name).absolutePath();
1258 if (!isPhraseBookOpen(name)) {
1259 if (PhraseBook *phraseBook = doOpenPhraseBook(name)) {
1260 int n = phraseBook->phrases().size();
1261 statusBar()->showMessage(tr("%n phrase(s) loaded.", 0, n), MessageMS);
1262 }
1263 }
1264 }
1265}
1266
1267void MainWindow::closePhraseBook(QAction *action)
1268{
1269 PhraseBook *pb = m_phraseBookMenu[PhraseCloseMenu].value(action);
1270 if (!maybeSavePhraseBook(pb))
1271 return;
1272
1273 m_phraseBookMenu[PhraseCloseMenu].remove(action);
1274 m_ui.menuClosePhraseBook->removeAction(action);
1275
1276 QAction *act = m_phraseBookMenu[PhraseEditMenu].key(pb);
1277 m_phraseBookMenu[PhraseEditMenu].remove(act);
1278 m_ui.menuEditPhraseBook->removeAction(act);
1279
1280 act = m_phraseBookMenu[PhrasePrintMenu].key(pb);
1281 m_ui.menuPrintPhraseBook->removeAction(act);
1282
1283 m_phraseBooks.removeOne(pb);
1284 disconnect(pb, &PhraseBook::listChanged,
1285 this, &MainWindow::updatePhraseDicts);
1286 updatePhraseDicts();
1287 delete pb;
1288 updatePhraseBookActions();
1289}
1290
1291void MainWindow::editPhraseBook(QAction *action)
1292{
1293 PhraseBook *pb = m_phraseBookMenu[PhraseEditMenu].value(action);
1294 PhraseBookBox box(pb, this);
1295 box.exec();
1296
1297 updatePhraseDicts();
1298}
1299
1300#if QT_CONFIG(printsupport)
1301
1302void MainWindow::printPhraseBook(QAction *action)
1303{
1304 PhraseBook *phraseBook = m_phraseBookMenu[PhrasePrintMenu].value(action);
1305
1306 int pageNum = 0;
1307
1308 QPrintDialog dlg(printer(), this);
1309 if (dlg.exec()) {
1310 printer()->setDocName(phraseBook->fileName());
1311 statusBar()->showMessage(tr("Printing..."));
1312 PrintOut pout(printer());
1313 pout.setRule(PrintOut::ThinRule);
1314 const auto phrases = phraseBook->phrases();
1315 for (const Phrase *p : phrases) {
1316 pout.setGuide(p->source());
1317 pout.addBox(29, p->source());
1318 pout.addBox(4);
1319 pout.addBox(29, p->target());
1320 pout.addBox(4);
1321 pout.addBox(34, p->definition(), PrintOut::Emphasis);
1322
1323 if (pout.pageNum() != pageNum) {
1324 pageNum = pout.pageNum();
1325 statusBar()->showMessage(tr("Printing... (page %1)")
1326 .arg(pageNum));
1327 }
1328 pout.setRule(PrintOut::NoRule);
1329 pout.flushLine(true);
1330 }
1331 pout.flushLine(true);
1332 statusBar()->showMessage(tr("Printing completed"), MessageMS);
1333 } else {
1334 statusBar()->showMessage(tr("Printing aborted"), MessageMS);
1335 }
1336}
1337
1338#endif // QT_CONFIG(printsupport)
1339
1340void MainWindow::addToPhraseBook()
1341{
1342 QStringList phraseBookList;
1343 QHash<QString, PhraseBook *> phraseBookHash;
1344 for (PhraseBook *pb : std::as_const(m_phraseBooks)) {
1345 if (pb->language() != QLocale::C && m_dataModel->language(m_currentIndex.model()) != QLocale::C) {
1346 if (pb->language() != m_dataModel->language(m_currentIndex.model()))
1347 continue;
1348 if (pb->territory() == m_dataModel->model(m_currentIndex.model())->territory())
1349 phraseBookList.prepend(pb->friendlyPhraseBookName());
1350 else
1351 phraseBookList.append(pb->friendlyPhraseBookName());
1352 } else {
1353 phraseBookList.append(pb->friendlyPhraseBookName());
1354 }
1355 phraseBookHash.insert(pb->friendlyPhraseBookName(), pb);
1356 }
1357 if (phraseBookList.isEmpty()) {
1358 QMessageBox::warning(this, tr("Add to phrase book"),
1359 tr("No appropriate phrasebook found."));
1360 return;
1361 }
1362
1363 QString selectedPhraseBook;
1364 if (phraseBookList.size() == 1) {
1365 selectedPhraseBook = phraseBookList.at(0);
1366 if (QMessageBox::information(this, tr("Add to phrase book"),
1367 tr("Adding entry to phrasebook %1").arg(selectedPhraseBook),
1368 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)
1369 != QMessageBox::Ok)
1370 return;
1371 } else {
1372 bool okPressed = false;
1373 selectedPhraseBook = QInputDialog::getItem(this, tr("Add to phrase book"),
1374 tr("Select phrase book to add to"),
1375 phraseBookList, 0, false, &okPressed);
1376 if (!okPressed)
1377 return;
1378 }
1379
1380 MessageItem *currentMessage = m_dataModel->messageItem(m_currentIndex);
1381 Phrase *phrase = new Phrase(currentMessage->text(), currentMessage->translation(),
1382 QString(), nullptr);
1383
1384 phraseBookHash.value(selectedPhraseBook)->append(phrase);
1385}
1386
1387void MainWindow::resetSorting()
1388{
1389 m_contextView->sortByColumn(-1, Qt::AscendingOrder);
1390 m_labelView->sortByColumn(-1, Qt::AscendingOrder);
1391 m_messageView->sortByColumn(-1, Qt::AscendingOrder);
1392}
1393
1394void MainWindow::manual()
1395{
1396 QString errorMessage;
1397 const QString page = m_helpClient->documentUrl(u"linguist"_s) + "/qtlinguist-index.html"_L1;
1398 if (!m_helpClient->showPage(page, &errorMessage))
1399 qWarning("%s", qPrintable(errorMessage));
1400}
1401
1402QString MainWindow::description()
1403{
1404 return tr("Qt Linguist is a tool for adding translations to Qt applications.");
1405}
1406
1407void MainWindow::about()
1408{
1409 QMessageBox box(this);
1410 box.setTextFormat(Qt::RichText);
1411 QString version = tr("Version %1");
1412 version = version.arg(QLatin1String(QT_VERSION_STR));
1413
1414 box.setText(QStringLiteral("<center><img src=\":/images/icons/linguist-128-32.png\"/></img><p>%1</p></center>"
1415 "<p>%2</p>"
1416 "<p>Copyright (C) The Qt Company Ltd.</p>").arg(version, description()));
1417
1418 box.setWindowTitle(QApplication::translate("AboutDialog", "Qt Linguist"));
1419 box.setIcon(QMessageBox::NoIcon);
1420 box.exec();
1421}
1422
1423void MainWindow::aboutQt()
1424{
1425 QMessageBox::aboutQt(this, tr("Qt Linguist"));
1426}
1427
1428void MainWindow::setupPhrase()
1429{
1430 bool enabled = !m_phraseBooks.isEmpty();
1431 m_ui.menuClosePhraseBook->setEnabled(enabled);
1432 m_ui.menuEditPhraseBook->setEnabled(enabled);
1433#if QT_CONFIG(printsupport)
1434 m_ui.menuPrintPhraseBook->setEnabled(enabled);
1435#endif
1436}
1437
1438void MainWindow::closeEvent(QCloseEvent *e)
1439{
1440 if (maybeSaveAll() && maybeSavePhraseBooks())
1441 e->accept();
1442 else
1443 e->ignore();
1444}
1445
1446bool MainWindow::maybeSaveAll()
1447{
1448 if (!m_dataModel->isModified())
1449 return true;
1450
1451 switch (QMessageBox::information(this, tr("Qt Linguist"),
1452 tr("Do you want to save the modified files?"),
1453 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
1454 {
1455 case QMessageBox::Cancel:
1456 return false;
1457 case QMessageBox::Yes:
1458 saveAll();
1459 return !m_dataModel->isModified();
1460 default:
1461 break;
1462 }
1463 return true;
1464}
1465
1466bool MainWindow::maybeSave(int model)
1467{
1468 if (!m_dataModel->isModified(model))
1469 return true;
1470
1471 switch (QMessageBox::information(this, tr("Qt Linguist"),
1472 tr("Do you want to save '%1'?").arg(m_dataModel->srcFileName(model, true)),
1473 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
1474 {
1475 case QMessageBox::Cancel:
1476 return false;
1477 case QMessageBox::Yes:
1478 saveInternal(model);
1479 return !m_dataModel->isModified(model);
1480 default:
1481 break;
1482 }
1483 return true;
1484}
1485
1486void MainWindow::updateCaption()
1487{
1488 QString cap;
1489 bool enable = false;
1490 bool enableRw = false;
1491 for (int i = 0; i < m_dataModel->modelCount(); ++i) {
1492 enable = true;
1493 if (m_dataModel->isModelWritable(i)) {
1494 enableRw = true;
1495 break;
1496 }
1497 }
1498 m_ui.actionSaveAll->setEnabled(enableRw);
1499 m_ui.actionReleaseAll->setEnabled(enableRw);
1500 m_ui.actionCloseAll->setEnabled(enable);
1501#if QT_CONFIG(printsupport)
1502 m_ui.actionPrint->setEnabled(enable);
1503#endif
1504 m_ui.actionAccelerators->setEnabled(enable);
1505 m_ui.actionSurroundingWhitespace->setEnabled(enable);
1506 m_ui.actionEndingPunctuation->setEnabled(enable);
1507 m_ui.actionPhraseMatches->setEnabled(enable);
1508 m_ui.actionPlaceMarkerMatches->setEnabled(enable);
1509 m_ui.actionResetSorting->setEnabled(enable);
1510
1511 updateActiveModel(m_messageEditor->activeModel());
1512 // Ensure that the action labels get updated
1513 m_fileActiveModel = m_editActiveModel = -2;
1514
1515 if (!enable)
1516 cap = tr("Qt Linguist[*]");
1517 else
1518 cap = tr("%1[*] - Qt Linguist").arg(m_dataModel->condensedSrcFileNames(true));
1519 setWindowTitle(cap);
1520}
1521
1522void MainWindow::selectedContextChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
1523{
1524 if (sortedIndex.isValid()) {
1525 if (m_settingCurrentMessage)
1526 return; // Avoid playing ping-pong with the current message
1527
1528 if (!m_activeTranslationType || *m_activeTranslationType == IDBASED)
1529 contextAndLabelTabChanged();
1530 QModelIndex sourceIndex = m_sortedContextsModel->mapToSource(sortedIndex);
1531 if (m_activeMessageModel->parent(currentMessageIndex()).row() == sourceIndex.row())
1532 return;
1533 QModelIndex contextIndex = setMessageViewRoot(sourceIndex);
1534 const QModelIndex &firstChild =
1535 m_activeSortedMessagesModel->index(0, sourceIndex.column(), contextIndex);
1536 m_messageView->setCurrentIndex(firstChild);
1537 } else if (oldIndex.isValid()) {
1538 m_contextView->setCurrentIndex(oldIndex);
1539 }
1540}
1541
1542void MainWindow::selectedLabelChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
1543{
1544 if (sortedIndex.isValid()) {
1545 if (m_settingCurrentMessage)
1546 return; // Avoid playing ping-pong with the current message
1547
1548 if (!m_activeTranslationType || *m_activeTranslationType != IDBASED)
1549 contextAndLabelTabChanged();
1550 QModelIndex sourceIndex = m_sortedLabelsModel->mapToSource(sortedIndex);
1551 if (m_activeMessageModel->parent(currentMessageIndex()).row() == sourceIndex.row())
1552 return;
1553 QModelIndex labelIndex = setMessageViewRoot(sourceIndex);
1554 const QModelIndex &firstChild =
1555 m_activeSortedMessagesModel->index(0, sourceIndex.column(), labelIndex);
1556 m_messageView->setCurrentIndex(firstChild);
1557 } else if (oldIndex.isValid()) {
1558 m_labelView->setCurrentIndex(oldIndex);
1559 }
1560}
1561
1562/*
1563 * Updates the message displayed in the message editor and related actions.
1564 */
1565void MainWindow::selectedMessageChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
1566{
1567 // Keep a valid selection whenever possible
1568 if (!sortedIndex.isValid() && oldIndex.isValid()) {
1569 m_messageView->setCurrentIndex(oldIndex);
1570 return;
1571 }
1572
1573 int model = -1;
1574 MessageItem *m = nullptr;
1575 QModelIndex index = m_activeSortedMessagesModel->mapToSource(sortedIndex);
1576 if (index.isValid()) {
1577 model = (index.column() && (index.column() - 1 < m_dataModel->modelCount()))
1578 ? index.column() - 1
1579 : m_currentIndex.model();
1580 m_currentIndex = m_activeMessageModel->dataIndex(index, model);
1581 m_messageEditor->showMessage(m_currentIndex);
1582 if (model >= 0 && (m = m_dataModel->messageItem(m_currentIndex))) {
1583 if (m_dataModel->isModelWritable(model) && !m->isObsolete())
1584 m_phraseView->setSourceText(m_currentIndex.model(), m->text());
1585 else
1586 m_phraseView->setSourceText(-1, QString());
1587 } else {
1588 if (model < 0) {
1589 model = m_dataModel->multiGroupItem(m_currentIndex)
1591 if (model >= 0)
1592 m = m_dataModel->messageItem(m_currentIndex, model);
1593 }
1594 m_phraseView->setSourceText(-1, QString());
1595 }
1596 m_errorsView->setEnabled(m != 0);
1597 updateDanger(m_currentIndex, true);
1598 } else {
1599 m_currentIndex = MultiDataIndex(m_currentIndex.translationType());
1600 m_messageEditor->showNothing();
1601 m_phraseView->setSourceText(-1, QString());
1602 }
1603 updateSourceView(model, m);
1604
1605 updatePhraseBookActions();
1606 m_ui.actionSelectAll->setEnabled(index.isValid());
1607}
1608
1609void MainWindow::translationChanged(const MultiDataIndex &index)
1610{
1611 m_messageEditor->showMessage(index);
1612 updateDanger(index, true);
1613
1614 MessageItem *m = m_dataModel->messageItem(index);
1615 if (hasUiFormPreview(m->fileName()))
1616 m_uiFormPreviewView->setSourceContext(index.model(), m);
1617#ifndef Q_OS_WASM
1618 else if (hasQmlFormPreview(m->fileName(), m_ui.actionQmlPreview->isChecked()))
1619 if (!m_qmlFormPreviewView->setSourceContext(index.model(), m))
1620 m_ui.actionQmlPreview->setChecked(false);
1621#endif // Q_OS_WASM
1622}
1623
1624// This and the following function operate directly on the messageitem,
1625// so the model does not emit modification notifications.
1626void MainWindow::updateTranslation(const QStringList &translations)
1627{
1628 MessageItem *m = m_dataModel->messageItem(m_currentIndex);
1629 if (!m)
1630 return;
1631 if (translations == m->translations())
1632 return;
1633
1634 m->setTranslations(translations);
1635 if (!m->fileName().isEmpty() && hasUiFormPreview(m->fileName()))
1636 m_uiFormPreviewView->setSourceContext(m_currentIndex.model(), m);
1637#ifndef Q_OS_WASM
1638 else if (!m->fileName().isEmpty()
1639 && hasQmlFormPreview(m->fileName(), m_ui.actionQmlPreview->isChecked()))
1640 if (!m_qmlFormPreviewView->setSourceContext(m_currentIndex.model(), m))
1641 m_ui.actionQmlPreview->setChecked(false);
1642#endif // Q_OS_WASM
1643 updateDanger(m_currentIndex, true);
1644
1645 if (m->isFinished())
1646 m_dataModel->setFinished(m_currentIndex, false);
1647 else
1648 m_dataModel->setModified(m_currentIndex.model(), true);
1649}
1650
1651void MainWindow::updateTranslatorComment(const QString &comment)
1652{
1653 MessageItem *m = m_dataModel->messageItem(m_currentIndex);
1654 if (!m)
1655 return;
1656 if (comment == m->translatorComment())
1657 return;
1658
1659 m->setTranslatorComment(comment);
1660
1661 m_dataModel->setModified(m_currentIndex.model(), true);
1662}
1663
1664void MainWindow::refreshItemViews()
1665{
1666 m_activeMessageModel->blockSignals(false);
1667 m_contextView->update();
1668 m_labelView->update();
1669 m_messageView->update();
1670 setWindowModified(m_dataModel->isModified());
1671 m_modifiedLabel->setVisible(m_dataModel->isModified());
1672 updateStatistics();
1673}
1674
1675void MainWindow::done()
1676{
1677 int model = m_messageEditor->activeModel();
1678 if (model >= 0 && m_dataModel->isModelWritable(model))
1679 m_dataModel->setFinished(m_currentIndex, true);
1680}
1681
1682void MainWindow::doneAndNext()
1683{
1684 done();
1685 if (!m_messageEditor->focusNextUnfinished())
1686 nextUnfinished();
1687}
1688
1689void MainWindow::toggleFinished(const QModelIndex &index)
1690{
1691 if (!index.isValid() || index.column() - 1 >= m_dataModel->modelCount()
1692 || !m_dataModel->isModelWritable(index.column() - 1) || index.parent() == QModelIndex())
1693 return;
1694
1695 QModelIndex item = m_activeSortedMessagesModel->mapToSource(index);
1696 MultiDataIndex dataIndex = m_activeMessageModel->dataIndex(item);
1697 MessageItem *m = m_dataModel->messageItem(dataIndex);
1698
1701 return;
1702
1703 m_dataModel->setFinished(dataIndex, !m->isFinished());
1704}
1705
1706void MainWindow::openMachineTranslateDialog()
1707{
1708 if (!m_machineTranslationDialog)
1709 m_machineTranslationDialog = new MachineTranslationDialog(this);
1710 m_machineTranslationDialog->setDataModel(m_dataModel);
1711 m_machineTranslationDialog->open();
1712}
1713
1714/*
1715 * Receives a context index in the sorted messages model and returns the next
1716 * logical context index in the same model, based on the sort order of the
1717 * contexts in the sorted contexts model.
1718 */
1719QModelIndex MainWindow::nextGroup(const QModelIndex &index) const
1720{
1721 QModelIndex sortedGroupIndex = m_activeSortedGroupsModel->mapFromSource(
1722 m_activeSortedMessagesModel->mapToSource(index));
1723
1724 int nextRow = sortedGroupIndex.row() + 1;
1725 if (nextRow >= m_activeSortedGroupsModel->rowCount()) {
1726 const QSortFilterProxyModel *inactiveModel =
1727 m_activeSortedGroupsModel == m_sortedLabelsModel ? m_sortedContextsModel
1728 : m_sortedLabelsModel;
1729 if (inactiveModel->rowCount())
1730 m_contextAndLabelView->setCurrentIndex(1 - m_contextAndLabelView->currentIndex());
1731 nextRow = 0;
1732 }
1733 sortedGroupIndex = m_activeSortedGroupsModel->index(nextRow, index.column());
1734
1735 return m_activeSortedMessagesModel->mapFromSource(
1736 m_activeSortedGroupsModel->mapToSource(sortedGroupIndex));
1737}
1738
1739/*
1740 * See nextContext.
1741 */
1742QModelIndex MainWindow::prevGroup(const QModelIndex &index) const
1743{
1744 QModelIndex sortedGroupIndex = m_activeSortedGroupsModel->mapFromSource(
1745 m_activeSortedMessagesModel->mapToSource(index));
1746
1747 int prevRow = sortedGroupIndex.row() - 1;
1748 if (prevRow < 0) {
1749 const QSortFilterProxyModel *inactiveModel =
1750 m_activeSortedGroupsModel == m_sortedLabelsModel ? m_sortedContextsModel
1751 : m_sortedLabelsModel;
1752 if (inactiveModel->rowCount())
1753 m_contextAndLabelView->setCurrentIndex(1 - m_contextAndLabelView->currentIndex());
1754 prevRow = m_activeSortedGroupsModel->rowCount() - 1;
1755 }
1756 sortedGroupIndex = m_activeSortedGroupsModel->index(prevRow, index.column());
1757
1758 return m_activeSortedMessagesModel->mapFromSource(
1759 m_activeSortedGroupsModel->mapToSource(sortedGroupIndex));
1760}
1761
1762QModelIndex MainWindow::firstMessage() const
1763{
1764 QModelIndex id = m_activeSortedMessagesModel->index(0, 0);
1765 QModelIndex firstId;
1766 if (id.isValid() && m_activeSortedMessagesModel->hasChildren(id))
1767 firstId = m_activeSortedMessagesModel->index(0, 0, id);
1768 else if (id.isValid())
1769 firstId = id;
1770 return firstId;
1771}
1772
1773QModelIndex MainWindow::nextMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
1774{
1775 QModelIndex idx =
1776 currentIndex.isValid() ? currentIndex : m_activeSortedMessagesModel->index(0, 0);
1777 do {
1778 int row = 0;
1779 QModelIndex par = idx.parent();
1780 if (par.isValid()) {
1781 row = idx.row() + 1;
1782 } else { // In case we are located on a top-level node
1783 par = idx;
1784 }
1785
1786 if (row >= m_activeSortedMessagesModel->rowCount(par)) {
1787 par = nextGroup(par);
1788 row = 0;
1789 }
1790 idx = m_activeSortedMessagesModel->index(row, idx.column(), par);
1791
1792 if (!checkUnfinished)
1793 return idx;
1794
1795 QModelIndex item = m_activeSortedMessagesModel->mapToSource(idx);
1796 MultiDataIndex index = m_activeMessageModel->dataIndex(item, -1);
1797 if (m_dataModel->multiMessageItem(index)->isUnfinished())
1798 return idx;
1799 } while (idx != currentIndex);
1800 return QModelIndex();
1801}
1802
1803QModelIndex MainWindow::prevMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
1804{
1805 QModelIndex idx =
1806 currentIndex.isValid() ? currentIndex : m_activeSortedMessagesModel->index(0, 0);
1807 do {
1808 int row = idx.row() - 1;
1809 QModelIndex par = idx.parent();
1810 if (!par.isValid()) { // In case we are located on a top-level node
1811 par = idx;
1812 row = -1;
1813 }
1814
1815 if (row < 0) {
1816 par = prevGroup(par);
1817 row = m_activeSortedMessagesModel->rowCount(par) - 1;
1818 }
1819 idx = m_activeSortedMessagesModel->index(row, idx.column(), par);
1820
1821 if (!checkUnfinished)
1822 return idx;
1823
1824 QModelIndex item = m_activeSortedMessagesModel->mapToSource(idx);
1825 MultiDataIndex index = m_activeMessageModel->dataIndex(item, -1);
1826 if (m_dataModel->multiMessageItem(index)->isUnfinished())
1827 return idx;
1828 } while (idx != currentIndex);
1829 return QModelIndex();
1830}
1831
1832void MainWindow::nextUnfinished()
1833{
1834 if (m_ui.actionNextUnfinished->isEnabled()) {
1835 if (!doNext(true)) {
1836 // If no Unfinished message is left, the user has finished the job. We
1837 // congratulate on a job well done with this ringing bell.
1838 statusBar()->showMessage(tr("No untranslated translation units left."), MessageMS);
1839 qApp->beep();
1840 }
1841 }
1842}
1843
1844void MainWindow::prevUnfinished()
1845{
1846 if (m_ui.actionNextUnfinished->isEnabled()) {
1847 if (!doPrev(true)) {
1848 // If no Unfinished message is left, the user has finished the job. We
1849 // congratulate on a job well done with this ringing bell.
1850 statusBar()->showMessage(tr("No untranslated translation units left."), MessageMS);
1851 qApp->beep();
1852 }
1853 }
1854}
1855
1856void MainWindow::prev()
1857{
1858 doPrev(false);
1859}
1860
1861void MainWindow::next()
1862{
1863 doNext(false);
1864}
1865
1866bool MainWindow::doPrev(bool checkUnfinished)
1867{
1868 QModelIndex index = prevMessage(m_messageView->currentIndex(), checkUnfinished);
1869 if (index.isValid())
1870 setCurrentMessage(m_activeSortedMessagesModel->mapToSource(index));
1871 if (checkUnfinished)
1872 m_messageEditor->setUnfinishedEditorFocus();
1873 else
1874 m_messageEditor->setEditorFocus();
1875 return index.isValid();
1876}
1877
1878bool MainWindow::doNext(bool checkUnfinished)
1879{
1880 QModelIndex index = nextMessage(m_messageView->currentIndex(), checkUnfinished);
1881 if (index.isValid())
1882 setCurrentMessage(m_activeSortedMessagesModel->mapToSource(index));
1883 if (checkUnfinished)
1884 m_messageEditor->setUnfinishedEditorFocus();
1885 else
1886 m_messageEditor->setEditorFocus();
1887 return index.isValid();
1888}
1889
1890void MainWindow::findNext(const QString &text, DataModel::FindLocation where,
1891 FindDialog::FindOptions options, int statusFilter)
1892{
1893 if (text.isEmpty())
1894 return;
1895 m_findText = text;
1896 m_findWhere = where;
1897 m_findOptions = options;
1898 m_findStatusFilter = statusFilter;
1899 if (options.testFlag(FindDialog::UseRegExp)) {
1900 m_findDialog->getRegExp().setPatternOptions(options.testFlag(FindDialog::MatchCase)
1901 ? QRegularExpression::NoPatternOption
1902 : QRegularExpression::CaseInsensitiveOption);
1903 }
1904 m_ui.actionFindNext->setEnabled(true);
1905 m_ui.actionFindPrev->setEnabled(true);
1906 findAgain();
1907}
1908
1909void MainWindow::revalidate()
1910{
1911 for (MultiDataModelIterator it(IDBASED, m_dataModel, -1); it.isValid(); ++it)
1912 updateDanger(it, false);
1913 for (MultiDataModelIterator it(TEXTBASED, m_dataModel, -1); it.isValid(); ++it)
1914 updateDanger(it, false);
1915
1916 if (m_currentIndex.isValid())
1917 updateDanger(m_currentIndex, true);
1918}
1919
1920void MainWindow::updateIcons()
1921{
1922 const QString prefix = isDarkMode() ? ":/images/darkicons/"_L1: ":/images/lighticons/"_L1;
1923 auto getIcon = [&prefix](const QString &name) {
1924 QIcon icon;
1925 icon.addPixmap(QPixmap(prefix + name + QStringLiteral(".png")), QIcon::Normal);
1926 icon.addPixmap(QPixmap(prefix + name + QStringLiteral("-disabled.png")), QIcon::Disabled);
1927 return icon;
1928 };
1929
1930 QIcon openIcon = getIcon("open-new"_L1);
1931 m_ui.actionOpen->setIcon(openIcon);
1932 m_ui.actionOpenAux->setIcon(openIcon);
1933 QIcon saveIcon = getIcon("save-fl-disk"_L1);
1934 m_ui.actionSave->setIcon(saveIcon);
1935 m_ui.actionSaveAll->setIcon(saveIcon);
1936 m_ui.actionPrint->setIcon(getIcon("print"_L1));
1937 m_ui.actionRedo->setIcon(getIcon("redo-arrow-right"_L1));
1938 m_ui.actionUndo->setIcon(getIcon("undo-arrow-left"_L1));
1939 m_ui.actionCut->setIcon(getIcon("cut"_L1));
1940 m_ui.actionCopy->setIcon(getIcon("copy-general"_L1));
1941 m_ui.actionPaste->setIcon(getIcon("paste-general"_L1));
1942 m_ui.actionFind->setIcon(getIcon("search-magnifier"_L1));
1943
1944 m_ui.actionAccelerators->setIcon(getIcon("/check-ampersands"_L1));
1945 m_ui.actionOpenPhraseBook->setIcon(getIcon("library"_L1));
1946 m_ui.actionDone->setIcon(getIcon("mark-current-translation-done"_L1));
1947 m_ui.actionDoneAndNext->setIcon(getIcon("mark-current-translation-done-move-to-next"_L1));
1948 m_ui.actionNext->setIcon(getIcon("next-translation-item"_L1));
1949 m_ui.actionNextUnfinished->setIcon(getIcon("next-unfinished-translation-item"_L1));
1950 m_ui.actionPhraseMatches->setIcon(getIcon("check-phrase-suggestions"_L1));
1951 m_ui.actionSurroundingWhitespace->setIcon(getIcon("check-white-spaces"_L1));
1952 m_ui.actionEndingPunctuation->setIcon(getIcon("check-ending-pontuation"_L1));
1953 m_ui.actionPrev->setIcon(getIcon("previous-translation-item"_L1));
1954 m_ui.actionPrevUnfinished->setIcon(getIcon("previous-unfinished-translation-item"_L1));
1955 m_ui.actionPlaceMarkerMatches->setIcon(getIcon("check-place-markers"_L1));
1956 m_ui.actionWhatsThis->setIcon(getIcon("hit-help-chosen-option"_L1));
1957}
1958
1959void MainWindow::setupMenuBar()
1960{
1961 m_ui.menuRecentlyOpenedFiles->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::DocumentOpenRecent));
1962 m_ui.actionCloseAll->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::WindowClose));
1963 m_ui.actionExit->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit));
1964 m_ui.actionSelectAll->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::EditSelectAll));
1965 updateIcons();
1966
1967 // File menu
1968 connect(m_ui.menuFile, &QMenu::aboutToShow, this, &MainWindow::fileAboutToShow);
1969 connect(m_ui.actionOpen, &QAction::triggered, this, &MainWindow::open);
1970 connect(m_ui.actionOpenAux, &QAction::triggered, this, &MainWindow::openAux);
1971 connect(m_ui.actionSave, &QAction::triggered, this, &MainWindow::save);
1972#ifndef Q_OS_WASM
1973 connect(m_ui.actionSaveAll, &QAction::triggered, this, &MainWindow::saveAll);
1974 connect(m_ui.actionSaveAs, &QAction::triggered, this, &MainWindow::saveAs);
1975#else
1976 m_ui.actionSaveAs->setVisible(false);
1977 m_ui.actionSaveAll->setVisible(false);
1978#endif // Q_OS_WASM
1979 connect(m_ui.actionReleaseAll, &QAction::triggered, this, &MainWindow::releaseAll);
1980 connect(m_ui.actionRelease, &QAction::triggered, this, &MainWindow::release);
1981 connect(m_ui.actionReleaseAs, &QAction::triggered, this, &MainWindow::releaseAs);
1982#if QT_CONFIG(printsupport)
1983 connect(m_ui.actionPrint, &QAction::triggered, this, &MainWindow::print);
1984#else
1985 m_ui.actionPrint->setEnabled(false);
1986#endif
1987 connect(m_ui.actionClose, &QAction::triggered, this, &MainWindow::closeFile);
1988 connect(m_ui.actionCloseAll, &QAction::triggered, this, &MainWindow::closeAll);
1989 connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close);
1990
1991 // Edit menu
1992 connect(m_ui.menuEdit, &QMenu::aboutToShow, this, &MainWindow::editAboutToShow);
1993
1994 connect(m_ui.actionUndo, &QAction::triggered, m_messageEditor, &MessageEditor::undo);
1995 connect(m_messageEditor, &MessageEditor::undoAvailable, m_ui.actionUndo, &QAction::setEnabled);
1996
1997 connect(m_ui.actionRedo, &QAction::triggered, m_messageEditor, &MessageEditor::redo);
1998 connect(m_messageEditor, &MessageEditor::redoAvailable, m_ui.actionRedo, &QAction::setEnabled);
1999
2000#ifndef QT_NO_CLIPBOARD
2001 connect(m_ui.actionCut, &QAction::triggered, m_messageEditor, &MessageEditor::cut);
2002 connect(m_messageEditor, &MessageEditor::cutAvailable, m_ui.actionCut, &QAction::setEnabled);
2003
2004 connect(m_ui.actionCopy, &QAction::triggered, m_messageEditor, &MessageEditor::copy);
2005 connect(m_messageEditor, &MessageEditor::copyAvailable, m_ui.actionCopy, &QAction::setEnabled);
2006
2007 connect(m_ui.actionPaste, &QAction::triggered, m_messageEditor, &MessageEditor::paste);
2008 connect(m_messageEditor, &MessageEditor::pasteAvailable, m_ui.actionPaste, &QAction::setEnabled);
2009#endif
2010
2011 connect(m_ui.actionSelectAll, &QAction::triggered,
2012 m_messageEditor, &MessageEditor::selectAll);
2013 connect(m_ui.actionFind, &QAction::triggered,
2014 m_findDialog, &FindDialog::find);
2015 connect(m_ui.actionFindNext, &QAction::triggered,
2016 this, [this] {findAgain(FindNext);});
2017 connect(m_ui.actionFindPrev, &QAction::triggered,
2018 this, [this] {findAgain(FindPrev);});
2019 connect(m_ui.actionSearchAndTranslate, &QAction::triggered,
2020 this, &MainWindow::showTranslateDialog);
2021 connect(m_ui.actionBatchTranslation, &QAction::triggered,
2022 this, &MainWindow::showBatchTranslateDialog);
2023 connect(m_ui.actionTranslationFileSettings, &QAction::triggered,
2024 this, &MainWindow::showTranslationSettings);
2025
2026 connect(m_batchTranslateDialog, &BatchTranslationDialog::finished,
2027 this, &MainWindow::refreshItemViews);
2028
2029 // Translation menu
2030 // when updating the accelerators, remember the status bar
2031 connect(m_ui.actionAuto_Translation, &QAction::triggered, this,
2032 &MainWindow::openMachineTranslateDialog);
2033 connect(m_ui.actionPrevUnfinished, &QAction::triggered, this, &MainWindow::prevUnfinished);
2034 connect(m_ui.actionNextUnfinished, &QAction::triggered, this, &MainWindow::nextUnfinished);
2035 connect(m_ui.actionNext, &QAction::triggered, this, &MainWindow::next);
2036 connect(m_ui.actionPrev, &QAction::triggered, this, &MainWindow::prev);
2037 connect(m_ui.actionDone, &QAction::triggered, this, &MainWindow::done);
2038 connect(m_ui.actionDoneAndNext, &QAction::triggered, this, &MainWindow::doneAndNext);
2039 connect(m_ui.actionBeginFromSource, &QAction::triggered, m_messageEditor,
2041
2042 // Phrasebook menu
2043 connect(m_ui.actionNewPhraseBook, &QAction::triggered, this, &MainWindow::newPhraseBook);
2044 connect(m_ui.actionOpenPhraseBook, &QAction::triggered, this, &MainWindow::openPhraseBook);
2045 connect(m_ui.menuClosePhraseBook, &QMenu::triggered,
2046 this, &MainWindow::closePhraseBook);
2047 connect(m_ui.menuEditPhraseBook, &QMenu::triggered,
2048 this, &MainWindow::editPhraseBook);
2049#if QT_CONFIG(printsupport)
2050 connect(m_ui.menuPrintPhraseBook, &QMenu::triggered,
2051 this, &MainWindow::printPhraseBook);
2052#else
2053 m_ui.menuPrintPhraseBook->setEnabled(false);
2054#endif
2055 connect(m_ui.actionAddToPhraseBook, &QAction::triggered,
2056 this, &MainWindow::addToPhraseBook);
2057
2058 // Validation menu
2059 connect(m_ui.actionAccelerators, &QAction::triggered, this, &MainWindow::revalidate);
2060 connect(m_ui.actionSurroundingWhitespace, &QAction::triggered, this, &MainWindow::revalidate);
2061 connect(m_ui.actionEndingPunctuation, &QAction::triggered, this, &MainWindow::revalidate);
2062 connect(m_ui.actionPhraseMatches, &QAction::triggered, this, &MainWindow::revalidate);
2063 connect(m_ui.actionPlaceMarkerMatches, &QAction::triggered, this, &MainWindow::revalidate);
2064
2065 // View menu
2066 connect(m_ui.actionResetSorting, &QAction::triggered,
2067 this, &MainWindow::resetSorting);
2068 connect(m_ui.actionDisplayGuesses, &QAction::triggered,
2069 m_phraseView, &PhraseView::toggleGuessing);
2070 connect(m_ui.actionStatistics, &QAction::triggered, this, &MainWindow::showStatistics);
2071#ifndef Q_OS_WASM
2072 connect(m_ui.actionQmlPreview, &QAction::triggered, this, &MainWindow::toggleQmlPreview);
2073#else
2074 m_ui.actionQmlPreview->setVisible(false);
2075#endif // Q_OS_WASM
2076 connect(m_ui.actionVisualizeWhitespace, &QAction::triggered,
2077 this, &MainWindow::toggleVisualizeWhitespace);
2078 connect(m_ui.actionIncreaseZoom, &QAction::triggered,
2079 m_messageEditor, &MessageEditor::increaseFontSize);
2080 connect(m_ui.actionDecreaseZoom, &QAction::triggered,
2081 m_messageEditor, &MessageEditor::decreaseFontSize);
2082 connect(m_ui.actionResetZoomToDefault, &QAction::triggered,
2083 m_messageEditor, &MessageEditor::resetFontSize);
2084 connect(m_ui.actionShowMoreGuesses, &QAction::triggered,
2085 m_phraseView, &PhraseView::moreGuesses);
2086 connect(m_ui.actionShowFewerGuesses, &QAction::triggered,
2087 m_phraseView, &PhraseView::fewerGuesses);
2088 connect(m_phraseView, &PhraseView::showFewerGuessesAvailable,
2089 m_ui.actionShowFewerGuesses, &QAction::setEnabled);
2090 connect(m_ui.actionResetGuessesToDefault, &QAction::triggered,
2091 m_phraseView, &PhraseView::resetNumGuesses);
2092 m_ui.menuViewViews->addAction(m_contextAndLabelDock->toggleViewAction());
2093 m_ui.menuViewViews->addAction(m_messagesDock->toggleViewAction());
2094 m_ui.menuViewViews->addAction(m_phrasesDock->toggleViewAction());
2095 m_ui.menuViewViews->addAction(m_sourceAndFormDock->toggleViewAction());
2096 m_ui.menuViewViews->addAction(m_errorsDock->toggleViewAction());
2097
2098#if defined(Q_OS_MAC)
2099 // Window menu
2100 QMenu *windowMenu = new QMenu(tr("&Window"), this);
2101 menuBar()->insertMenu(m_ui.menuHelp->menuAction(), windowMenu);
2102 windowMenu->addAction(tr("Minimize"), QKeySequence(tr("Ctrl+M")),
2103 this, &QWidget::showMinimized);
2104#endif
2105
2106 // Help
2107 connect(m_ui.actionManual, &QAction::triggered, this, &MainWindow::manual);
2108 connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::about);
2109 connect(m_ui.actionAboutQt, &QAction::triggered, this, &MainWindow::aboutQt);
2110 connect(m_ui.actionWhatsThis, &QAction::triggered, this, &MainWindow::onWhatsThis);
2111
2112 connect(m_ui.menuRecentlyOpenedFiles, &QMenu::triggered,
2113 this, &MainWindow::recentFileActivated);
2114
2115 m_ui.actionManual->setToolTip(tr("Displays the manual for %1.").arg(tr("Qt Linguist")));
2116 m_ui.actionAbout->setToolTip(tr("Displays information about %1.").arg(tr("Qt Linguist")));
2117 m_ui.actionDone->setShortcuts(
2118 { Qt::AltModifier | Qt::Key_Return, Qt::AltModifier | Qt::Key_Enter });
2119 m_ui.actionDoneAndNext->setShortcuts({
2120 Qt::ControlModifier | Qt::Key_Return,
2121 Qt::ControlModifier | Qt::Key_Enter,
2122 });
2123
2124 // Disable the Close/Edit/Print phrasebook menuitems if they are not loaded
2125 connect(m_ui.menuPhrases, &QMenu::aboutToShow, this, &MainWindow::setupPhrase);
2126
2127 connect(m_ui.menuRecentlyOpenedFiles, &QMenu::aboutToShow,
2128 this, &MainWindow::setupRecentFilesMenu);
2129}
2130
2131void MainWindow::updateActiveModel(int model)
2132{
2133 if (model >= 0)
2134 doUpdateLatestModel(model);
2135}
2136
2137// Arriving here implies that the messageEditor does not have focus
2138void MainWindow::updateLatestModel(const QModelIndex &index)
2139{
2140 if (index.column() && (index.column() - 1 < m_dataModel->modelCount()))
2141 doUpdateLatestModel(index.column() - 1);
2142}
2143
2144void MainWindow::doUpdateLatestModel(int model)
2145{
2146 m_currentIndex = MultiDataIndex(m_currentIndex.translationType(), model, m_currentIndex.group(),
2147 m_currentIndex.message());
2148 bool enable = false;
2149 bool enableRw = false;
2150 MessageItem *item = nullptr;
2151 if (model >= 0) {
2152 enable = true;
2153 if (m_dataModel->isModelWritable(model))
2154 enableRw = true;
2155 if (m_currentIndex.isValid()) {
2156 if ((item = m_dataModel->messageItem(m_currentIndex))) {
2157 if (enableRw && !item->isObsolete())
2158 m_phraseView->setSourceText(model, item->text());
2159 else
2160 m_phraseView->setSourceText(-1, QString());
2161 } else {
2162 m_phraseView->setSourceText(-1, QString());
2163 }
2164 }
2165 }
2166 updateSourceView(model, item);
2167 m_ui.actionSave->setEnabled(enableRw);
2168 m_ui.actionSaveAs->setEnabled(enableRw);
2169 m_ui.actionRelease->setEnabled(enableRw);
2170 m_ui.actionReleaseAs->setEnabled(enableRw);
2171 m_ui.actionClose->setEnabled(enable);
2172 m_ui.actionTranslationFileSettings->setEnabled(enableRw);
2173 m_ui.actionSearchAndTranslate->setEnabled(enableRw);
2174 // cut & paste - edit only
2175 updatePhraseBookActions();
2176 updateStatistics();
2177}
2178
2179void MainWindow::updateSourceView(int model, MessageItem *item)
2180{
2181 if (item && !item->fileName().isEmpty()) {
2182 if (hasUiFormPreview(item->fileName())) {
2183 m_sourceAndFormView->setCurrentWidget(m_uiFormPreviewView);
2184 m_uiFormPreviewView->setSourceContext(model, item);
2185#ifndef Q_OS_WASM
2186 } else if (hasQmlFormPreview(item->fileName(), m_ui.actionQmlPreview->isChecked())
2187 && m_qmlFormPreviewView->setSourceContext(model, item)) {
2188 m_sourceAndFormView->setCurrentWidget(m_qmlFormPreviewView);
2189#endif // Q_OS_WASM
2190 } else {
2191#ifndef Q_OS_WASM
2192 m_ui.actionQmlPreview->setChecked(false);
2193#endif // Q_OS_WASM
2194 m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2195 QDir dir = QFileInfo(m_dataModel->srcFileName(model)).dir();
2196 QString fileName = QDir::cleanPath(dir.absoluteFilePath(item->fileName()));
2197 m_sourceCodeView->setSourceContext(fileName, item->lineNumber());
2198 }
2199 } else {
2200 m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2201 m_sourceCodeView->setSourceContext(QString(), 0);
2202 }
2203}
2204
2205// Note for *AboutToShow: Due to the delayed nature, only actions without shortcuts
2206// and representations outside the menu may be setEnabled()/setVisible() here.
2207
2208void MainWindow::fileAboutToShow()
2209{
2210 if (m_fileActiveModel != m_currentIndex.model()) {
2211 // We rename the actions so the shortcuts need not be reassigned.
2212 bool en;
2213 if (m_dataModel->modelCount() > 1) {
2214 if (m_currentIndex.model() >= 0) {
2215 QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
2216#ifndef Q_OS_WASM
2217 m_ui.actionSave->setText(tr("&Save '%1'").arg(fn));
2218 m_ui.actionSaveAs->setText(tr("Save '%1' &As...").arg(fn));
2219#else
2220 m_ui.actionSave->setText(tr("&Download '%1'").arg(fn));
2221#endif // Q_OS_WASM
2222 m_ui.actionRelease->setText(tr("Release '%1'").arg(fn));
2223 m_ui.actionReleaseAs->setText(tr("Release '%1' As...").arg(fn));
2224 m_ui.actionClose->setText(tr("&Close '%1'").arg(fn));
2225 } else {
2226#ifndef Q_OS_WASM
2227 m_ui.actionSave->setText(tr("&Save"));
2228 m_ui.actionSaveAs->setText(tr("Save &As..."));
2229#else
2230 m_ui.actionSave->setText(tr("&Download"));
2231#endif // Q_OS_WASM
2232 m_ui.actionRelease->setText(tr("Release"));
2233 m_ui.actionReleaseAs->setText(tr("Release As..."));
2234 m_ui.actionClose->setText(tr("&Close"));
2235 }
2236
2237#ifndef Q_OS_WASM
2238 m_ui.actionSaveAll->setText(tr("Save All"));
2239#endif // Q_OS_WASM
2240 m_ui.actionReleaseAll->setText(tr("&Release All"));
2241 m_ui.actionCloseAll->setText(tr("Close All"));
2242 en = true;
2243 } else {
2244#ifndef Q_OS_WASM
2245 m_ui.actionSaveAs->setText(tr("Save &As..."));
2246 m_ui.actionSaveAll->setText(tr("&Save"));
2247#else
2248 m_ui.actionSave->setText(tr("&Download"));
2249#endif // Q_OS_WASM
2250 m_ui.actionReleaseAs->setText(tr("Release As..."));
2251 m_ui.actionReleaseAll->setText(tr("&Release"));
2252 m_ui.actionCloseAll->setText(tr("&Close"));
2253 en = false;
2254 }
2255#ifndef Q_OS_WASM
2256 m_ui.actionSave->setVisible(en);
2257#endif // Q_OS_WASM
2258 m_ui.actionRelease->setVisible(en);
2259 m_ui.actionClose->setVisible(en);
2260 m_fileActiveModel = m_currentIndex.model();
2261 }
2262}
2263
2264void MainWindow::editAboutToShow()
2265{
2266 if (m_editActiveModel != m_currentIndex.model()) {
2267 if (m_currentIndex.model() >= 0 && m_dataModel->modelCount() > 1) {
2268 QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
2269 m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings for '%1'...").arg(fn));
2270 m_ui.actionBatchTranslation->setText(tr("&Batch Translation of '%1'...").arg(fn));
2271 m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate in '%1'...").arg(fn));
2272 } else {
2273 m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings..."));
2274 m_ui.actionBatchTranslation->setText(tr("&Batch Translation..."));
2275 m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate..."));
2276 }
2277 m_editActiveModel = m_currentIndex.model();
2278 }
2279}
2280
2281void MainWindow::showContextDock()
2282{
2283 m_contextAndLabelDock->show();
2284 m_contextAndLabelDock->raise();
2285}
2286
2287void MainWindow::showMessagesDock()
2288{
2289 m_messagesDock->show();
2290 m_messagesDock->raise();
2291}
2292
2293void MainWindow::showPhrasesDock()
2294{
2295 m_phrasesDock->show();
2296 m_phrasesDock->raise();
2297}
2298
2299void MainWindow::showSourceCodeDock()
2300{
2301 m_sourceAndFormDock->show();
2302 m_sourceAndFormDock->raise();
2303}
2304
2305void MainWindow::showErrorDock()
2306{
2307 m_errorsDock->show();
2308 m_errorsDock->raise();
2309}
2310
2311void MainWindow::onWhatsThis()
2312{
2313 QWhatsThis::enterWhatsThisMode();
2314}
2315
2316void MainWindow::setupToolBars()
2317{
2318 QToolBar *filet = new QToolBar(this);
2319 filet->setObjectName("FileToolbar");
2320 filet->setWindowTitle(tr("File"));
2321 this->addToolBar(filet);
2322 m_ui.menuToolbars->addAction(filet->toggleViewAction());
2323
2324 QToolBar *editt = new QToolBar(this);
2325 editt->setVisible(false);
2326 editt->setObjectName("EditToolbar");
2327 editt->setWindowTitle(tr("Edit"));
2328 this->addToolBar(editt);
2329 m_ui.menuToolbars->addAction(editt->toggleViewAction());
2330
2331 QToolBar *translationst = new QToolBar(this);
2332 translationst->setObjectName("TranslationToolbar");
2333 translationst->setWindowTitle(tr("Translation"));
2334 this->addToolBar(translationst);
2335 m_ui.menuToolbars->addAction(translationst->toggleViewAction());
2336
2337 QToolBar *validationt = new QToolBar(this);
2338 validationt->setObjectName("ValidationToolbar");
2339 validationt->setWindowTitle(tr("Validation"));
2340 this->addToolBar(validationt);
2341 m_ui.menuToolbars->addAction(validationt->toggleViewAction());
2342
2343 QToolBar *helpt = new QToolBar(this);
2344 helpt->setVisible(false);
2345 helpt->setObjectName("HelpToolbar");
2346 helpt->setWindowTitle(tr("Help"));
2347 this->addToolBar(helpt);
2348 m_ui.menuToolbars->addAction(helpt->toggleViewAction());
2349
2350
2351 filet->addAction(m_ui.actionOpen);
2352 filet->addAction(m_ui.actionSaveAll);
2353 filet->addAction(m_ui.actionPrint);
2354 filet->addSeparator();
2355 filet->addAction(m_ui.actionOpenPhraseBook);
2356
2357 editt->addAction(m_ui.actionUndo);
2358 editt->addAction(m_ui.actionRedo);
2359 editt->addSeparator();
2360 editt->addAction(m_ui.actionCut);
2361 editt->addAction(m_ui.actionCopy);
2362 editt->addAction(m_ui.actionPaste);
2363 editt->addSeparator();
2364 editt->addAction(m_ui.actionFind);
2365
2366 translationst->addAction(m_ui.actionPrev);
2367 translationst->addAction(m_ui.actionNext);
2368 translationst->addAction(m_ui.actionPrevUnfinished);
2369 translationst->addAction(m_ui.actionNextUnfinished);
2370 translationst->addAction(m_ui.actionDone);
2371 translationst->addAction(m_ui.actionDoneAndNext);
2372
2373 validationt->addAction(m_ui.actionAccelerators);
2374 validationt->addAction(m_ui.actionSurroundingWhitespace);
2375 validationt->addAction(m_ui.actionEndingPunctuation);
2376 validationt->addAction(m_ui.actionPhraseMatches);
2377 validationt->addAction(m_ui.actionPlaceMarkerMatches);
2378
2379 helpt->addAction(m_ui.actionWhatsThis);
2380}
2381
2382QModelIndex MainWindow::setMessageViewRoot(const QModelIndex &index)
2383{
2384 const QModelIndex &sortedGroupIndex = m_activeSortedMessagesModel->mapFromSource(index);
2385 const QModelIndex &trueGroupIndex =
2386 m_activeSortedMessagesModel->index(sortedGroupIndex.row(), 0);
2387 if (m_messageView->rootIndex() != trueGroupIndex)
2388 m_messageView->setRootIndex(trueGroupIndex);
2389 return trueGroupIndex;
2390}
2391
2392/*
2393 * Updates the selected entries in the context and message views.
2394 */
2395void MainWindow::setCurrentMessage(const QModelIndex &index)
2396{
2397 const QModelIndex &groupIndex = m_activeMessageModel->parent(index);
2398 if (!groupIndex.isValid())
2399 return;
2400
2401 const QModelIndex &trueIndex =
2402 m_activeMessageModel->index(groupIndex.row(), index.column(), QModelIndex());
2403 m_settingCurrentMessage = true;
2404 QTreeView *view = *m_activeTranslationType == IDBASED ? m_labelView : m_contextView;
2405 view->setCurrentIndex(m_activeSortedGroupsModel->mapFromSource(trueIndex));
2406 m_settingCurrentMessage = false;
2407 setMessageViewRoot(groupIndex);
2408 m_messageView->setCurrentIndex(m_activeSortedMessagesModel->mapFromSource(index));
2409}
2410
2411void MainWindow::setCurrentMessage(const QModelIndex &index, int model)
2412{
2413 const QModelIndex &theIndex =
2414 m_activeMessageModel->index(index.row(), model + 1, index.parent());
2415 setCurrentMessage(theIndex);
2416 m_messageEditor->setEditorFocusForModel(model);
2417}
2418
2419void MainWindow::setCurrentMessageFromGuess(int modelIndex, const Candidate &cand)
2420{
2421 if (cand.context.isEmpty()) {
2422 int labelIndex = m_dataModel->findGroupIndex(cand.label, IDBASED);
2423 int messageIndex =
2424 m_dataModel->multiGroupItem(labelIndex, IDBASED)->findMessageById(cand.id);
2425 setCurrentMessage(m_activeMessageModel->modelIndex(
2426 MultiDataIndex(IDBASED, modelIndex, labelIndex, messageIndex)));
2427 } else {
2428 int contextIndex = m_dataModel->findGroupIndex(cand.context, TEXTBASED);
2429 int messageIndex = m_dataModel->multiGroupItem(contextIndex, TEXTBASED)
2430 ->findMessage(cand.source, cand.disambiguation);
2431 setCurrentMessage(m_activeMessageModel->modelIndex(
2432 MultiDataIndex(TEXTBASED, modelIndex, contextIndex, messageIndex)));
2433 }
2434}
2435
2436void MainWindow::contextAndLabelTabChanged()
2437{
2438 auto refreshMessageView = [this](QTreeView *view) {
2439 m_messageView->reset();
2440 m_messageView->setModel(m_activeSortedMessagesModel);
2441 view->setCurrentIndex(m_activeSortedGroupsModel->index(0, 0));
2442 connect(m_messageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
2443 &MainWindow::selectedMessageChanged);
2444 connect(m_messageView->selectionModel(), &QItemSelectionModel::currentColumnChanged, this,
2445 &MainWindow::updateLatestModel);
2446 m_messageView->update();
2447 if (m_activeMessageModel->rowCount())
2448 setCurrentMessage(m_activeMessageModel->modelIndex(
2449 MultiDataIndex(*m_activeTranslationType, 0, 0, 0)));
2450 selectedMessageChanged(m_messageView->currentIndex(), QModelIndex{});
2451 updateVisibleColumns();
2452 };
2453
2454 if (m_contextAndLabelView->currentWidget() == m_labelView
2455 && (!m_activeTranslationType || *m_activeTranslationType != IDBASED)) {
2456 m_activeTranslationType.emplace(IDBASED);
2457 m_activeSortedMessagesModel = m_sortedIdBasedMessagesModel;
2458 m_activeSortedGroupsModel = m_sortedLabelsModel;
2459 m_activeMessageModel = m_idBasedMessageModel;
2460 refreshMessageView(m_labelView);
2461 } else if (m_contextAndLabelView->currentWidget() == m_contextView
2462 && (!m_activeTranslationType || *m_activeTranslationType != TEXTBASED)) {
2463 m_activeTranslationType.emplace(TEXTBASED);
2464 m_activeSortedMessagesModel = m_sortedTextBasedMessagesModel;
2465 m_activeSortedGroupsModel = m_sortedContextsModel;
2466 m_activeMessageModel = m_textBasedMessageModel;
2467 refreshMessageView(m_contextView);
2468 }
2469}
2470
2471void MainWindow::updateVisibleColumns()
2472{
2473 int cols = m_dataModel->modelCount() + 2;
2474 if (*m_activeTranslationType == IDBASED)
2475 cols++;
2476 for (int i = 1; i < cols; i++)
2477 m_messageView->setColumnHidden(i, false);
2478 for (int i = cols; i < m_messageView->header()->count(); i++)
2479 m_messageView->setColumnHidden(i, true);
2480 m_messageView->header()->setStretchLastSection(true);
2481}
2482
2483QModelIndex MainWindow::currentMessageIndex() const
2484{
2485 return m_activeSortedMessagesModel->mapToSource(m_messageView->currentIndex());
2486}
2487
2488PhraseBook *MainWindow::doOpenPhraseBook(const QString& name)
2489{
2490 PhraseBook *pb = new PhraseBook();
2491 bool langGuessed;
2492 if (!pb->load(name, &langGuessed)) {
2493 QMessageBox::warning(this, tr("Qt Linguist"),
2494 tr("Cannot read from phrase book '%1'.").arg(name));
2495 delete pb;
2496 return 0;
2497 }
2498 if (langGuessed) {
2499 if (!m_translationSettingsDialog)
2500 m_translationSettingsDialog = new TranslationSettingsDialog(this);
2501 m_translationSettingsDialog->setPhraseBook(pb);
2502 m_translationSettingsDialog->exec();
2503 }
2504
2505 m_phraseBooks.append(pb);
2506
2507 QAction *a = m_ui.menuClosePhraseBook->addAction(pb->friendlyPhraseBookName());
2508 m_phraseBookMenu[PhraseCloseMenu].insert(a, pb);
2509 a->setToolTip(tr("Close this phrase book."));
2510
2511 a = m_ui.menuEditPhraseBook->addAction(pb->friendlyPhraseBookName());
2512 m_phraseBookMenu[PhraseEditMenu].insert(a, pb);
2513 a->setToolTip(tr("Enables you to add, modify, or delete"
2514 " entries in this phrase book."));
2515
2516 a = m_ui.menuPrintPhraseBook->addAction(pb->friendlyPhraseBookName());
2517 m_phraseBookMenu[PhrasePrintMenu].insert(a, pb);
2518 a->setToolTip(tr("Print the entries in this phrase book."));
2519
2520 connect(pb, &PhraseBook::listChanged, this, &MainWindow::updatePhraseDicts);
2521 updatePhraseDicts();
2522 updatePhraseBookActions();
2523
2524 return pb;
2525}
2526
2527bool MainWindow::savePhraseBook(QString *name, PhraseBook &pb)
2528{
2529 if (!name->contains(u'.'))
2530 *name += ".qph"_L1;
2531
2532 if (!pb.save(*name)) {
2533 QMessageBox::warning(this, tr("Qt Linguist"),
2534 tr("Cannot create phrase book '%1'.").arg(*name));
2535 return false;
2536 }
2537 return true;
2538}
2539
2540bool MainWindow::maybeSavePhraseBook(PhraseBook *pb)
2541{
2542 if (pb->isModified())
2543 switch (QMessageBox::information(this, tr("Qt Linguist"),
2544 tr("Do you want to save phrase book '%1'?").arg(pb->friendlyPhraseBookName()),
2545 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
2546 {
2547 case QMessageBox::Cancel:
2548 return false;
2549 case QMessageBox::Yes:
2550 if (!pb->save(pb->fileName()))
2551 return false;
2552 break;
2553 default:
2554 break;
2555 }
2556 return true;
2557}
2558
2559bool MainWindow::maybeSavePhraseBooks()
2560{
2561 for (PhraseBook *phraseBook : std::as_const(m_phraseBooks))
2562 if (!maybeSavePhraseBook(phraseBook))
2563 return false;
2564 return true;
2565}
2566
2567void MainWindow::updateProgress()
2568{
2569 int numEditable = m_dataModel->getNumEditable();
2570 int numFinished = m_dataModel->getNumFinished();
2571 if (!m_dataModel->modelCount()) {
2572 m_progressLabel->setText(QString(" "_L1));
2573 m_progressLabel->setToolTip(QString());
2574 } else {
2575 m_progressLabel->setText(QStringLiteral(" %1/%2 ").arg(numFinished).arg(numEditable));
2576 m_progressLabel->setToolTip(tr("%n unfinished message(s) left.", 0,
2577 numEditable - numFinished));
2578 }
2579 bool enable = numFinished != numEditable;
2580 m_ui.actionPrevUnfinished->setEnabled(enable);
2581 m_ui.actionNextUnfinished->setEnabled(enable);
2582 m_ui.actionDone->setEnabled(enable);
2583 m_ui.actionDoneAndNext->setEnabled(enable);
2584
2585 m_ui.actionPrev->setEnabled(m_dataModel->contextCount() > 0 || m_dataModel->labelCount() > 0);
2586 m_ui.actionNext->setEnabled(m_dataModel->contextCount() > 0 || m_dataModel->labelCount() > 0);
2587}
2588
2589void MainWindow::updatePhraseBookActions()
2590{
2591 bool phraseBookLoaded = (m_currentIndex.model() >= 0) && !m_phraseBooks.isEmpty();
2592 m_ui.actionBatchTranslation->setEnabled(m_dataModel->contextCount() > 0 && phraseBookLoaded
2593 && m_dataModel->isModelWritable(m_currentIndex.model()));
2594 m_ui.actionAddToPhraseBook->setEnabled(currentMessageIndex().isValid() && phraseBookLoaded);
2595}
2596
2597void MainWindow::updatePhraseDictInternal(int model)
2598{
2599 QHash<QString, QList<Phrase *> > &pd = m_phraseDict[model];
2600
2601 pd.clear();
2602 for (PhraseBook *pb : std::as_const(m_phraseBooks)) {
2603 bool before;
2604 if (pb->language() != QLocale::C && m_dataModel->language(model) != QLocale::C) {
2605 if (pb->language() != m_dataModel->language(model))
2606 continue;
2607 before = (pb->territory() == m_dataModel->model(model)->territory());
2608 } else {
2609 before = false;
2610 }
2611 const auto phrases = pb->phrases();
2612 for (Phrase *p : phrases) {
2613 QString f = friendlyString(p->source());
2614 if (f.size() > 0) {
2615 f = f.split(u' ').first();
2616 if (!pd.contains(f)) {
2617 pd.insert(f, QList<Phrase *>());
2618 }
2619 if (before)
2620 pd[f].prepend(p);
2621 else
2622 pd[f].append(p);
2623 }
2624 }
2625 }
2626}
2627
2628void MainWindow::updatePhraseDict(int model)
2629{
2630 updatePhraseDictInternal(model);
2631 m_phraseView->update();
2632}
2633
2634void MainWindow::updatePhraseDicts()
2635{
2636 for (int i = 0; i < m_phraseDict.size(); ++i)
2637 if (!m_dataModel->isModelWritable(i))
2638 m_phraseDict[i].clear();
2639 else
2640 updatePhraseDictInternal(i);
2641 revalidate();
2642 m_phraseView->update();
2643}
2644
2645void MainWindow::updateDanger(const MultiDataIndex &index, bool verbose)
2646{
2647 MultiDataIndex curIdx = index;
2648 m_errorsView->clear();
2649
2650 QString source;
2651
2652 Validator::Checks checks{ m_ui.actionAccelerators->isChecked(),
2653 m_ui.actionEndingPunctuation->isChecked(),
2654 m_ui.actionPlaceMarkerMatches->isChecked(),
2655 m_ui.actionSurroundingWhitespace->isChecked(),
2656 m_ui.actionPhraseMatches->isChecked() };
2657
2658 for (int mi = 0; mi < m_dataModel->modelCount(); ++mi) {
2659 if (!m_dataModel->isModelWritable(mi))
2660 continue;
2661 curIdx.setModel(mi);
2662 MessageItem *m = m_dataModel->messageItem(curIdx);
2663 if (!m || m->isObsolete())
2664 continue;
2665
2666 bool danger = false;
2668 if (source.isEmpty()) {
2669 source = m->pluralText();
2670 if (source.isEmpty())
2671 source = m->text();
2672 }
2673
2674 Validator validator = Validator::fromSource(
2675 source, checks, m_dataModel->sourceLanguage(mi), m_phraseDict[mi]);
2676 const auto errors =
2677 validator.validate(m->translations(), m->message(), m_dataModel->language(mi),
2678 m_dataModel->model(mi)->countRefNeeds());
2679 if (verbose)
2680 for (const auto &[error, message] : errors.asKeyValueRange())
2681 m_errorsView->addError(mi, error, message);
2682 }
2683
2684 if (danger != m->danger())
2685 m_dataModel->setDanger(curIdx, danger);
2686 }
2687
2688 if (verbose)
2689 statusBar()->showMessage(m_errorsView->firstError());
2690}
2691
2693{
2694 QSettings config;
2695
2696 restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray());
2697 restoreState(config.value(settingPath("MainWindowState")).toByteArray());
2698
2699 m_ui.actionAccelerators->setChecked(
2700 config.value(settingPath("Validators/Accelerator"), true).toBool());
2701 m_ui.actionSurroundingWhitespace->setChecked(
2702 config.value(settingPath("Validators/SurroundingWhitespace"), true).toBool());
2703 m_ui.actionEndingPunctuation->setChecked(
2704 config.value(settingPath("Validators/EndingPunctuation"), true).toBool());
2705 m_ui.actionPhraseMatches->setChecked(
2706 config.value(settingPath("Validators/PhraseMatch"), true).toBool());
2707 m_ui.actionPlaceMarkerMatches->setChecked(
2708 config.value(settingPath("Validators/PlaceMarkers"), true).toBool());
2709 m_ui.actionLengthVariants->setChecked(
2710 config.value(settingPath("Options/LengthVariants"), false).toBool());
2711 m_ui.actionVisualizeWhitespace->setChecked(
2712 config.value(settingPath("Options/VisualizeWhitespace"), true).toBool());
2713
2714 m_messageEditor->setFontSize(
2715 config.value(settingPath("Options/EditorFontsize"), font().pointSize()).toReal());
2716 m_phraseView->setMaxCandidates(config.value(settingPath("Options/NumberOfGuesses"),
2718
2719 m_recentFiles.readConfig();
2720
2721 int size = config.beginReadArray(settingPath("OpenedPhraseBooks"));
2722 for (int i = 0; i < size; ++i) {
2723 config.setArrayIndex(i);
2724 doOpenPhraseBook(config.value("FileName"_L1).toString());
2725 }
2726 config.endArray();
2727}
2728
2730{
2731 QSettings config;
2732 config.setValue(settingPath("Geometry/WindowGeometry"),
2733 saveGeometry());
2734 config.setValue(settingPath("Validators/Accelerator"),
2735 m_ui.actionAccelerators->isChecked());
2736 config.setValue(settingPath("Validators/SurroundingWhitespace"),
2737 m_ui.actionSurroundingWhitespace->isChecked());
2738 config.setValue(settingPath("Validators/EndingPunctuation"),
2739 m_ui.actionEndingPunctuation->isChecked());
2740 config.setValue(settingPath("Validators/PhraseMatch"),
2741 m_ui.actionPhraseMatches->isChecked());
2742 config.setValue(settingPath("Validators/PlaceMarkers"),
2743 m_ui.actionPlaceMarkerMatches->isChecked());
2744 config.setValue(settingPath("Options/LengthVariants"),
2745 m_ui.actionLengthVariants->isChecked());
2746 config.setValue(settingPath("Options/VisualizeWhitespace"),
2747 m_ui.actionVisualizeWhitespace->isChecked());
2748 config.setValue(settingPath("MainWindowState"),
2749 saveState());
2750 m_recentFiles.writeConfig();
2751
2752 config.setValue(settingPath("Options/EditorFontsize"), m_messageEditor->fontSize());
2753 config.setValue(settingPath("Options/NumberOfGuesses"), m_phraseView->getMaxCandidates());
2754
2755 config.beginWriteArray(settingPath("OpenedPhraseBooks"),
2756 m_phraseBooks.size());
2757 for (int i = 0; i < m_phraseBooks.size(); ++i) {
2758 config.setArrayIndex(i);
2759 config.setValue("FileName"_L1, m_phraseBooks.at(i)->fileName());
2760 }
2761 config.endArray();
2762}
2763
2764void MainWindow::setupRecentFilesMenu()
2765{
2766 m_ui.menuRecentlyOpenedFiles->clear();
2767 for (const QStringList &strList : m_recentFiles.filesLists())
2768 if (strList.size() == 1) {
2769 const QString &str = strList.first();
2770 m_ui.menuRecentlyOpenedFiles->addAction(
2771 DataModel::prettifyFileName(str))->setData(str);
2772 } else {
2773 QMenu *menu = m_ui.menuRecentlyOpenedFiles->addMenu(
2774 MultiDataModel::condenseFileNames(
2775 MultiDataModel::prettifyFileNames(strList)));
2776 menu->addAction(tr("All"))->setData(strList);
2777 for (const QString &str : strList)
2778 menu->addAction(DataModel::prettifyFileName(str))->setData(str);
2779 }
2780}
2781
2782void MainWindow::recentFileActivated(QAction *action)
2783{
2784 openFiles(action->data().toStringList());
2785}
2786
2787void MainWindow::showStatistics()
2788{
2789 if (!m_statistics) {
2790 m_statistics = new Statistics(this);
2791 connect(m_dataModel, &MultiDataModel::statsChanged, m_statistics, &Statistics::updateStats);
2792 }
2793 m_statistics->show();
2794 updateStatistics();
2795}
2796
2797#ifndef Q_OS_WASM
2798void MainWindow::toggleQmlPreview()
2799{
2800 if (m_ui.actionQmlPreview->isChecked())
2801 m_sourceAndFormView->setCurrentWidget(m_qmlFormPreviewView);
2802 else
2803 m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2804}
2805#endif // Q_OS_WASM
2806
2807void MainWindow::toggleVisualizeWhitespace()
2808{
2809 m_messageEditor->setVisualizeWhitespace(m_ui.actionVisualizeWhitespace->isChecked());
2810}
2811
2812void MainWindow::maybeUpdateStatistics(const MultiDataIndex &index)
2813{
2814 if (index.model() == m_currentIndex.model())
2815 updateStatistics();
2816}
2817
2818void MainWindow::updateStatistics()
2819{
2820 // don't call this if stats dialog is not open
2821 // because this can be slow...
2822 if (!m_statistics || !m_statistics->isVisible() || m_currentIndex.model() < 0)
2823 return;
2824
2825 m_dataModel->model(m_currentIndex.model())->updateStatistics();
2826}
2827
2828void MainWindow::doShowTranslationSettings(int model)
2829{
2830 if (!m_translationSettingsDialog)
2831 m_translationSettingsDialog = new TranslationSettingsDialog(this);
2832 m_translationSettingsDialog->setDataModel(m_dataModel->model(model));
2833 m_translationSettingsDialog->exec();
2834}
2835
2836void MainWindow::showTranslationSettings()
2837{
2838 doShowTranslationSettings(m_currentIndex.model());
2839}
2840
2841bool MainWindow::eventFilter(QObject *object, QEvent *event)
2842{
2843 if (event->type() == QEvent::DragEnter) {
2844 QDragEnterEvent *e = static_cast<QDragEnterEvent*>(event);
2845 if (e->mimeData()->hasFormat("text/uri-list"_L1)) {
2846 e->acceptProposedAction();
2847 return true;
2848 }
2849 } else if (event->type() == QEvent::Drop) {
2850 QDropEvent *e = static_cast<QDropEvent*>(event);
2851 if (!e->mimeData()->hasFormat("text/uri-list"_L1))
2852 return false;
2853 QStringList urls;
2854 const auto &qurls = e->mimeData()->urls();
2855 for (const QUrl &url : qurls)
2856 if (!url.toLocalFile().isEmpty())
2857 urls << url.toLocalFile();
2858 if (!urls.isEmpty())
2859 openFiles(urls);
2860 e->acceptProposedAction();
2861 return true;
2862 } else if (event->type() == QEvent::KeyPress) {
2863 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
2864 if (ke->key() == Qt::Key_Escape) {
2865 if (object == m_messageEditor)
2866 m_messageView->setFocus();
2867 else if (object == m_messagesDock)
2868 m_contextAndLabelView->currentWidget()->setFocus();
2869 } else if ((ke->key() == Qt::Key_Plus || ke->key() == Qt::Key_Equal)
2870 && (ke->modifiers() & Qt::ControlModifier)) {
2871 m_messageEditor->increaseFontSize();
2872 } else if (ke->key() == Qt::Key_Minus
2873 && (ke->modifiers() & Qt::ControlModifier)) {
2874 m_messageEditor->decreaseFontSize();
2875 }
2876 } else if (event->type() == QEvent::Wheel) {
2877 QWheelEvent *we = static_cast<QWheelEvent *>(event);
2878 if (we->modifiers() & Qt::ControlModifier) {
2879 if (we->angleDelta().y() > 0)
2880 m_messageEditor->increaseFontSize();
2881 else
2882 m_messageEditor->decreaseFontSize();
2883 }
2884 } else if (event->type() == QEvent::ApplicationPaletteChange) {
2885 m_dataModel->updateColors();
2886 updateIcons();
2887 }
2888 return QMainWindow::eventFilter(object, event);
2889}
2890
2891QT_END_NAMESPACE
int message() const
TranslationType translationType() const
int group() const
bool isValid() const
void updateStatistics()
void clear()
FocusWatcher(MessageEditor *msgedit, QObject *parent)
bool eventFilter(QObject *object, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
void setDataModel(MultiDataModel *dm)
~MainWindow() override
MainWindow(HelpClientType helpClientType)
void writeConfig()
void closeEvent(QCloseEvent *e) override
This event handler is called with the given event when Qt receives a window close request for a top-l...
bool openFiles(const QStringList &names)
bool eventFilter(QObject *obj, QEvent *ev) override
Filters events if this object has been installed as an event filter for the watched object.
void readConfig()
void setLengthVariants(bool on)
int activeModel() const
void redoAvailable(bool avail)
void cutAvailable(bool avail)
void undoAvailable(bool avail)
void setVisualizeWhitespace(bool value)
void setEditorFocusForModel(int model)
void pasteAvailable(bool avail)
void setFontSize(const float fontSize)
void setUnfinishedEditorFocus()
bool focusNextUnfinished()
void activeModelChanged(int model)
void copyAvailable(bool avail)
void showMessage(const MultiDataIndex &index)
bool danger() const
bool isFinished() const
const TranslatorMessage & message() const
TranslatorMessage::Type type() const
int lineNumber() const
bool isObsolete() const
void setModel(int model)
int model() const
MultiDataIndex(TranslationType type=TEXTBASED, int model=-1, int group=-1, int message=-1)
MultiDataModelIterator(TranslationType type, MultiDataModel *model=0, int modelNo=-1, int groupNo=0, int messageNo=0)
MessageItem * current() const
bool isModified(int model) const
int labelCount() const
MessageItem * messageItem(const MultiDataIndex &index, int model) const
void setModified(int model, bool dirty)
MultiGroupItem * multiGroupItem(const MultiDataIndex &index) const
DataModel * model(int i)
bool isModified() const
MultiGroupItem * multiGroupItem(int idx, TranslationType type) const
void languageChanged(int model)
int getNumFinished() const
void setDanger(const MultiDataIndex &index, bool danger)
int modelCount() const
bool isModelWritable(int model) const
MultiMessageItem * multiMessageItem(const MultiDataIndex &index) const
int getNumEditable() const
int contextCount() const
void modifiedChanged(bool)
void translationChanged(const MultiDataIndex &index)
void statsChanged(const StatisticalData &newStats)
void multiGroupDataChanged(const MultiDataIndex &index)
MessageItem * messageItem(const MultiDataIndex &index) const
void setFinished(const MultiDataIndex &index, bool finished)
void messageDataChanged(const MultiDataIndex &index)
void close(int model)
bool isModified() const
Definition phrase.h:68
void listChanged()
void resetNumGuesses()
void setMaxCandidates(const int max)
int getMaxCandidates() const
Definition phraseview.h:30
void update()
static int getDefaultMaxCandidates()
Definition phraseview.h:32
void showFewerGuessesAvailable(bool canShow)
void moreGuesses()
void fewerGuesses()
void setCurrentMessageFromGuess(int modelIndex, const Candidate &cand)
Definition qlist.h:81
bool setSourceContext(int model, MessageItem *messageItem)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
\reimp
SortedGroupsModel(QObject *parent, MultiDataModel *model, TranslationType translationType)
SortedMessagesModel(QObject *parent, MultiDataModel *model, TranslationType translationType)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
\reimp
void setPhraseBook(PhraseBook *phraseBook)
bool isTranslated() const
void setSourceContext(int model, MessageItem *messageItem)
HelpClientType
Definition helpclient.h:16
TranslationType
@ IDBASED
@ TEXTBASED
static QString fileFilters(bool allFirst)
static const QVariant & pxObsolete()
int firstNonobsoleteMessageIndex(int msgIdx) const
bool isUnfinished() const
OpenedFile(DataModel *_dataModel, bool _readWrite, bool _langGuessed)
DataModel * dataModel
@ SaveEverything