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"
17#include "globals.h"
18#include "messageeditor.h"
19#include "messagemodel.h"
20#include "phrasebookbox.h"
21#include "phraseview.h"
22#include "printout.h"
23#include "sourcecodeview.h"
24#include "statistics.h"
27#include "validator.h"
28
29#include <QAction>
30#include <QApplication>
31#include <QBitmap>
32#include <QCloseEvent>
33#include <QDebug>
34#include <QDesktopServices>
35#include <QDockWidget>
36#include <QFile>
37#include <QFileDialog>
38#include <QFileInfo>
39#include <QHeaderView>
40#include <QInputDialog>
41#include <QItemDelegate>
42#include <QLabel>
43#include <QLayout>
44#include <QLibraryInfo>
45#include <QMenu>
46#include <QMenuBar>
47#include <QMessageBox>
48#include <QMimeData>
49#include <QProcess>
50#include <QRegularExpression>
51#include <QScreen>
52#include <QShortcut>
53#include <QSettings>
54#include <QSortFilterProxyModel>
55#include <QStackedWidget>
56#include <QStatusBar>
57#include <QTextStream>
58#include <QToolBar>
59#include <QUrl>
60#include <QWhatsThis>
61
62#if QT_CONFIG(printsupport)
63#include <QPrintDialog>
64#include <QPrinter>
65#endif
66
67using namespace Qt::Literals::StringLiterals;
68namespace {
69
70static bool hasUiFormPreview(const QString &fileName)
71{
72 return fileName.endsWith(".ui"_L1) || fileName.endsWith(".jui"_L1);
73}
74
75static bool hasQmlFormPreview(const QString &fileName, bool qmlPreviewChecked)
76{
77 return fileName.endsWith(QLatin1String(".qml")) && qmlPreviewChecked;
78}
79
80static const int MessageMS = 2500;
81
82} // namespace
83
84QT_BEGIN_NAMESPACE
85
86class GroupItemDelegate : public QItemDelegate
87{
88public:
89 GroupItemDelegate(QObject *parent, MultiDataModel *model)
90 : QItemDelegate(parent), m_dataModel(model)
91 {
92 }
93
94 void paint(QPainter *painter, const QStyleOptionViewItem &option,
95 const QModelIndex &index) const override
96 {
97 const QAbstractItemModel *model = index.model();
98 Q_ASSERT(model);
99
100 if (!model->parent(index).isValid()) {
101 if (index.column() - 1 == m_dataModel->modelCount()) {
102 QStyleOptionViewItem opt = option;
103 opt.font.setBold(true);
104 QItemDelegate::paint(painter, opt, index);
105 return;
106 }
107 }
108 QItemDelegate::paint(painter, option, index);
109 }
110
111private:
112 MultiDataModel *m_dataModel;
113};
114
115static const QVariant &pxObsolete()
116{
117 static const QVariant v = createMarkIcon(TranslationMarks::ObsoleteMark);
118 return v;
119}
120
121
123{
124public:
125 SortedMessagesModel(QObject *parent, MultiDataModel *model, TranslationType translationType)
126 : QSortFilterProxyModel(parent), m_dataModel(model), m_translationType(translationType)
127 {
128 }
129
130 QVariant headerData(int section, Qt::Orientation orientation, int role) const override
131 {
132 if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
133 if (m_translationType == TEXTBASED) {
134 switch (section - m_dataModel->modelCount()) {
135 case 0:
136 return QString();
137 case 1:
138 return MainWindow::tr("Source text");
139 }
140 } else {
141 switch (section - m_dataModel->modelCount()) {
142 case 0: return QString();
143 case 1:
144 return MainWindow::tr("ID");
145 case 2:
146 return MainWindow::tr("Source text");
147 }
148 }
149 }
150 if (role == Qt::DecorationRole && orientation == Qt::Horizontal && section - 1 < m_dataModel->modelCount())
151 return pxObsolete();
152
153 return QVariant();
154 }
155
156private:
157 MultiDataModel *m_dataModel;
158 TranslationType m_translationType;
159};
160
162{
163public:
164 SortedGroupsModel(QObject *parent, MultiDataModel *model, TranslationType translationType)
165 : QSortFilterProxyModel(parent), m_dataModel(model), m_translationType(translationType)
166 {
167 }
168
169 QVariant headerData(int section, Qt::Orientation orientation, int role) const override
170 {
171 if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
172 switch (section - m_dataModel->modelCount()) {
173 case 0: return QString();
174 case 1:
175 return m_translationType == TEXTBASED ? MainWindow::tr("Context")
176 : MainWindow::tr("Label");
177 case 2:
178 return MainWindow::tr("Items");
179 }
180
181 if (role == Qt::DecorationRole && orientation == Qt::Horizontal && section - 1 < m_dataModel->modelCount())
182 return pxObsolete();
183
184 return QVariant();
185 }
186
187private:
188 MultiDataModel *m_dataModel;
189 TranslationType m_translationType;
190};
191
192class FocusWatcher : public QObject
193{
194public:
195 FocusWatcher(MessageEditor *msgedit, QObject *parent) : QObject(parent), m_messageEditor(msgedit) {}
196
197protected:
198 bool eventFilter(QObject *object, QEvent *event) override;
199
200private:
201 MessageEditor *m_messageEditor;
202};
203
204bool FocusWatcher::eventFilter(QObject *, QEvent *event)
205{
206 if (event->type() == QEvent::FocusIn)
207 m_messageEditor->setEditorFocusForModel(-1);
208 return false;
209}
210
212 : QMainWindow(0, Qt::Window),
213#if QT_CONFIG(process)
215#endif // QT_CONFIG(process)
216#if QT_CONFIG(printsupport)
217 m_printer(0),
218#endif // QT_CONFIG(printsupport)
219 m_findWhere(DataModel::NoLocation),
220 m_translationSettingsDialog(0),
221 m_settingCurrentMessage(false),
222 m_fileActiveModel(-1),
223 m_editActiveModel(-1),
224 m_statistics(0),
225 m_recentFiles(10)
226{
227 setUnifiedTitleAndToolBarOnMac(true);
228 m_ui.setupUi(this);
229
230#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
231 setWindowIcon(QPixmap(":/images/appicon.png"_L1));
232#endif
233
234 m_dataModel = new MultiDataModel(this);
235 m_idBasedMessageModel = new MessageModel(IDBASED, this, m_dataModel);
236 m_textBasedMessageModel = new MessageModel(TEXTBASED, this, m_dataModel);
237
238 // Set up the context/label dock widget
239 m_contextAndLabelDock = new QDockWidget(this);
240 m_contextAndLabelDock->setObjectName("ContextLabelDockWidget");
241 m_contextAndLabelDock->setAllowedAreas(Qt::AllDockWidgetAreas);
242 m_contextAndLabelDock->setWindowTitle(tr("Context/Label"));
243 m_contextAndLabelDock->setAcceptDrops(true);
244 m_contextAndLabelDock->installEventFilter(this);
245
246 m_contextAndLabelView = new QTabWidget(this);
247
248 QWidget* dockContent = new QWidget(this);
249 QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, dockContent);
250 layout->addWidget(m_contextAndLabelView);
251
252 m_contextAndLabelDock->setWidget(dockContent);
253
254 // context view
255 m_sortedContextsModel = new SortedGroupsModel(this, m_dataModel, TEXTBASED);
256 m_sortedContextsModel->setSortRole(MessageModel::SortRole);
257 m_sortedContextsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
258 m_sortedContextsModel->setSourceModel(m_textBasedMessageModel);
259
260 m_contextView = new QTreeView(this);
261 m_contextView->setRootIsDecorated(false);
262 m_contextView->setItemsExpandable(false);
263 m_contextView->setUniformRowHeights(true);
264 m_contextView->setAlternatingRowColors(true);
265 m_contextView->setAllColumnsShowFocus(true);
266 m_contextView->setItemDelegate(new GroupItemDelegate(this, m_dataModel));
267 m_contextView->setSortingEnabled(true);
268 m_contextView->setWhatsThis(tr("This panel lists the source contexts."));
269 m_contextView->setModel(m_sortedContextsModel);
270 m_contextView->header()->setSectionsMovable(false);
271 m_contextView->setColumnHidden(0, true);
272 m_contextView->header()->setStretchLastSection(false);
273
274 m_contextAndLabelView->addTab(m_contextView, "Text Based"_L1);
275
276 // label view
277 m_sortedLabelsModel = new SortedGroupsModel(this, m_dataModel, IDBASED);
278 m_sortedLabelsModel->setSortRole(MessageModel::SortRole);
279 m_sortedLabelsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
280 m_sortedLabelsModel->setSourceModel(m_idBasedMessageModel);
281
282 m_labelView = new QTreeView(this);
283 m_labelView->setRootIsDecorated(false);
284 m_labelView->setItemsExpandable(false);
285 m_labelView->setUniformRowHeights(true);
286 m_labelView->setAlternatingRowColors(true);
287 m_labelView->setAllColumnsShowFocus(true);
288 m_labelView->setItemDelegate(new GroupItemDelegate(this, m_dataModel));
289 m_labelView->setSortingEnabled(true);
290 m_labelView->setWhatsThis(tr("This panel lists the source labels."));
291 m_labelView->setModel(m_sortedLabelsModel);
292 m_labelView->header()->setSectionsMovable(false);
293 m_labelView->setColumnHidden(0, true);
294 m_labelView->header()->setStretchLastSection(false);
295
296 m_contextAndLabelView->addTab(m_labelView, "ID Based"_L1);
297
298 // Set up the messages dock widget
299 m_messagesDock = new QDockWidget(this);
300 m_messagesDock->setObjectName("StringsDockWidget");
301 m_messagesDock->setAllowedAreas(Qt::AllDockWidgetAreas);
302 m_messagesDock->setWindowTitle(tr("Strings"));
303 m_messagesDock->setAcceptDrops(true);
304 m_messagesDock->installEventFilter(this);
305
306 m_sortedTextBasedMessagesModel = new SortedMessagesModel(this, m_dataModel, TEXTBASED);
307 m_sortedTextBasedMessagesModel->setSortRole(MessageModel::SortRole);
308 m_sortedTextBasedMessagesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
309 m_sortedTextBasedMessagesModel->setSortLocaleAware(true);
310 m_sortedTextBasedMessagesModel->setSourceModel(m_textBasedMessageModel);
311
312 m_sortedIdBasedMessagesModel = new SortedMessagesModel(this, m_dataModel, IDBASED);
313 m_sortedIdBasedMessagesModel->setSortRole(MessageModel::SortRole);
314 m_sortedIdBasedMessagesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
315 m_sortedIdBasedMessagesModel->setSortLocaleAware(true);
316 m_sortedIdBasedMessagesModel->setSourceModel(m_idBasedMessageModel);
317
318 m_messageView = new QTreeView(m_messagesDock);
319 m_messageView->setSortingEnabled(true);
320 m_messageView->setRootIsDecorated(false);
321 m_messageView->setUniformRowHeights(true);
322 m_messageView->setAllColumnsShowFocus(true);
323 m_messageView->setItemsExpandable(false);
324 m_messageView->setModel(m_sortedTextBasedMessagesModel);
325 m_messageView->header()->setSectionsMovable(false);
326 m_messageView->setColumnHidden(0, true);
327
328 m_messagesDock->setWidget(m_messageView);
329
330 // Set up main message view
331 m_messageEditor = new MessageEditor(m_dataModel, this);
332 m_messageEditor->setAcceptDrops(true);
333 m_messageEditor->installEventFilter(this);
334 // We can't call setCentralWidget(m_messageEditor), since it is already called in m_ui.setupUi()
335 QBoxLayout *lout = new QBoxLayout(QBoxLayout::TopToBottom, m_ui.centralwidget);
336 lout->addWidget(m_messageEditor);
337 lout->setContentsMargins(QMargins());
338 m_ui.centralwidget->setLayout(lout);
339
340 // Set up the phrases & guesses dock widget
341 m_phrasesDock = new QDockWidget(this);
342 m_phrasesDock->setObjectName("PhrasesDockwidget");
343 m_phrasesDock->setAllowedAreas(Qt::AllDockWidgetAreas);
344 m_phrasesDock->setWindowTitle(tr("Phrases and guesses"));
345
346 m_phraseView = new PhraseView(m_dataModel, &m_phraseDict, this);
347 m_phrasesDock->setWidget(m_phraseView);
348
349 // Set up source code and form preview dock widget
350 m_sourceAndFormDock = new QDockWidget(this);
351 m_sourceAndFormDock->setObjectName("SourceAndFormDock");
352 m_sourceAndFormDock->setAllowedAreas(Qt::AllDockWidgetAreas);
353 m_sourceAndFormDock->setWindowTitle(tr("Sources and Forms"));
354 m_sourceAndFormView = new QStackedWidget(this);
355 m_sourceAndFormDock->setWidget(m_sourceAndFormView);
356 m_uiFormPreviewView = new UiFormPreviewView(0, m_dataModel);
357 m_qmlFormPreviewView = new QmlFormPreviewView(m_dataModel);
358 m_sourceCodeView = new SourceCodeView(0);
359 m_sourceAndFormView->addWidget(m_sourceCodeView);
360 m_sourceAndFormView->addWidget(m_uiFormPreviewView);
361 m_sourceAndFormView->addWidget(m_qmlFormPreviewView);
362
363 // Set up errors dock widget
364 m_errorsDock = new QDockWidget(this);
365 m_errorsDock->setObjectName("ErrorsDockWidget");
366 m_errorsDock->setAllowedAreas(Qt::AllDockWidgetAreas);
367 m_errorsDock->setWindowTitle(tr("Warnings"));
368 m_errorsView = new ErrorsView(m_dataModel, this);
369 m_errorsDock->setWidget(m_errorsView);
370
371 // Arrange dock widgets
372 setDockNestingEnabled(true);
373 setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
374 setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
375 setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
376 setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
377 addDockWidget(Qt::LeftDockWidgetArea, m_contextAndLabelDock);
378 addDockWidget(Qt::TopDockWidgetArea, m_messagesDock);
379 addDockWidget(Qt::BottomDockWidgetArea, m_phrasesDock);
380 addDockWidget(Qt::TopDockWidgetArea, m_sourceAndFormDock);
381 m_sourceAndFormDock->hide();
382 addDockWidget(Qt::BottomDockWidgetArea, m_errorsDock);
383 //tabifyDockWidget(m_errorsDock, m_sourceAndFormDock);
384 //tabifyDockWidget(m_sourceCodeDock, m_phrasesDock);
385
386 // Allow phrases doc to intercept guesses shortcuts
387 m_messageEditor->installEventFilter(m_phraseView);
388
389 // Set up shortcuts for the dock widgets
390 QShortcut *contextShortcut = new QShortcut(QKeySequence(Qt::Key_F6), this);
391 connect(contextShortcut, &QShortcut::activated,
392 this, &MainWindow::showContextDock);
393 QShortcut *messagesShortcut = new QShortcut(QKeySequence(Qt::Key_F7), this);
394 connect(messagesShortcut, &QShortcut::activated,
395 this, &MainWindow::showMessagesDock);
396 QShortcut *errorsShortcut = new QShortcut(QKeySequence(Qt::Key_F8), this);
397 connect(errorsShortcut, &QShortcut::activated,
398 this, &MainWindow::showErrorDock);
399 QShortcut *sourceCodeShortcut = new QShortcut(QKeySequence(Qt::Key_F9), this);
400 connect(sourceCodeShortcut, &QShortcut::activated,
401 this, &MainWindow::showSourceCodeDock);
402 QShortcut *phrasesShortcut = new QShortcut(QKeySequence(Qt::Key_F10), this);
403 connect(phrasesShortcut, &QShortcut::activated,
404 this, &MainWindow::showPhrasesDock);
405
406 connect(m_phraseView, &PhraseView::phraseSelected,
407 m_messageEditor, &MessageEditor::setTranslation);
408 connect(m_phraseView, &PhraseView::setCurrentMessageFromGuess,
409 this, &MainWindow::setCurrentMessageFromGuess);
410 connect(m_contextView->selectionModel(), &QItemSelectionModel::currentRowChanged,
411 this, &MainWindow::selectedContextChanged);
412 connect(m_labelView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
413 &MainWindow::selectedLabelChanged);
414 connect(m_contextView->selectionModel(), &QItemSelectionModel::currentColumnChanged,
415 this, &MainWindow::updateLatestModel);
416 connect(m_labelView->selectionModel(), &QItemSelectionModel::currentColumnChanged, this,
417 &MainWindow::updateLatestModel);
418 connect(m_messageEditor, &MessageEditor::activeModelChanged,
419 this, &MainWindow::updateActiveModel);
420 connect(m_contextAndLabelView, &QTabWidget::currentChanged, this,
421 &MainWindow::contextAndLabelTabChanged);
422
423 m_translateDialog = new TranslateDialog(this);
424 m_batchTranslateDialog = new BatchTranslationDialog(m_dataModel, this);
425 m_findDialog = new FindDialog(this);
426
427 setupMenuBar();
428 setupToolBars();
429
430 m_progressLabel = new QLabel();
431 statusBar()->addPermanentWidget(m_progressLabel);
432 m_modifiedLabel = new QLabel(tr(" MOD ", "status bar: file(s) modified"));
433 statusBar()->addPermanentWidget(m_modifiedLabel);
434 m_contextAndLabelView->setCurrentWidget(m_contextView);
435 contextAndLabelTabChanged();
436
437 modelCountChanged();
438 initViewHeaders();
439 resetSorting();
440
441 connect(m_dataModel, &MultiDataModel::modifiedChanged,
442 this, &QWidget::setWindowModified);
443 connect(m_dataModel, &MultiDataModel::modifiedChanged,
444 m_modifiedLabel, &QWidget::setVisible);
445 connect(m_dataModel, &MultiDataModel::multiGroupDataChanged, this, &MainWindow::updateProgress);
446 connect(m_dataModel, &MultiDataModel::messageDataChanged, this,
447 &MainWindow::maybeUpdateStatistics);
448 connect(m_dataModel, &MultiDataModel::translationChanged, this,
449 &MainWindow::translationChanged);
450 connect(m_dataModel, &MultiDataModel::languageChanged,
451 this, &MainWindow::updatePhraseDict);
452
453 setWindowModified(m_dataModel->isModified());
454 m_modifiedLabel->setVisible(m_dataModel->isModified());
455
456 connect(m_messageView, &QAbstractItemView::clicked,
457 this, &MainWindow::toggleFinished);
458 connect(m_messageView, &QAbstractItemView::activated,
459 m_messageEditor, &MessageEditor::setEditorFocus);
460 connect(m_contextView, &QAbstractItemView::activated,
461 m_messageView, qOverload<>(&QWidget::setFocus));
462 connect(m_labelView, &QAbstractItemView::activated, m_messageView,
463 qOverload<>(&QWidget::setFocus));
464 connect(m_messageEditor, &MessageEditor::translationChanged,
465 this, &MainWindow::updateTranslation);
466 connect(m_messageEditor, &MessageEditor::translatorCommentChanged,
467 this, &MainWindow::updateTranslatorComment);
468 connect(m_findDialog, &FindDialog::findNext,
469 this, &MainWindow::findNext);
470 connect(m_translateDialog, &TranslateDialog::requestMatchUpdate,
471 this, &MainWindow::updateTranslateHit);
472 connect(m_translateDialog, &TranslateDialog::activated,
473 this, &MainWindow::translate);
474
475 QSize as(screen()->size());
476 as -= QSize(30, 30);
477 resize(QSize(1000, 800).boundedTo(as));
478 show();
480 m_statistics = 0;
481
482 connect(m_ui.actionLengthVariants, &QAction::toggled,
483 m_messageEditor, &MessageEditor::setLengthVariants);
484 m_messageEditor->setLengthVariants(m_ui.actionLengthVariants->isChecked());
485 m_messageEditor->setVisualizeWhitespace(m_ui.actionVisualizeWhitespace->isChecked());
486
487 m_focusWatcher = new FocusWatcher(m_messageEditor, this);
488 m_contextView->installEventFilter(m_focusWatcher);
489 m_labelView->installEventFilter(m_focusWatcher);
490 m_messageView->installEventFilter(m_focusWatcher);
491 m_messageEditor->installEventFilter(m_focusWatcher);
492 m_sourceAndFormView->installEventFilter(m_focusWatcher);
493 m_phraseView->installEventFilter(m_focusWatcher);
494 m_errorsView->installEventFilter(m_focusWatcher);
495}
496
498{
500#if QT_CONFIG(process)
501 if (m_assistantProcess && m_assistantProcess->state() == QProcess::Running) {
502 m_assistantProcess->terminate();
503 m_assistantProcess->waitForFinished(3000);
504 }
505#endif // QT_CONFIG(process)
506 qDeleteAll(m_phraseBooks);
507 delete m_dataModel;
508 delete m_statistics;
509#if QT_CONFIG(printsupport)
510 delete m_printer;
511#endif
512}
513
514void MainWindow::initViewHeaders()
515{
516 m_contextView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
517 m_contextView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
518 m_labelView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
519 m_labelView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
520 // last visible column auto-stretches
521}
522
523void MainWindow::modelCountChanged()
524{
525 int mc = m_dataModel->modelCount();
526
527 for (int i = 1; i < mc + 1; ++i) {
528 m_contextView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
529 m_contextView->header()->resizeSection(i, 24);
530
531 m_labelView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
532 m_labelView->header()->resizeSection(i, 24);
533
534 m_messageView->header()->setSectionResizeMode(i, QHeaderView::Fixed);
535 m_messageView->header()->resizeSection(i, 24);
536 }
537 for (int i = mc + 1; i < m_messageView->header()->count(); i++)
538 m_messageView->header()->setSectionResizeMode(i, QHeaderView::Stretch);
539
540 if (!mc) {
541 selectedMessageChanged(QModelIndex(), QModelIndex());
542 doUpdateLatestModel(-1);
543 } else {
544 QTreeView *view = qobject_cast<QTreeView *>(m_contextAndLabelView->currentWidget());
545 if (!view->currentIndex().isValid()) {
546 // Ensure that something is selected
547 view->setCurrentIndex(m_activeSortedGroupsModel->index(0, 0));
548 } else {
549 // Plug holes that turn up in the selection due to inserting columns
550 view->selectionModel()->select(view->currentIndex(),
551 QItemSelectionModel::SelectCurrent
552 | QItemSelectionModel::Rows);
553 m_messageView->selectionModel()->select(m_messageView->currentIndex(),
554 QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
555 }
556 // Field insertions/removals are automatic, but not the re-fill
557 m_messageEditor->showMessage(m_currentIndex);
558 if (mc == 1)
559 doUpdateLatestModel(0);
560 else if (m_currentIndex.model() >= mc)
561 doUpdateLatestModel(mc - 1);
562 }
563
564 m_contextView->setUpdatesEnabled(true);
565 m_labelView->setUpdatesEnabled(true);
566 m_messageView->setUpdatesEnabled(true);
567
568 updateProgress();
569 updateCaption();
570
571 m_ui.actionFind->setEnabled(m_dataModel->contextCount() > 0 || m_dataModel->labelCount() > 0);
572 m_ui.actionFindNext->setEnabled(false);
573 m_ui.actionFindPrev->setEnabled(false);
574
575 m_uiFormPreviewView->setSourceContext(-1, 0);
576 m_qmlFormPreviewView->setSourceContext(-1, 0);
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#if QT_CONFIG(process)
1397 if (!m_assistantProcess)
1398 m_assistantProcess = new QProcess();
1399
1400 if (m_assistantProcess->state() != QProcess::Running) {
1401 QString app = QLibraryInfo::path(QLibraryInfo::BinariesPath) + QDir::separator();
1402#if !defined(Q_OS_MAC)
1403 app += "assistant"_L1;
1404#else
1405 app += "Assistant.app/Contents/MacOS/Assistant"_L1;
1406#endif
1407
1408 m_assistantProcess->start(app, { "-enableRemoteControl"_L1 });
1409 if (!m_assistantProcess->waitForStarted()) {
1410 QMessageBox::critical(this, tr("Qt Linguist"),
1411 tr("Unable to launch Qt Assistant (%1)").arg(app));
1412 return;
1413 }
1414 }
1415 QTextStream str(m_assistantProcess);
1416 str << "SetSource qthelp://org.qt-project.linguist."_L1 << QT_VERSION_MAJOR << QT_VERSION_MINOR
1417 << QT_VERSION_PATCH << "/qtlinguist/qtlinguist-index.html"_L1 << u'\n' << Qt::endl;
1418#else // QT_CONFIG(process)
1419 QDesktopServices::openUrl(
1420 QUrl::fromUserInput(QLatin1String("https://doc.qt.io/qt-6/qtlinguist-index.html")));
1421#endif // QT_CONFIG(process)
1422}
1423
1424void MainWindow::about()
1425{
1426 QMessageBox box(this);
1427 box.setTextFormat(Qt::RichText);
1428 QString version = tr("Version %1");
1429 version = version.arg(QLatin1String(QT_VERSION_STR));
1430
1431 const QString description
1432 = tr("Qt Linguist is a tool for adding translations to Qt applications.");
1433 box.setText(QStringLiteral("<center><img src=\":/images/icons/linguist-128-32.png\"/></img><p>%1</p></center>"
1434 "<p>%2</p>"
1435 "<p>Copyright (C) The Qt Company Ltd.</p>").arg(version, description));
1436
1437 box.setWindowTitle(QApplication::translate("AboutDialog", "Qt Linguist"));
1438 box.setIcon(QMessageBox::NoIcon);
1439 box.exec();
1440}
1441
1442void MainWindow::aboutQt()
1443{
1444 QMessageBox::aboutQt(this, tr("Qt Linguist"));
1445}
1446
1447void MainWindow::setupPhrase()
1448{
1449 bool enabled = !m_phraseBooks.isEmpty();
1450 m_ui.menuClosePhraseBook->setEnabled(enabled);
1451 m_ui.menuEditPhraseBook->setEnabled(enabled);
1452#if QT_CONFIG(printsupport)
1453 m_ui.menuPrintPhraseBook->setEnabled(enabled);
1454#endif
1455}
1456
1457void MainWindow::closeEvent(QCloseEvent *e)
1458{
1459 if (maybeSaveAll() && maybeSavePhraseBooks())
1460 e->accept();
1461 else
1462 e->ignore();
1463}
1464
1465bool MainWindow::maybeSaveAll()
1466{
1467 if (!m_dataModel->isModified())
1468 return true;
1469
1470 switch (QMessageBox::information(this, tr("Qt Linguist"),
1471 tr("Do you want to save the modified files?"),
1472 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
1473 {
1474 case QMessageBox::Cancel:
1475 return false;
1476 case QMessageBox::Yes:
1477 saveAll();
1478 return !m_dataModel->isModified();
1479 default:
1480 break;
1481 }
1482 return true;
1483}
1484
1485bool MainWindow::maybeSave(int model)
1486{
1487 if (!m_dataModel->isModified(model))
1488 return true;
1489
1490 switch (QMessageBox::information(this, tr("Qt Linguist"),
1491 tr("Do you want to save '%1'?").arg(m_dataModel->srcFileName(model, true)),
1492 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
1493 {
1494 case QMessageBox::Cancel:
1495 return false;
1496 case QMessageBox::Yes:
1497 saveInternal(model);
1498 return !m_dataModel->isModified(model);
1499 default:
1500 break;
1501 }
1502 return true;
1503}
1504
1505void MainWindow::updateCaption()
1506{
1507 QString cap;
1508 bool enable = false;
1509 bool enableRw = false;
1510 for (int i = 0; i < m_dataModel->modelCount(); ++i) {
1511 enable = true;
1512 if (m_dataModel->isModelWritable(i)) {
1513 enableRw = true;
1514 break;
1515 }
1516 }
1517 m_ui.actionSaveAll->setEnabled(enableRw);
1518 m_ui.actionReleaseAll->setEnabled(enableRw);
1519 m_ui.actionCloseAll->setEnabled(enable);
1520#if QT_CONFIG(printsupport)
1521 m_ui.actionPrint->setEnabled(enable);
1522#endif
1523 m_ui.actionAccelerators->setEnabled(enable);
1524 m_ui.actionSurroundingWhitespace->setEnabled(enable);
1525 m_ui.actionEndingPunctuation->setEnabled(enable);
1526 m_ui.actionPhraseMatches->setEnabled(enable);
1527 m_ui.actionPlaceMarkerMatches->setEnabled(enable);
1528 m_ui.actionResetSorting->setEnabled(enable);
1529
1530 updateActiveModel(m_messageEditor->activeModel());
1531 // Ensure that the action labels get updated
1532 m_fileActiveModel = m_editActiveModel = -2;
1533
1534 if (!enable)
1535 cap = tr("Qt Linguist[*]");
1536 else
1537 cap = tr("%1[*] - Qt Linguist").arg(m_dataModel->condensedSrcFileNames(true));
1538 setWindowTitle(cap);
1539}
1540
1541void MainWindow::selectedContextChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
1542{
1543 if (sortedIndex.isValid()) {
1544 if (m_settingCurrentMessage)
1545 return; // Avoid playing ping-pong with the current message
1546
1547 if (!m_activeTranslationType || *m_activeTranslationType == IDBASED)
1548 contextAndLabelTabChanged();
1549 QModelIndex sourceIndex = m_sortedContextsModel->mapToSource(sortedIndex);
1550 if (m_activeMessageModel->parent(currentMessageIndex()).row() == sourceIndex.row())
1551 return;
1552 QModelIndex contextIndex = setMessageViewRoot(sourceIndex);
1553 const QModelIndex &firstChild =
1554 m_activeSortedMessagesModel->index(0, sourceIndex.column(), contextIndex);
1555 m_messageView->setCurrentIndex(firstChild);
1556 } else if (oldIndex.isValid()) {
1557 m_contextView->setCurrentIndex(oldIndex);
1558 }
1559}
1560
1561void MainWindow::selectedLabelChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
1562{
1563 if (sortedIndex.isValid()) {
1564 if (m_settingCurrentMessage)
1565 return; // Avoid playing ping-pong with the current message
1566
1567 if (!m_activeTranslationType || *m_activeTranslationType != IDBASED)
1568 contextAndLabelTabChanged();
1569 QModelIndex sourceIndex = m_sortedLabelsModel->mapToSource(sortedIndex);
1570 if (m_activeMessageModel->parent(currentMessageIndex()).row() == sourceIndex.row())
1571 return;
1572 QModelIndex labelIndex = setMessageViewRoot(sourceIndex);
1573 const QModelIndex &firstChild =
1574 m_activeSortedMessagesModel->index(0, sourceIndex.column(), labelIndex);
1575 m_messageView->setCurrentIndex(firstChild);
1576 } else if (oldIndex.isValid()) {
1577 m_labelView->setCurrentIndex(oldIndex);
1578 }
1579}
1580
1581/*
1582 * Updates the message displayed in the message editor and related actions.
1583 */
1584void MainWindow::selectedMessageChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
1585{
1586 // Keep a valid selection whenever possible
1587 if (!sortedIndex.isValid() && oldIndex.isValid()) {
1588 m_messageView->setCurrentIndex(oldIndex);
1589 return;
1590 }
1591
1592 int model = -1;
1593 MessageItem *m = nullptr;
1594 QModelIndex index = m_activeSortedMessagesModel->mapToSource(sortedIndex);
1595 if (index.isValid()) {
1596 model = (index.column() && (index.column() - 1 < m_dataModel->modelCount()))
1597 ? index.column() - 1
1598 : m_currentIndex.model();
1599 m_currentIndex = m_activeMessageModel->dataIndex(index, model);
1600 m_messageEditor->showMessage(m_currentIndex);
1601 if (model >= 0 && (m = m_dataModel->messageItem(m_currentIndex))) {
1602 if (m_dataModel->isModelWritable(model) && !m->isObsolete())
1603 m_phraseView->setSourceText(m_currentIndex.model(), m->text());
1604 else
1605 m_phraseView->setSourceText(-1, QString());
1606 } else {
1607 if (model < 0) {
1608 model = m_dataModel->multiGroupItem(m_currentIndex)
1610 if (model >= 0)
1611 m = m_dataModel->messageItem(m_currentIndex, model);
1612 }
1613 m_phraseView->setSourceText(-1, QString());
1614 }
1615 m_errorsView->setEnabled(m != 0);
1616 updateDanger(m_currentIndex, true);
1617 } else {
1618 m_currentIndex = MultiDataIndex(m_currentIndex.translationType());
1619 m_messageEditor->showNothing();
1620 m_phraseView->setSourceText(-1, QString());
1621 }
1622 updateSourceView(model, m);
1623
1624 updatePhraseBookActions();
1625 m_ui.actionSelectAll->setEnabled(index.isValid());
1626}
1627
1628void MainWindow::translationChanged(const MultiDataIndex &index)
1629{
1630 m_messageEditor->showMessage(index);
1631 updateDanger(index, true);
1632
1633 MessageItem *m = m_dataModel->messageItem(index);
1634 if (hasUiFormPreview(m->fileName()))
1635 m_uiFormPreviewView->setSourceContext(index.model(), m);
1636 else if (hasQmlFormPreview(m->fileName(), m_ui.actionQmlPreview->isChecked()))
1637 if (!m_qmlFormPreviewView->setSourceContext(index.model(), m))
1638 m_ui.actionQmlPreview->setChecked(false);
1639}
1640
1641// This and the following function operate directly on the messageitem,
1642// so the model does not emit modification notifications.
1643void MainWindow::updateTranslation(const QStringList &translations)
1644{
1645 MessageItem *m = m_dataModel->messageItem(m_currentIndex);
1646 if (!m)
1647 return;
1648 if (translations == m->translations())
1649 return;
1650
1651 m->setTranslations(translations);
1652 if (!m->fileName().isEmpty() && hasUiFormPreview(m->fileName()))
1653 m_uiFormPreviewView->setSourceContext(m_currentIndex.model(), m);
1654 else if (!m->fileName().isEmpty()
1655 && hasQmlFormPreview(m->fileName(), m_ui.actionQmlPreview->isChecked()))
1656 if (!m_qmlFormPreviewView->setSourceContext(m_currentIndex.model(), m))
1657 m_ui.actionQmlPreview->setChecked(false);
1658 updateDanger(m_currentIndex, true);
1659
1660 if (m->isFinished())
1661 m_dataModel->setFinished(m_currentIndex, false);
1662 else
1663 m_dataModel->setModified(m_currentIndex.model(), true);
1664}
1665
1666void MainWindow::updateTranslatorComment(const QString &comment)
1667{
1668 MessageItem *m = m_dataModel->messageItem(m_currentIndex);
1669 if (!m)
1670 return;
1671 if (comment == m->translatorComment())
1672 return;
1673
1674 m->setTranslatorComment(comment);
1675
1676 m_dataModel->setModified(m_currentIndex.model(), true);
1677}
1678
1679void MainWindow::refreshItemViews()
1680{
1681 m_activeMessageModel->blockSignals(false);
1682 m_contextView->update();
1683 m_labelView->update();
1684 m_messageView->update();
1685 setWindowModified(m_dataModel->isModified());
1686 m_modifiedLabel->setVisible(m_dataModel->isModified());
1687 updateStatistics();
1688}
1689
1690void MainWindow::done()
1691{
1692 int model = m_messageEditor->activeModel();
1693 if (model >= 0 && m_dataModel->isModelWritable(model))
1694 m_dataModel->setFinished(m_currentIndex, true);
1695}
1696
1697void MainWindow::doneAndNext()
1698{
1699 done();
1700 if (!m_messageEditor->focusNextUnfinished())
1701 nextUnfinished();
1702}
1703
1704void MainWindow::toggleFinished(const QModelIndex &index)
1705{
1706 if (!index.isValid() || index.column() - 1 >= m_dataModel->modelCount()
1707 || !m_dataModel->isModelWritable(index.column() - 1) || index.parent() == QModelIndex())
1708 return;
1709
1710 QModelIndex item = m_activeSortedMessagesModel->mapToSource(index);
1711 MultiDataIndex dataIndex = m_activeMessageModel->dataIndex(item);
1712 MessageItem *m = m_dataModel->messageItem(dataIndex);
1713
1716 return;
1717
1718 m_dataModel->setFinished(dataIndex, !m->isFinished());
1719}
1720
1721void MainWindow::openMachineTranslateDialog()
1722{
1723 if (!m_machineTranslationDialog)
1724 m_machineTranslationDialog = new MachineTranslationDialog(this);
1725 m_machineTranslationDialog->setDataModel(m_dataModel);
1726 m_machineTranslationDialog->open();
1727}
1728
1729/*
1730 * Receives a context index in the sorted messages model and returns the next
1731 * logical context index in the same model, based on the sort order of the
1732 * contexts in the sorted contexts model.
1733 */
1734QModelIndex MainWindow::nextGroup(const QModelIndex &index) const
1735{
1736 QModelIndex sortedGroupIndex = m_activeSortedGroupsModel->mapFromSource(
1737 m_activeSortedMessagesModel->mapToSource(index));
1738
1739 int nextRow = sortedGroupIndex.row() + 1;
1740 if (nextRow >= m_activeSortedGroupsModel->rowCount()) {
1741 const QSortFilterProxyModel *inactiveModel =
1742 m_activeSortedGroupsModel == m_sortedLabelsModel ? m_sortedContextsModel
1743 : m_sortedLabelsModel;
1744 if (inactiveModel->rowCount())
1745 m_contextAndLabelView->setCurrentIndex(1 - m_contextAndLabelView->currentIndex());
1746 nextRow = 0;
1747 }
1748 sortedGroupIndex = m_activeSortedGroupsModel->index(nextRow, index.column());
1749
1750 return m_activeSortedMessagesModel->mapFromSource(
1751 m_activeSortedGroupsModel->mapToSource(sortedGroupIndex));
1752}
1753
1754/*
1755 * See nextContext.
1756 */
1757QModelIndex MainWindow::prevGroup(const QModelIndex &index) const
1758{
1759 QModelIndex sortedGroupIndex = m_activeSortedGroupsModel->mapFromSource(
1760 m_activeSortedMessagesModel->mapToSource(index));
1761
1762 int prevRow = sortedGroupIndex.row() - 1;
1763 if (prevRow < 0) {
1764 const QSortFilterProxyModel *inactiveModel =
1765 m_activeSortedGroupsModel == m_sortedLabelsModel ? m_sortedContextsModel
1766 : m_sortedLabelsModel;
1767 if (inactiveModel->rowCount())
1768 m_contextAndLabelView->setCurrentIndex(1 - m_contextAndLabelView->currentIndex());
1769 prevRow = m_activeSortedGroupsModel->rowCount() - 1;
1770 }
1771 sortedGroupIndex = m_activeSortedGroupsModel->index(prevRow, index.column());
1772
1773 return m_activeSortedMessagesModel->mapFromSource(
1774 m_activeSortedGroupsModel->mapToSource(sortedGroupIndex));
1775}
1776
1777QModelIndex MainWindow::firstMessage() const
1778{
1779 QModelIndex id = m_activeSortedMessagesModel->index(0, 0);
1780 QModelIndex firstId;
1781 if (id.isValid() && m_activeSortedMessagesModel->hasChildren(id))
1782 firstId = m_activeSortedMessagesModel->index(0, 0, id);
1783 else if (id.isValid())
1784 firstId = id;
1785 return firstId;
1786}
1787
1788QModelIndex MainWindow::nextMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
1789{
1790 QModelIndex idx =
1791 currentIndex.isValid() ? currentIndex : m_activeSortedMessagesModel->index(0, 0);
1792 do {
1793 int row = 0;
1794 QModelIndex par = idx.parent();
1795 if (par.isValid()) {
1796 row = idx.row() + 1;
1797 } else { // In case we are located on a top-level node
1798 par = idx;
1799 }
1800
1801 if (row >= m_activeSortedMessagesModel->rowCount(par)) {
1802 par = nextGroup(par);
1803 row = 0;
1804 }
1805 idx = m_activeSortedMessagesModel->index(row, idx.column(), par);
1806
1807 if (!checkUnfinished)
1808 return idx;
1809
1810 QModelIndex item = m_activeSortedMessagesModel->mapToSource(idx);
1811 MultiDataIndex index = m_activeMessageModel->dataIndex(item, -1);
1812 if (m_dataModel->multiMessageItem(index)->isUnfinished())
1813 return idx;
1814 } while (idx != currentIndex);
1815 return QModelIndex();
1816}
1817
1818QModelIndex MainWindow::prevMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
1819{
1820 QModelIndex idx =
1821 currentIndex.isValid() ? currentIndex : m_activeSortedMessagesModel->index(0, 0);
1822 do {
1823 int row = idx.row() - 1;
1824 QModelIndex par = idx.parent();
1825 if (!par.isValid()) { // In case we are located on a top-level node
1826 par = idx;
1827 row = -1;
1828 }
1829
1830 if (row < 0) {
1831 par = prevGroup(par);
1832 row = m_activeSortedMessagesModel->rowCount(par) - 1;
1833 }
1834 idx = m_activeSortedMessagesModel->index(row, idx.column(), par);
1835
1836 if (!checkUnfinished)
1837 return idx;
1838
1839 QModelIndex item = m_activeSortedMessagesModel->mapToSource(idx);
1840 MultiDataIndex index = m_activeMessageModel->dataIndex(item, -1);
1841 if (m_dataModel->multiMessageItem(index)->isUnfinished())
1842 return idx;
1843 } while (idx != currentIndex);
1844 return QModelIndex();
1845}
1846
1847void MainWindow::nextUnfinished()
1848{
1849 if (m_ui.actionNextUnfinished->isEnabled()) {
1850 if (!doNext(true)) {
1851 // If no Unfinished message is left, the user has finished the job. We
1852 // congratulate on a job well done with this ringing bell.
1853 statusBar()->showMessage(tr("No untranslated translation units left."), MessageMS);
1854 qApp->beep();
1855 }
1856 }
1857}
1858
1859void MainWindow::prevUnfinished()
1860{
1861 if (m_ui.actionNextUnfinished->isEnabled()) {
1862 if (!doPrev(true)) {
1863 // If no Unfinished message is left, the user has finished the job. We
1864 // congratulate on a job well done with this ringing bell.
1865 statusBar()->showMessage(tr("No untranslated translation units left."), MessageMS);
1866 qApp->beep();
1867 }
1868 }
1869}
1870
1871void MainWindow::prev()
1872{
1873 doPrev(false);
1874}
1875
1876void MainWindow::next()
1877{
1878 doNext(false);
1879}
1880
1881bool MainWindow::doPrev(bool checkUnfinished)
1882{
1883 QModelIndex index = prevMessage(m_messageView->currentIndex(), checkUnfinished);
1884 if (index.isValid())
1885 setCurrentMessage(m_activeSortedMessagesModel->mapToSource(index));
1886 if (checkUnfinished)
1887 m_messageEditor->setUnfinishedEditorFocus();
1888 else
1889 m_messageEditor->setEditorFocus();
1890 return index.isValid();
1891}
1892
1893bool MainWindow::doNext(bool checkUnfinished)
1894{
1895 QModelIndex index = nextMessage(m_messageView->currentIndex(), checkUnfinished);
1896 if (index.isValid())
1897 setCurrentMessage(m_activeSortedMessagesModel->mapToSource(index));
1898 if (checkUnfinished)
1899 m_messageEditor->setUnfinishedEditorFocus();
1900 else
1901 m_messageEditor->setEditorFocus();
1902 return index.isValid();
1903}
1904
1905void MainWindow::findNext(const QString &text, DataModel::FindLocation where,
1906 FindDialog::FindOptions options, int statusFilter)
1907{
1908 if (text.isEmpty())
1909 return;
1910 m_findText = text;
1911 m_findWhere = where;
1912 m_findOptions = options;
1913 m_findStatusFilter = statusFilter;
1914 if (options.testFlag(FindDialog::UseRegExp)) {
1915 m_findDialog->getRegExp().setPatternOptions(options.testFlag(FindDialog::MatchCase)
1916 ? QRegularExpression::NoPatternOption
1917 : QRegularExpression::CaseInsensitiveOption);
1918 }
1919 m_ui.actionFindNext->setEnabled(true);
1920 m_ui.actionFindPrev->setEnabled(true);
1921 findAgain();
1922}
1923
1924void MainWindow::revalidate()
1925{
1926 for (MultiDataModelIterator it(IDBASED, m_dataModel, -1); it.isValid(); ++it)
1927 updateDanger(it, false);
1928 for (MultiDataModelIterator it(TEXTBASED, m_dataModel, -1); it.isValid(); ++it)
1929 updateDanger(it, false);
1930
1931 if (m_currentIndex.isValid())
1932 updateDanger(m_currentIndex, true);
1933}
1934
1935void MainWindow::updateIcons()
1936{
1937 const QString prefix = isDarkMode() ? ":/images/darkicons/"_L1: ":/images/lighticons/"_L1;
1938 auto getIcon = [&prefix](const QString &name) {
1939 QIcon icon;
1940 icon.addPixmap(QPixmap(prefix + name + QStringLiteral(".png")), QIcon::Normal);
1941 icon.addPixmap(QPixmap(prefix + name + QStringLiteral("-disabled.png")), QIcon::Disabled);
1942 return icon;
1943 };
1944
1945 QIcon openIcon = getIcon("open-new"_L1);
1946 m_ui.actionOpen->setIcon(openIcon);
1947 m_ui.actionOpenAux->setIcon(openIcon);
1948 QIcon saveIcon = getIcon("save-fl-disk"_L1);
1949 m_ui.actionSave->setIcon(saveIcon);
1950 m_ui.actionSaveAll->setIcon(saveIcon);
1951 m_ui.actionPrint->setIcon(getIcon("print"_L1));
1952 m_ui.actionRedo->setIcon(getIcon("redo-arrow-right"_L1));
1953 m_ui.actionUndo->setIcon(getIcon("undo-arrow-left"_L1));
1954 m_ui.actionCut->setIcon(getIcon("cut"_L1));
1955 m_ui.actionCopy->setIcon(getIcon("copy-general"_L1));
1956 m_ui.actionPaste->setIcon(getIcon("paste-general"_L1));
1957 m_ui.actionFind->setIcon(getIcon("search-magnifier"_L1));
1958
1959 m_ui.actionAccelerators->setIcon(getIcon("/check-ampersands"_L1));
1960 m_ui.actionOpenPhraseBook->setIcon(getIcon("library"_L1));
1961 m_ui.actionDone->setIcon(getIcon("mark-current-translation-done"_L1));
1962 m_ui.actionDoneAndNext->setIcon(getIcon("mark-current-translation-done-move-to-next"_L1));
1963 m_ui.actionNext->setIcon(getIcon("next-translation-item"_L1));
1964 m_ui.actionNextUnfinished->setIcon(getIcon("next-unfinished-translation-item"_L1));
1965 m_ui.actionPhraseMatches->setIcon(getIcon("check-phrase-suggestions"_L1));
1966 m_ui.actionSurroundingWhitespace->setIcon(getIcon("check-white-spaces"_L1));
1967 m_ui.actionEndingPunctuation->setIcon(getIcon("check-ending-pontuation"_L1));
1968 m_ui.actionPrev->setIcon(getIcon("previous-translation-item"_L1));
1969 m_ui.actionPrevUnfinished->setIcon(getIcon("previous-unfinished-translation-item"_L1));
1970 m_ui.actionPlaceMarkerMatches->setIcon(getIcon("check-place-markers"_L1));
1971 m_ui.actionWhatsThis->setIcon(getIcon("hit-help-chosen-option"_L1));
1972}
1973
1974void MainWindow::setupMenuBar()
1975{
1976 m_ui.menuRecentlyOpenedFiles->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::DocumentOpenRecent));
1977 m_ui.actionCloseAll->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::WindowClose));
1978 m_ui.actionExit->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit));
1979 m_ui.actionSelectAll->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::EditSelectAll));
1980 updateIcons();
1981
1982 // File menu
1983 connect(m_ui.menuFile, &QMenu::aboutToShow, this, &MainWindow::fileAboutToShow);
1984 connect(m_ui.actionOpen, &QAction::triggered, this, &MainWindow::open);
1985 connect(m_ui.actionOpenAux, &QAction::triggered, this, &MainWindow::openAux);
1986 connect(m_ui.actionSave, &QAction::triggered, this, &MainWindow::save);
1987#ifndef Q_OS_WASM
1988 connect(m_ui.actionSaveAll, &QAction::triggered, this, &MainWindow::saveAll);
1989 connect(m_ui.actionSaveAs, &QAction::triggered, this, &MainWindow::saveAs);
1990#else
1991 m_ui.actionSaveAs->setVisible(false);
1992 m_ui.actionSaveAll->setVisible(false);
1993#endif // Q_OS_WASM
1994 connect(m_ui.actionReleaseAll, &QAction::triggered, this, &MainWindow::releaseAll);
1995 connect(m_ui.actionRelease, &QAction::triggered, this, &MainWindow::release);
1996 connect(m_ui.actionReleaseAs, &QAction::triggered, this, &MainWindow::releaseAs);
1997#if QT_CONFIG(printsupport)
1998 connect(m_ui.actionPrint, &QAction::triggered, this, &MainWindow::print);
1999#else
2000 m_ui.actionPrint->setEnabled(false);
2001#endif
2002 connect(m_ui.actionClose, &QAction::triggered, this, &MainWindow::closeFile);
2003 connect(m_ui.actionCloseAll, &QAction::triggered, this, &MainWindow::closeAll);
2004 connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close);
2005
2006 // Edit menu
2007 connect(m_ui.menuEdit, &QMenu::aboutToShow, this, &MainWindow::editAboutToShow);
2008
2009 connect(m_ui.actionUndo, &QAction::triggered, m_messageEditor, &MessageEditor::undo);
2010 connect(m_messageEditor, &MessageEditor::undoAvailable, m_ui.actionUndo, &QAction::setEnabled);
2011
2012 connect(m_ui.actionRedo, &QAction::triggered, m_messageEditor, &MessageEditor::redo);
2013 connect(m_messageEditor, &MessageEditor::redoAvailable, m_ui.actionRedo, &QAction::setEnabled);
2014
2015#ifndef QT_NO_CLIPBOARD
2016 connect(m_ui.actionCut, &QAction::triggered, m_messageEditor, &MessageEditor::cut);
2017 connect(m_messageEditor, &MessageEditor::cutAvailable, m_ui.actionCut, &QAction::setEnabled);
2018
2019 connect(m_ui.actionCopy, &QAction::triggered, m_messageEditor, &MessageEditor::copy);
2020 connect(m_messageEditor, &MessageEditor::copyAvailable, m_ui.actionCopy, &QAction::setEnabled);
2021
2022 connect(m_ui.actionPaste, &QAction::triggered, m_messageEditor, &MessageEditor::paste);
2023 connect(m_messageEditor, &MessageEditor::pasteAvailable, m_ui.actionPaste, &QAction::setEnabled);
2024#endif
2025
2026 connect(m_ui.actionSelectAll, &QAction::triggered,
2027 m_messageEditor, &MessageEditor::selectAll);
2028 connect(m_ui.actionFind, &QAction::triggered,
2029 m_findDialog, &FindDialog::find);
2030 connect(m_ui.actionFindNext, &QAction::triggered,
2031 this, [this] {findAgain(FindNext);});
2032 connect(m_ui.actionFindPrev, &QAction::triggered,
2033 this, [this] {findAgain(FindPrev);});
2034 connect(m_ui.actionSearchAndTranslate, &QAction::triggered,
2035 this, &MainWindow::showTranslateDialog);
2036 connect(m_ui.actionBatchTranslation, &QAction::triggered,
2037 this, &MainWindow::showBatchTranslateDialog);
2038 connect(m_ui.actionTranslationFileSettings, &QAction::triggered,
2039 this, &MainWindow::showTranslationSettings);
2040
2041 connect(m_batchTranslateDialog, &BatchTranslationDialog::finished,
2042 this, &MainWindow::refreshItemViews);
2043
2044 // Translation menu
2045 // when updating the accelerators, remember the status bar
2046 connect(m_ui.actionAuto_Translation, &QAction::triggered, this,
2047 &MainWindow::openMachineTranslateDialog);
2048 connect(m_ui.actionPrevUnfinished, &QAction::triggered, this, &MainWindow::prevUnfinished);
2049 connect(m_ui.actionNextUnfinished, &QAction::triggered, this, &MainWindow::nextUnfinished);
2050 connect(m_ui.actionNext, &QAction::triggered, this, &MainWindow::next);
2051 connect(m_ui.actionPrev, &QAction::triggered, this, &MainWindow::prev);
2052 connect(m_ui.actionDone, &QAction::triggered, this, &MainWindow::done);
2053 connect(m_ui.actionDoneAndNext, &QAction::triggered, this, &MainWindow::doneAndNext);
2054 connect(m_ui.actionBeginFromSource, &QAction::triggered, m_messageEditor,
2055 &MessageEditor::beginFromSource);
2056
2057 // Phrasebook menu
2058 connect(m_ui.actionNewPhraseBook, &QAction::triggered, this, &MainWindow::newPhraseBook);
2059 connect(m_ui.actionOpenPhraseBook, &QAction::triggered, this, &MainWindow::openPhraseBook);
2060 connect(m_ui.menuClosePhraseBook, &QMenu::triggered,
2061 this, &MainWindow::closePhraseBook);
2062 connect(m_ui.menuEditPhraseBook, &QMenu::triggered,
2063 this, &MainWindow::editPhraseBook);
2064#if QT_CONFIG(printsupport)
2065 connect(m_ui.menuPrintPhraseBook, &QMenu::triggered,
2066 this, &MainWindow::printPhraseBook);
2067#else
2068 m_ui.menuPrintPhraseBook->setEnabled(false);
2069#endif
2070 connect(m_ui.actionAddToPhraseBook, &QAction::triggered,
2071 this, &MainWindow::addToPhraseBook);
2072
2073 // Validation menu
2074 connect(m_ui.actionAccelerators, &QAction::triggered, this, &MainWindow::revalidate);
2075 connect(m_ui.actionSurroundingWhitespace, &QAction::triggered, this, &MainWindow::revalidate);
2076 connect(m_ui.actionEndingPunctuation, &QAction::triggered, this, &MainWindow::revalidate);
2077 connect(m_ui.actionPhraseMatches, &QAction::triggered, this, &MainWindow::revalidate);
2078 connect(m_ui.actionPlaceMarkerMatches, &QAction::triggered, this, &MainWindow::revalidate);
2079
2080 // View menu
2081 connect(m_ui.actionResetSorting, &QAction::triggered,
2082 this, &MainWindow::resetSorting);
2083 connect(m_ui.actionDisplayGuesses, &QAction::triggered,
2084 m_phraseView, &PhraseView::toggleGuessing);
2085 connect(m_ui.actionStatistics, &QAction::triggered, this, &MainWindow::showStatistics);
2086 connect(m_ui.actionQmlPreview, &QAction::triggered, this, &MainWindow::toggleQmlPreview);
2087 connect(m_ui.actionVisualizeWhitespace, &QAction::triggered,
2088 this, &MainWindow::toggleVisualizeWhitespace);
2089 connect(m_ui.actionIncreaseZoom, &QAction::triggered,
2090 m_messageEditor, &MessageEditor::increaseFontSize);
2091 connect(m_ui.actionDecreaseZoom, &QAction::triggered,
2092 m_messageEditor, &MessageEditor::decreaseFontSize);
2093 connect(m_ui.actionResetZoomToDefault, &QAction::triggered,
2094 m_messageEditor, &MessageEditor::resetFontSize);
2095 connect(m_ui.actionShowMoreGuesses, &QAction::triggered,
2096 m_phraseView, &PhraseView::moreGuesses);
2097 connect(m_ui.actionShowFewerGuesses, &QAction::triggered,
2098 m_phraseView, &PhraseView::fewerGuesses);
2099 connect(m_phraseView, &PhraseView::showFewerGuessesAvailable,
2100 m_ui.actionShowFewerGuesses, &QAction::setEnabled);
2101 connect(m_ui.actionResetGuessesToDefault, &QAction::triggered,
2102 m_phraseView, &PhraseView::resetNumGuesses);
2103 m_ui.menuViewViews->addAction(m_contextAndLabelDock->toggleViewAction());
2104 m_ui.menuViewViews->addAction(m_messagesDock->toggleViewAction());
2105 m_ui.menuViewViews->addAction(m_phrasesDock->toggleViewAction());
2106 m_ui.menuViewViews->addAction(m_sourceAndFormDock->toggleViewAction());
2107 m_ui.menuViewViews->addAction(m_errorsDock->toggleViewAction());
2108
2109#if defined(Q_OS_MAC)
2110 // Window menu
2111 QMenu *windowMenu = new QMenu(tr("&Window"), this);
2112 menuBar()->insertMenu(m_ui.menuHelp->menuAction(), windowMenu);
2113 windowMenu->addAction(tr("Minimize"), QKeySequence(tr("Ctrl+M")),
2114 this, &QWidget::showMinimized);
2115#endif
2116
2117 // Help
2118 connect(m_ui.actionManual, &QAction::triggered, this, &MainWindow::manual);
2119 connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::about);
2120 connect(m_ui.actionAboutQt, &QAction::triggered, this, &MainWindow::aboutQt);
2121 connect(m_ui.actionWhatsThis, &QAction::triggered, this, &MainWindow::onWhatsThis);
2122
2123 connect(m_ui.menuRecentlyOpenedFiles, &QMenu::triggered,
2124 this, &MainWindow::recentFileActivated);
2125
2126 m_ui.actionManual->setToolTip(tr("Displays the manual for %1.").arg(tr("Qt Linguist")));
2127 m_ui.actionAbout->setToolTip(tr("Displays information about %1.").arg(tr("Qt Linguist")));
2128 m_ui.actionDone->setShortcuts(
2129 { Qt::AltModifier | Qt::Key_Return, Qt::AltModifier | Qt::Key_Enter });
2130 m_ui.actionDoneAndNext->setShortcuts({
2131 Qt::ControlModifier | Qt::Key_Return,
2132 Qt::ControlModifier | Qt::Key_Enter,
2133 });
2134
2135 // Disable the Close/Edit/Print phrasebook menuitems if they are not loaded
2136 connect(m_ui.menuPhrases, &QMenu::aboutToShow, this, &MainWindow::setupPhrase);
2137
2138 connect(m_ui.menuRecentlyOpenedFiles, &QMenu::aboutToShow,
2139 this, &MainWindow::setupRecentFilesMenu);
2140}
2141
2142void MainWindow::updateActiveModel(int model)
2143{
2144 if (model >= 0)
2145 doUpdateLatestModel(model);
2146}
2147
2148// Arriving here implies that the messageEditor does not have focus
2149void MainWindow::updateLatestModel(const QModelIndex &index)
2150{
2151 if (index.column() && (index.column() - 1 < m_dataModel->modelCount()))
2152 doUpdateLatestModel(index.column() - 1);
2153}
2154
2155void MainWindow::doUpdateLatestModel(int model)
2156{
2157 m_currentIndex = MultiDataIndex(m_currentIndex.translationType(), model, m_currentIndex.group(),
2158 m_currentIndex.message());
2159 bool enable = false;
2160 bool enableRw = false;
2161 MessageItem *item = nullptr;
2162 if (model >= 0) {
2163 enable = true;
2164 if (m_dataModel->isModelWritable(model))
2165 enableRw = true;
2166 if (m_currentIndex.isValid()) {
2167 if ((item = m_dataModel->messageItem(m_currentIndex))) {
2168 if (enableRw && !item->isObsolete())
2169 m_phraseView->setSourceText(model, item->text());
2170 else
2171 m_phraseView->setSourceText(-1, QString());
2172 } else {
2173 m_phraseView->setSourceText(-1, QString());
2174 }
2175 }
2176 }
2177 updateSourceView(model, item);
2178 m_ui.actionSave->setEnabled(enableRw);
2179 m_ui.actionSaveAs->setEnabled(enableRw);
2180 m_ui.actionRelease->setEnabled(enableRw);
2181 m_ui.actionReleaseAs->setEnabled(enableRw);
2182 m_ui.actionClose->setEnabled(enable);
2183 m_ui.actionTranslationFileSettings->setEnabled(enableRw);
2184 m_ui.actionSearchAndTranslate->setEnabled(enableRw);
2185 // cut & paste - edit only
2186 updatePhraseBookActions();
2187 updateStatistics();
2188}
2189
2190void MainWindow::updateSourceView(int model, MessageItem *item)
2191{
2192 if (item && !item->fileName().isEmpty()) {
2193 if (hasUiFormPreview(item->fileName())) {
2194 m_sourceAndFormView->setCurrentWidget(m_uiFormPreviewView);
2195 m_uiFormPreviewView->setSourceContext(model, item);
2196 } else if (hasQmlFormPreview(item->fileName(), m_ui.actionQmlPreview->isChecked())
2197 && m_qmlFormPreviewView->setSourceContext(model, item)) {
2198 m_sourceAndFormView->setCurrentWidget(m_qmlFormPreviewView);
2199 } else {
2200 m_ui.actionQmlPreview->setChecked(false);
2201 m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2202 QDir dir = QFileInfo(m_dataModel->srcFileName(model)).dir();
2203 QString fileName = QDir::cleanPath(dir.absoluteFilePath(item->fileName()));
2204 m_sourceCodeView->setSourceContext(fileName, item->lineNumber());
2205 }
2206 } else {
2207 m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2208 m_sourceCodeView->setSourceContext(QString(), 0);
2209 }
2210}
2211
2212// Note for *AboutToShow: Due to the delayed nature, only actions without shortcuts
2213// and representations outside the menu may be setEnabled()/setVisible() here.
2214
2215void MainWindow::fileAboutToShow()
2216{
2217 if (m_fileActiveModel != m_currentIndex.model()) {
2218 // We rename the actions so the shortcuts need not be reassigned.
2219 bool en;
2220 if (m_dataModel->modelCount() > 1) {
2221 if (m_currentIndex.model() >= 0) {
2222 QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
2223#ifndef Q_OS_WASM
2224 m_ui.actionSave->setText(tr("&Save '%1'").arg(fn));
2225 m_ui.actionSaveAs->setText(tr("Save '%1' &As...").arg(fn));
2226#else
2227 m_ui.actionSave->setText(tr("&Download '%1'").arg(fn));
2228#endif // Q_OS_WASM
2229 m_ui.actionRelease->setText(tr("Release '%1'").arg(fn));
2230 m_ui.actionReleaseAs->setText(tr("Release '%1' As...").arg(fn));
2231 m_ui.actionClose->setText(tr("&Close '%1'").arg(fn));
2232 } else {
2233#ifndef Q_OS_WASM
2234 m_ui.actionSave->setText(tr("&Save"));
2235 m_ui.actionSaveAs->setText(tr("Save &As..."));
2236#else
2237 m_ui.actionSave->setText(tr("&Download"));
2238#endif // Q_OS_WASM
2239 m_ui.actionRelease->setText(tr("Release"));
2240 m_ui.actionReleaseAs->setText(tr("Release As..."));
2241 m_ui.actionClose->setText(tr("&Close"));
2242 }
2243
2244#ifndef Q_OS_WASM
2245 m_ui.actionSaveAll->setText(tr("Save All"));
2246#endif // Q_OS_WASM
2247 m_ui.actionReleaseAll->setText(tr("&Release All"));
2248 m_ui.actionCloseAll->setText(tr("Close All"));
2249 en = true;
2250 } else {
2251#ifndef Q_OS_WASM
2252 m_ui.actionSaveAs->setText(tr("Save &As..."));
2253 m_ui.actionSaveAll->setText(tr("&Save"));
2254#else
2255 m_ui.actionSave->setText(tr("&Download"));
2256#endif // Q_OS_WASM
2257 m_ui.actionReleaseAs->setText(tr("Release As..."));
2258 m_ui.actionReleaseAll->setText(tr("&Release"));
2259 m_ui.actionCloseAll->setText(tr("&Close"));
2260 en = false;
2261 }
2262#ifndef Q_OS_WASM
2263 m_ui.actionSave->setVisible(en);
2264#endif // Q_OS_WASM
2265 m_ui.actionRelease->setVisible(en);
2266 m_ui.actionClose->setVisible(en);
2267 m_fileActiveModel = m_currentIndex.model();
2268 }
2269}
2270
2271void MainWindow::editAboutToShow()
2272{
2273 if (m_editActiveModel != m_currentIndex.model()) {
2274 if (m_currentIndex.model() >= 0 && m_dataModel->modelCount() > 1) {
2275 QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
2276 m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings for '%1'...").arg(fn));
2277 m_ui.actionBatchTranslation->setText(tr("&Batch Translation of '%1'...").arg(fn));
2278 m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate in '%1'...").arg(fn));
2279 } else {
2280 m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings..."));
2281 m_ui.actionBatchTranslation->setText(tr("&Batch Translation..."));
2282 m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate..."));
2283 }
2284 m_editActiveModel = m_currentIndex.model();
2285 }
2286}
2287
2288void MainWindow::showContextDock()
2289{
2290 m_contextAndLabelDock->show();
2291 m_contextAndLabelDock->raise();
2292}
2293
2294void MainWindow::showMessagesDock()
2295{
2296 m_messagesDock->show();
2297 m_messagesDock->raise();
2298}
2299
2300void MainWindow::showPhrasesDock()
2301{
2302 m_phrasesDock->show();
2303 m_phrasesDock->raise();
2304}
2305
2306void MainWindow::showSourceCodeDock()
2307{
2308 m_sourceAndFormDock->show();
2309 m_sourceAndFormDock->raise();
2310}
2311
2312void MainWindow::showErrorDock()
2313{
2314 m_errorsDock->show();
2315 m_errorsDock->raise();
2316}
2317
2318void MainWindow::onWhatsThis()
2319{
2320 QWhatsThis::enterWhatsThisMode();
2321}
2322
2323void MainWindow::setupToolBars()
2324{
2325 QToolBar *filet = new QToolBar(this);
2326 filet->setObjectName("FileToolbar");
2327 filet->setWindowTitle(tr("File"));
2328 this->addToolBar(filet);
2329 m_ui.menuToolbars->addAction(filet->toggleViewAction());
2330
2331 QToolBar *editt = new QToolBar(this);
2332 editt->setVisible(false);
2333 editt->setObjectName("EditToolbar");
2334 editt->setWindowTitle(tr("Edit"));
2335 this->addToolBar(editt);
2336 m_ui.menuToolbars->addAction(editt->toggleViewAction());
2337
2338 QToolBar *translationst = new QToolBar(this);
2339 translationst->setObjectName("TranslationToolbar");
2340 translationst->setWindowTitle(tr("Translation"));
2341 this->addToolBar(translationst);
2342 m_ui.menuToolbars->addAction(translationst->toggleViewAction());
2343
2344 QToolBar *validationt = new QToolBar(this);
2345 validationt->setObjectName("ValidationToolbar");
2346 validationt->setWindowTitle(tr("Validation"));
2347 this->addToolBar(validationt);
2348 m_ui.menuToolbars->addAction(validationt->toggleViewAction());
2349
2350 QToolBar *helpt = new QToolBar(this);
2351 helpt->setVisible(false);
2352 helpt->setObjectName("HelpToolbar");
2353 helpt->setWindowTitle(tr("Help"));
2354 this->addToolBar(helpt);
2355 m_ui.menuToolbars->addAction(helpt->toggleViewAction());
2356
2357
2358 filet->addAction(m_ui.actionOpen);
2359 filet->addAction(m_ui.actionSaveAll);
2360 filet->addAction(m_ui.actionPrint);
2361 filet->addSeparator();
2362 filet->addAction(m_ui.actionOpenPhraseBook);
2363
2364 editt->addAction(m_ui.actionUndo);
2365 editt->addAction(m_ui.actionRedo);
2366 editt->addSeparator();
2367 editt->addAction(m_ui.actionCut);
2368 editt->addAction(m_ui.actionCopy);
2369 editt->addAction(m_ui.actionPaste);
2370 editt->addSeparator();
2371 editt->addAction(m_ui.actionFind);
2372
2373 translationst->addAction(m_ui.actionPrev);
2374 translationst->addAction(m_ui.actionNext);
2375 translationst->addAction(m_ui.actionPrevUnfinished);
2376 translationst->addAction(m_ui.actionNextUnfinished);
2377 translationst->addAction(m_ui.actionDone);
2378 translationst->addAction(m_ui.actionDoneAndNext);
2379
2380 validationt->addAction(m_ui.actionAccelerators);
2381 validationt->addAction(m_ui.actionSurroundingWhitespace);
2382 validationt->addAction(m_ui.actionEndingPunctuation);
2383 validationt->addAction(m_ui.actionPhraseMatches);
2384 validationt->addAction(m_ui.actionPlaceMarkerMatches);
2385
2386 helpt->addAction(m_ui.actionWhatsThis);
2387}
2388
2389QModelIndex MainWindow::setMessageViewRoot(const QModelIndex &index)
2390{
2391 const QModelIndex &sortedGroupIndex = m_activeSortedMessagesModel->mapFromSource(index);
2392 const QModelIndex &trueGroupIndex =
2393 m_activeSortedMessagesModel->index(sortedGroupIndex.row(), 0);
2394 if (m_messageView->rootIndex() != trueGroupIndex)
2395 m_messageView->setRootIndex(trueGroupIndex);
2396 return trueGroupIndex;
2397}
2398
2399/*
2400 * Updates the selected entries in the context and message views.
2401 */
2402void MainWindow::setCurrentMessage(const QModelIndex &index)
2403{
2404 const QModelIndex &groupIndex = m_activeMessageModel->parent(index);
2405 if (!groupIndex.isValid())
2406 return;
2407
2408 const QModelIndex &trueIndex =
2409 m_activeMessageModel->index(groupIndex.row(), index.column(), QModelIndex());
2410 m_settingCurrentMessage = true;
2411 QTreeView *view = *m_activeTranslationType == IDBASED ? m_labelView : m_contextView;
2412 view->setCurrentIndex(m_activeSortedGroupsModel->mapFromSource(trueIndex));
2413 m_settingCurrentMessage = false;
2414 setMessageViewRoot(groupIndex);
2415 m_messageView->setCurrentIndex(m_activeSortedMessagesModel->mapFromSource(index));
2416}
2417
2418void MainWindow::setCurrentMessage(const QModelIndex &index, int model)
2419{
2420 const QModelIndex &theIndex =
2421 m_activeMessageModel->index(index.row(), model + 1, index.parent());
2422 setCurrentMessage(theIndex);
2423 m_messageEditor->setEditorFocusForModel(model);
2424}
2425
2426void MainWindow::setCurrentMessageFromGuess(int modelIndex, const Candidate &cand)
2427{
2428 if (cand.context.isEmpty()) {
2429 int labelIndex = m_dataModel->findGroupIndex(cand.label, IDBASED);
2430 int messageIndex =
2431 m_dataModel->multiGroupItem(labelIndex, IDBASED)->findMessageById(cand.id);
2432 setCurrentMessage(m_activeMessageModel->modelIndex(
2433 MultiDataIndex(IDBASED, modelIndex, labelIndex, messageIndex)));
2434 } else {
2435 int contextIndex = m_dataModel->findGroupIndex(cand.context, TEXTBASED);
2436 int messageIndex = m_dataModel->multiGroupItem(contextIndex, TEXTBASED)
2437 ->findMessage(cand.source, cand.disambiguation);
2438 setCurrentMessage(m_activeMessageModel->modelIndex(
2439 MultiDataIndex(TEXTBASED, modelIndex, contextIndex, messageIndex)));
2440 }
2441}
2442
2443void MainWindow::contextAndLabelTabChanged()
2444{
2445 auto refreshMessageView = [this](QTreeView *view) {
2446 m_messageView->reset();
2447 m_messageView->setModel(m_activeSortedMessagesModel);
2448 view->setCurrentIndex(m_activeSortedGroupsModel->index(0, 0));
2449 connect(m_messageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
2450 &MainWindow::selectedMessageChanged);
2451 connect(m_messageView->selectionModel(), &QItemSelectionModel::currentColumnChanged, this,
2452 &MainWindow::updateLatestModel);
2453 m_messageView->update();
2454 if (m_activeMessageModel->rowCount())
2455 setCurrentMessage(m_activeMessageModel->modelIndex(
2456 MultiDataIndex(*m_activeTranslationType, 0, 0, 0)));
2457 selectedMessageChanged(m_messageView->currentIndex(), QModelIndex{});
2458 updateVisibleColumns();
2459 };
2460
2461 if (m_contextAndLabelView->currentWidget() == m_labelView
2462 && (!m_activeTranslationType || *m_activeTranslationType != IDBASED)) {
2463 m_activeTranslationType.emplace(IDBASED);
2464 m_activeSortedMessagesModel = m_sortedIdBasedMessagesModel;
2465 m_activeSortedGroupsModel = m_sortedLabelsModel;
2466 m_activeMessageModel = m_idBasedMessageModel;
2467 refreshMessageView(m_labelView);
2468 } else if (m_contextAndLabelView->currentWidget() == m_contextView
2469 && (!m_activeTranslationType || *m_activeTranslationType != TEXTBASED)) {
2470 m_activeTranslationType.emplace(TEXTBASED);
2471 m_activeSortedMessagesModel = m_sortedTextBasedMessagesModel;
2472 m_activeSortedGroupsModel = m_sortedContextsModel;
2473 m_activeMessageModel = m_textBasedMessageModel;
2474 refreshMessageView(m_contextView);
2475 }
2476}
2477
2478void MainWindow::updateVisibleColumns()
2479{
2480 int cols = m_dataModel->modelCount() + 2;
2481 if (*m_activeTranslationType == IDBASED)
2482 cols++;
2483 for (int i = 1; i < cols; i++)
2484 m_messageView->setColumnHidden(i, false);
2485 for (int i = cols; i < m_messageView->header()->count(); i++)
2486 m_messageView->setColumnHidden(i, true);
2487 m_messageView->header()->setStretchLastSection(true);
2488}
2489
2490QModelIndex MainWindow::currentMessageIndex() const
2491{
2492 return m_activeSortedMessagesModel->mapToSource(m_messageView->currentIndex());
2493}
2494
2495PhraseBook *MainWindow::doOpenPhraseBook(const QString& name)
2496{
2497 PhraseBook *pb = new PhraseBook();
2498 bool langGuessed;
2499 if (!pb->load(name, &langGuessed)) {
2500 QMessageBox::warning(this, tr("Qt Linguist"),
2501 tr("Cannot read from phrase book '%1'.").arg(name));
2502 delete pb;
2503 return 0;
2504 }
2505 if (langGuessed) {
2506 if (!m_translationSettingsDialog)
2507 m_translationSettingsDialog = new TranslationSettingsDialog(this);
2508 m_translationSettingsDialog->setPhraseBook(pb);
2509 m_translationSettingsDialog->exec();
2510 }
2511
2512 m_phraseBooks.append(pb);
2513
2514 QAction *a = m_ui.menuClosePhraseBook->addAction(pb->friendlyPhraseBookName());
2515 m_phraseBookMenu[PhraseCloseMenu].insert(a, pb);
2516 a->setToolTip(tr("Close this phrase book."));
2517
2518 a = m_ui.menuEditPhraseBook->addAction(pb->friendlyPhraseBookName());
2519 m_phraseBookMenu[PhraseEditMenu].insert(a, pb);
2520 a->setToolTip(tr("Enables you to add, modify, or delete"
2521 " entries in this phrase book."));
2522
2523 a = m_ui.menuPrintPhraseBook->addAction(pb->friendlyPhraseBookName());
2524 m_phraseBookMenu[PhrasePrintMenu].insert(a, pb);
2525 a->setToolTip(tr("Print the entries in this phrase book."));
2526
2527 connect(pb, &PhraseBook::listChanged, this, &MainWindow::updatePhraseDicts);
2528 updatePhraseDicts();
2529 updatePhraseBookActions();
2530
2531 return pb;
2532}
2533
2534bool MainWindow::savePhraseBook(QString *name, PhraseBook &pb)
2535{
2536 if (!name->contains(u'.'))
2537 *name += ".qph"_L1;
2538
2539 if (!pb.save(*name)) {
2540 QMessageBox::warning(this, tr("Qt Linguist"),
2541 tr("Cannot create phrase book '%1'.").arg(*name));
2542 return false;
2543 }
2544 return true;
2545}
2546
2547bool MainWindow::maybeSavePhraseBook(PhraseBook *pb)
2548{
2549 if (pb->isModified())
2550 switch (QMessageBox::information(this, tr("Qt Linguist"),
2551 tr("Do you want to save phrase book '%1'?").arg(pb->friendlyPhraseBookName()),
2552 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes))
2553 {
2554 case QMessageBox::Cancel:
2555 return false;
2556 case QMessageBox::Yes:
2557 if (!pb->save(pb->fileName()))
2558 return false;
2559 break;
2560 default:
2561 break;
2562 }
2563 return true;
2564}
2565
2566bool MainWindow::maybeSavePhraseBooks()
2567{
2568 for (PhraseBook *phraseBook : std::as_const(m_phraseBooks))
2569 if (!maybeSavePhraseBook(phraseBook))
2570 return false;
2571 return true;
2572}
2573
2574void MainWindow::updateProgress()
2575{
2576 int numEditable = m_dataModel->getNumEditable();
2577 int numFinished = m_dataModel->getNumFinished();
2578 if (!m_dataModel->modelCount()) {
2579 m_progressLabel->setText(QString(" "_L1));
2580 m_progressLabel->setToolTip(QString());
2581 } else {
2582 m_progressLabel->setText(QStringLiteral(" %1/%2 ").arg(numFinished).arg(numEditable));
2583 m_progressLabel->setToolTip(tr("%n unfinished message(s) left.", 0,
2584 numEditable - numFinished));
2585 }
2586 bool enable = numFinished != numEditable;
2587 m_ui.actionPrevUnfinished->setEnabled(enable);
2588 m_ui.actionNextUnfinished->setEnabled(enable);
2589 m_ui.actionDone->setEnabled(enable);
2590 m_ui.actionDoneAndNext->setEnabled(enable);
2591
2592 m_ui.actionPrev->setEnabled(m_dataModel->contextCount() > 0 || m_dataModel->labelCount() > 0);
2593 m_ui.actionNext->setEnabled(m_dataModel->contextCount() > 0 || m_dataModel->labelCount() > 0);
2594}
2595
2596void MainWindow::updatePhraseBookActions()
2597{
2598 bool phraseBookLoaded = (m_currentIndex.model() >= 0) && !m_phraseBooks.isEmpty();
2599 m_ui.actionBatchTranslation->setEnabled(m_dataModel->contextCount() > 0 && phraseBookLoaded
2600 && m_dataModel->isModelWritable(m_currentIndex.model()));
2601 m_ui.actionAddToPhraseBook->setEnabled(currentMessageIndex().isValid() && phraseBookLoaded);
2602}
2603
2604void MainWindow::updatePhraseDictInternal(int model)
2605{
2606 QHash<QString, QList<Phrase *> > &pd = m_phraseDict[model];
2607
2608 pd.clear();
2609 for (PhraseBook *pb : std::as_const(m_phraseBooks)) {
2610 bool before;
2611 if (pb->language() != QLocale::C && m_dataModel->language(model) != QLocale::C) {
2612 if (pb->language() != m_dataModel->language(model))
2613 continue;
2614 before = (pb->territory() == m_dataModel->model(model)->territory());
2615 } else {
2616 before = false;
2617 }
2618 const auto phrases = pb->phrases();
2619 for (Phrase *p : phrases) {
2620 QString f = friendlyString(p->source());
2621 if (f.size() > 0) {
2622 f = f.split(u' ').first();
2623 if (!pd.contains(f)) {
2624 pd.insert(f, QList<Phrase *>());
2625 }
2626 if (before)
2627 pd[f].prepend(p);
2628 else
2629 pd[f].append(p);
2630 }
2631 }
2632 }
2633}
2634
2635void MainWindow::updatePhraseDict(int model)
2636{
2637 updatePhraseDictInternal(model);
2638 m_phraseView->update();
2639}
2640
2641void MainWindow::updatePhraseDicts()
2642{
2643 for (int i = 0; i < m_phraseDict.size(); ++i)
2644 if (!m_dataModel->isModelWritable(i))
2645 m_phraseDict[i].clear();
2646 else
2647 updatePhraseDictInternal(i);
2648 revalidate();
2649 m_phraseView->update();
2650}
2651
2652void MainWindow::updateDanger(const MultiDataIndex &index, bool verbose)
2653{
2654 MultiDataIndex curIdx = index;
2655 m_errorsView->clear();
2656
2657 QString source;
2658
2659 Validator::Checks checks{ m_ui.actionAccelerators->isChecked(),
2660 m_ui.actionEndingPunctuation->isChecked(),
2661 m_ui.actionPlaceMarkerMatches->isChecked(),
2662 m_ui.actionSurroundingWhitespace->isChecked(),
2663 m_ui.actionPhraseMatches->isChecked() };
2664
2665 for (int mi = 0; mi < m_dataModel->modelCount(); ++mi) {
2666 if (!m_dataModel->isModelWritable(mi))
2667 continue;
2668 curIdx.setModel(mi);
2669 MessageItem *m = m_dataModel->messageItem(curIdx);
2670 if (!m || m->isObsolete())
2671 continue;
2672
2673 bool danger = false;
2675 if (source.isEmpty()) {
2676 source = m->pluralText();
2677 if (source.isEmpty())
2678 source = m->text();
2679 }
2680
2681 Validator validator = Validator::fromSource(
2682 source, checks, m_dataModel->sourceLanguage(mi), m_phraseDict[mi]);
2683 const auto errors =
2684 validator.validate(m->translations(), m->message(), m_dataModel->language(mi),
2685 m_dataModel->model(mi)->countRefNeeds());
2686 if (verbose)
2687 for (const auto &[error, message] : errors.asKeyValueRange())
2688 m_errorsView->addError(mi, error, message);
2689 }
2690
2691 if (danger != m->danger())
2692 m_dataModel->setDanger(curIdx, danger);
2693 }
2694
2695 if (verbose)
2696 statusBar()->showMessage(m_errorsView->firstError());
2697}
2698
2700{
2701 QSettings config;
2702
2703 restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray());
2704 restoreState(config.value(settingPath("MainWindowState")).toByteArray());
2705
2706 m_ui.actionAccelerators->setChecked(
2707 config.value(settingPath("Validators/Accelerator"), true).toBool());
2708 m_ui.actionSurroundingWhitespace->setChecked(
2709 config.value(settingPath("Validators/SurroundingWhitespace"), true).toBool());
2710 m_ui.actionEndingPunctuation->setChecked(
2711 config.value(settingPath("Validators/EndingPunctuation"), true).toBool());
2712 m_ui.actionPhraseMatches->setChecked(
2713 config.value(settingPath("Validators/PhraseMatch"), true).toBool());
2714 m_ui.actionPlaceMarkerMatches->setChecked(
2715 config.value(settingPath("Validators/PlaceMarkers"), true).toBool());
2716 m_ui.actionLengthVariants->setChecked(
2717 config.value(settingPath("Options/LengthVariants"), false).toBool());
2718 m_ui.actionVisualizeWhitespace->setChecked(
2719 config.value(settingPath("Options/VisualizeWhitespace"), true).toBool());
2720
2721 m_messageEditor->setFontSize(
2722 config.value(settingPath("Options/EditorFontsize"), font().pointSize()).toReal());
2723 m_phraseView->setMaxCandidates(config.value(settingPath("Options/NumberOfGuesses"),
2724 PhraseView::getDefaultMaxCandidates()).toInt());
2725
2726 m_recentFiles.readConfig();
2727
2728 int size = config.beginReadArray(settingPath("OpenedPhraseBooks"));
2729 for (int i = 0; i < size; ++i) {
2730 config.setArrayIndex(i);
2731 doOpenPhraseBook(config.value("FileName"_L1).toString());
2732 }
2733 config.endArray();
2734}
2735
2737{
2738 QSettings config;
2739 config.setValue(settingPath("Geometry/WindowGeometry"),
2740 saveGeometry());
2741 config.setValue(settingPath("Validators/Accelerator"),
2742 m_ui.actionAccelerators->isChecked());
2743 config.setValue(settingPath("Validators/SurroundingWhitespace"),
2744 m_ui.actionSurroundingWhitespace->isChecked());
2745 config.setValue(settingPath("Validators/EndingPunctuation"),
2746 m_ui.actionEndingPunctuation->isChecked());
2747 config.setValue(settingPath("Validators/PhraseMatch"),
2748 m_ui.actionPhraseMatches->isChecked());
2749 config.setValue(settingPath("Validators/PlaceMarkers"),
2750 m_ui.actionPlaceMarkerMatches->isChecked());
2751 config.setValue(settingPath("Options/LengthVariants"),
2752 m_ui.actionLengthVariants->isChecked());
2753 config.setValue(settingPath("Options/VisualizeWhitespace"),
2754 m_ui.actionVisualizeWhitespace->isChecked());
2755 config.setValue(settingPath("MainWindowState"),
2756 saveState());
2757 m_recentFiles.writeConfig();
2758
2759 config.setValue(settingPath("Options/EditorFontsize"), m_messageEditor->fontSize());
2760 config.setValue(settingPath("Options/NumberOfGuesses"), m_phraseView->getMaxCandidates());
2761
2762 config.beginWriteArray(settingPath("OpenedPhraseBooks"),
2763 m_phraseBooks.size());
2764 for (int i = 0; i < m_phraseBooks.size(); ++i) {
2765 config.setArrayIndex(i);
2766 config.setValue("FileName"_L1, m_phraseBooks.at(i)->fileName());
2767 }
2768 config.endArray();
2769}
2770
2771void MainWindow::setupRecentFilesMenu()
2772{
2773 m_ui.menuRecentlyOpenedFiles->clear();
2774 for (const QStringList &strList : m_recentFiles.filesLists())
2775 if (strList.size() == 1) {
2776 const QString &str = strList.first();
2777 m_ui.menuRecentlyOpenedFiles->addAction(
2778 DataModel::prettifyFileName(str))->setData(str);
2779 } else {
2780 QMenu *menu = m_ui.menuRecentlyOpenedFiles->addMenu(
2781 MultiDataModel::condenseFileNames(
2782 MultiDataModel::prettifyFileNames(strList)));
2783 menu->addAction(tr("All"))->setData(strList);
2784 for (const QString &str : strList)
2785 menu->addAction(DataModel::prettifyFileName(str))->setData(str);
2786 }
2787}
2788
2789void MainWindow::recentFileActivated(QAction *action)
2790{
2791 openFiles(action->data().toStringList());
2792}
2793
2794void MainWindow::showStatistics()
2795{
2796 if (!m_statistics) {
2797 m_statistics = new Statistics(this);
2798 connect(m_dataModel, &MultiDataModel::statsChanged, m_statistics, &Statistics::updateStats);
2799 }
2800 m_statistics->show();
2801 updateStatistics();
2802}
2803
2804void MainWindow::toggleQmlPreview()
2805{
2806 if (m_ui.actionQmlPreview->isChecked())
2807 m_sourceAndFormView->setCurrentWidget(m_qmlFormPreviewView);
2808 else
2809 m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2810}
2811
2812void MainWindow::toggleVisualizeWhitespace()
2813{
2814 m_messageEditor->setVisualizeWhitespace(m_ui.actionVisualizeWhitespace->isChecked());
2815}
2816
2817void MainWindow::maybeUpdateStatistics(const MultiDataIndex &index)
2818{
2819 if (index.model() == m_currentIndex.model())
2820 updateStatistics();
2821}
2822
2823void MainWindow::updateStatistics()
2824{
2825 // don't call this if stats dialog is not open
2826 // because this can be slow...
2827 if (!m_statistics || !m_statistics->isVisible() || m_currentIndex.model() < 0)
2828 return;
2829
2830 m_dataModel->model(m_currentIndex.model())->updateStatistics();
2831}
2832
2833void MainWindow::doShowTranslationSettings(int model)
2834{
2835 if (!m_translationSettingsDialog)
2836 m_translationSettingsDialog = new TranslationSettingsDialog(this);
2837 m_translationSettingsDialog->setDataModel(m_dataModel->model(model));
2838 m_translationSettingsDialog->exec();
2839}
2840
2841void MainWindow::showTranslationSettings()
2842{
2843 doShowTranslationSettings(m_currentIndex.model());
2844}
2845
2846bool MainWindow::eventFilter(QObject *object, QEvent *event)
2847{
2848 if (event->type() == QEvent::DragEnter) {
2849 QDragEnterEvent *e = static_cast<QDragEnterEvent*>(event);
2850 if (e->mimeData()->hasFormat("text/uri-list"_L1)) {
2851 e->acceptProposedAction();
2852 return true;
2853 }
2854 } else if (event->type() == QEvent::Drop) {
2855 QDropEvent *e = static_cast<QDropEvent*>(event);
2856 if (!e->mimeData()->hasFormat("text/uri-list"_L1))
2857 return false;
2858 QStringList urls;
2859 for (const QUrl &url : e->mimeData()->urls())
2860 if (!url.toLocalFile().isEmpty())
2861 urls << url.toLocalFile();
2862 if (!urls.isEmpty())
2863 openFiles(urls);
2864 e->acceptProposedAction();
2865 return true;
2866 } else if (event->type() == QEvent::KeyPress) {
2867 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
2868 if (ke->key() == Qt::Key_Escape) {
2869 if (object == m_messageEditor)
2870 m_messageView->setFocus();
2871 else if (object == m_messagesDock)
2872 m_contextAndLabelView->currentWidget()->setFocus();
2873 } else if ((ke->key() == Qt::Key_Plus || ke->key() == Qt::Key_Equal)
2874 && (ke->modifiers() & Qt::ControlModifier)) {
2875 m_messageEditor->increaseFontSize();
2876 } else if (ke->key() == Qt::Key_Minus
2877 && (ke->modifiers() & Qt::ControlModifier)) {
2878 m_messageEditor->decreaseFontSize();
2879 }
2880 } else if (event->type() == QEvent::Wheel) {
2881 QWheelEvent *we = static_cast<QWheelEvent *>(event);
2882 if (we->modifiers() & Qt::ControlModifier) {
2883 if (we->angleDelta().y() > 0)
2884 m_messageEditor->increaseFontSize();
2885 else
2886 m_messageEditor->decreaseFontSize();
2887 }
2888 } else if (event->type() == QEvent::ApplicationPaletteChange) {
2889 m_dataModel->updateColors();
2890 updateIcons();
2891 }
2892 return QMainWindow::eventFilter(object, event);
2893}
2894
2895QT_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
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()
int activeModel() const
void setEditorFocusForModel(int model)
void setUnfinishedEditorFocus()
bool focusNextUnfinished()
void activeModelChanged(int model)
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 translationChanged(const MultiDataIndex &index)
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 update()
void setCurrentMessageFromGuess(int modelIndex, const Candidate &cand)
Definition qlist.h:80
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)
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