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

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