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