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

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