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 
87 QT_BEGIN_NAMESPACE
88 
89 static const int MessageMS = 2500;
90 
91 enum Ending {
92     End_None,
93     End_FullStop,
94     End_Interrobang,
95     End_Colon,
96     End_Ellipsis
97 };
98 
hasFormPreview(const QString & fileName)99 static bool hasFormPreview(const QString &fileName)
100 {
101     return fileName.endsWith(QLatin1String(".ui"))
102       || fileName.endsWith(QLatin1String(".jui"));
103 }
104 
leadingWhitespace(const QString & str)105 static 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(i);
114 }
115 
trailingWhitespace(const QString & str)116 static 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(i + 1);
125 }
126 
ending(QString str,QLocale::Language lang)127 static 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(str.length() - 1).unicode()) {
134     case 0x002e: // full stop
135         if (str.endsWith(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 
171 class ContextItemDelegate : public QItemDelegate
172 {
173 public:
ContextItemDelegate(QObject * parent,MultiDataModel * model)174     ContextItemDelegate(QObject *parent, MultiDataModel *model) : QItemDelegate(parent), m_dataModel(model) {}
175 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const176     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(index).isValid()) {
183             if (index.column() - 1 == m_dataModel->modelCount()) {
184                 QStyleOptionViewItem opt = option;
185                 opt.font.setBold(true);
186                 QItemDelegate::paint(painter, opt, index);
187                 return;
188             }
189         }
190         QItemDelegate::paint(painter, option, index);
191     }
192 
193 private:
194     MultiDataModel *m_dataModel;
195 };
196 
pxObsolete()197 static const QVariant &pxObsolete()
198 {
199     static const QVariant v =
200         QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_obsolete.png")));
201     return v;
202 }
203 
204 
205 class SortedMessagesModel : public QSortFilterProxyModel
206 {
207 public:
SortedMessagesModel(QObject * parent,MultiDataModel * model)208     SortedMessagesModel(QObject *parent, MultiDataModel *model) : QSortFilterProxyModel(parent), m_dataModel(model) {}
209 
headerData(int section,Qt::Orientation orientation,int role) const210     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("Source text");
216                 case 2: return MainWindow::tr("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 
225 private:
226     MultiDataModel *m_dataModel;
227 };
228 
229 class SortedContextsModel : public QSortFilterProxyModel
230 {
231 public:
SortedContextsModel(QObject * parent,MultiDataModel * model)232     SortedContextsModel(QObject *parent, MultiDataModel *model) : QSortFilterProxyModel(parent), m_dataModel(model) {}
233 
headerData(int section,Qt::Orientation orientation,int role) const234     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("Context");
240                 case 2: return MainWindow::tr("Items");
241                 case 3: return MainWindow::tr("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 
250 private:
251     MultiDataModel *m_dataModel;
252 };
253 
254 class FocusWatcher : public QObject
255 {
256 public:
FocusWatcher(MessageEditor * msgedit,QObject * parent)257     FocusWatcher(MessageEditor *msgedit, QObject *parent) : QObject(parent), m_messageEditor(msgedit) {}
258 
259 protected:
260     bool eventFilter(QObject *object, QEvent *event);
261 
262 private:
263     MessageEditor *m_messageEditor;
264 };
265 
eventFilter(QObject *,QEvent * event)266 bool FocusWatcher::eventFilter(QObject *, QEvent *event)
267 {
268     if (event->type() == QEvent::FocusIn)
269         m_messageEditor->setEditorFocus(-1);
270     return false;
271 }
272 
MainWindow()273 MainWindow::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("Context"));
303     m_contextDock->setAcceptDrops(true);
304     m_contextDock->installEventFilter(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("This panel lists the source contexts."));
320     m_contextView->setModel(m_sortedContextsModel);
321     m_contextView->header()->setSectionsMovable(false);
322     m_contextView->setColumnHidden(0, 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("Strings"));
332     m_messagesDock->setAcceptDrops(true);
333     m_messagesDock->installEventFilter(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(0, 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(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("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("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(m_sourceCodeView);
384     m_sourceAndFormView->addWidget(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("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(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
397     setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
398     setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
399     setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
400     addDockWidget(Qt::LeftDockWidgetArea, m_contextDock);
401     addDockWidget(Qt::TopDockWidgetArea, m_messagesDock);
402     addDockWidget(Qt::BottomDockWidgetArea, m_phrasesDock);
403     addDockWidget(Qt::TopDockWidgetArea, m_sourceAndFormDock);
404     addDockWidget(Qt::BottomDockWidgetArea, 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(m_phraseView);
410 
411     // Set up shortcuts for the dock widgets
412     QShortcut *contextShortcut = new QShortcut(QKeySequence(Qt::Key_F6), this);
413     connect(contextShortcut, SIGNAL(activated()), this, SLOT(showContextDock()));
414     QShortcut *messagesShortcut = new QShortcut(QKeySequence(Qt::Key_F7), this);
415     connect(messagesShortcut, SIGNAL(activated()), this, SLOT(showMessagesDock()));
416     QShortcut *errorsShortcut = new QShortcut(QKeySequence(Qt::Key_F8), this);
417     connect(errorsShortcut, SIGNAL(activated()), this, SLOT(showErrorDock()));
418     QShortcut *sourceCodeShortcut = new QShortcut(QKeySequence(Qt::Key_F9), this);
419     connect(sourceCodeShortcut, SIGNAL(activated()), this, SLOT(showSourceCodeDock()));
420     QShortcut *phrasesShortcut = new QShortcut(QKeySequence(Qt::Key_F10), this);
421     connect(phrasesShortcut, SIGNAL(activated()), this, SLOT(showPhrasesDock()));
422 
423     connect(m_phraseView, SIGNAL(phraseSelected(int,QString)),
424             m_messageEditor, SLOT(setTranslation(int,QString)));
425     connect(m_phraseView, SIGNAL(setCurrentMessageFromGuess(int,Candidate)),
426             this, SLOT(setCurrentMessage(int,Candidate)));
427     connect(m_contextView->selectionModel(),
428             SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
429             this, SLOT(selectedContextChanged(QModelIndex,QModelIndex)));
430     connect(m_messageView->selectionModel(),
431             SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
432             this, SLOT(selectedMessageChanged(QModelIndex,QModelIndex)));
433     connect(m_contextView->selectionModel(),
434             SIGNAL(currentColumnChanged(QModelIndex,QModelIndex)),
435             SLOT(updateLatestModel(QModelIndex)));
436     connect(m_messageView->selectionModel(),
437             SIGNAL(currentColumnChanged(QModelIndex,QModelIndex)),
438             SLOT(updateLatestModel(QModelIndex)));
439 
440     connect(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(m_progressLabel);
451     m_modifiedLabel = new QLabel(tr(" MOD ", "status bar: file(s) modified"));
452     statusBar()->addPermanentWidget(m_modifiedLabel);
453 
454     modelCountChanged();
455     initViewHeaders();
456     resetSorting();
457 
458     connect(m_dataModel, SIGNAL(modifiedChanged(bool)),
459             this, SLOT(setWindowModified(bool)));
460     connect(m_dataModel, SIGNAL(modifiedChanged(bool)),
461             m_modifiedLabel, SLOT(setVisible(bool)));
462     connect(m_dataModel, SIGNAL(multiContextDataChanged(MultiDataIndex)),
463             SLOT(updateProgress()));
464     connect(m_dataModel, SIGNAL(messageDataChanged(MultiDataIndex)),
465             SLOT(maybeUpdateStatistics(MultiDataIndex)));
466     connect(m_dataModel, SIGNAL(translationChanged(MultiDataIndex)),
467             SLOT(translationChanged(MultiDataIndex)));
468     connect(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(m_messageView, SIGNAL(clicked(QModelIndex)),
475             this, SLOT(toggleFinished(QModelIndex)));
476     connect(m_messageView, SIGNAL(activated(QModelIndex)),
477             m_messageEditor, SLOT(setEditorFocus()));
478     connect(m_contextView, SIGNAL(activated(QModelIndex)),
479             m_messageView, SLOT(setFocus()));
480     connect(m_messageEditor, SIGNAL(translationChanged(QStringList)),
481             this, SLOT(updateTranslation(QStringList)));
482     connect(m_messageEditor, SIGNAL(translatorCommentChanged(QString)),
483             this, SLOT(updateTranslatorComment(QString)));
484     connect(m_findDialog, SIGNAL(findNext(QString,DataModel::FindLocation,bool,bool,bool,bool)),
485             this, SLOT(findNext(QString,DataModel::FindLocation,bool,bool,bool,bool)));
486     connect(m_translateDialog, SIGNAL(requestMatchUpdate(bool&)), SLOT(updateTranslateHit(bool&)));
487     connect(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(as));
492     show();
493     readConfig();
494     m_statistics = 0;
495 
496     connect(m_ui.actionLengthVariants, SIGNAL(toggled(bool)),
497             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(m_focusWatcher);
503     m_messageView->installEventFilter(m_focusWatcher);
504     m_messageEditor->installEventFilter(m_focusWatcher);
505     m_sourceAndFormView->installEventFilter(m_focusWatcher);
506     m_phraseView->installEventFilter(m_focusWatcher);
507     m_errorsView->installEventFilter(m_focusWatcher);
508 }
509 
~MainWindow()510 MainWindow::~MainWindow()
511 {
512     writeConfig();
513     if (m_assistantProcess && m_assistantProcess->state() == QProcess::Running) {
514         m_assistantProcess->terminate();
515         m_assistantProcess->waitForFinished(3000);
516     }
517     qDeleteAll(m_phraseBooks);
518     delete m_dataModel;
519     delete m_statistics;
520     delete m_printer;
521 }
522 
initViewHeaders()523 void MainWindow::initViewHeaders()
524 {
525     m_contextView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
526     m_contextView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
527     m_messageView->setColumnHidden(2, true);
528     // last visible column auto-stretches
529 }
530 
modelCountChanged()531 void MainWindow::modelCountChanged()
532 {
533     int mc = m_dataModel->modelCount();
534 
535     for (int i = 0; i < mc; ++i) {
536         m_contextView->header()->setSectionResizeMode(i + 1, QHeaderView::Fixed);
537         m_contextView->header()->resizeSection(i + 1, 24);
538 
539         m_messageView->header()->setSectionResizeMode(i + 1, QHeaderView::Fixed);
540         m_messageView->header()->resizeSection(i + 1, 24);
541     }
542 
543     if (!mc) {
544         selectedMessageChanged(QModelIndex(), QModelIndex());
545         updateLatestModel(-1);
546     } else {
547         if (!m_contextView->currentIndex().isValid()) {
548             // Ensure that something is selected
549             m_contextView->setCurrentIndex(m_sortedContextsModel->index(0, 0));
550         } else {
551             // Plug holes that turn up in the selection due to inserting columns
552             m_contextView->selectionModel()->select(m_contextView->currentIndex(),
553                         QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
554             m_messageView->selectionModel()->select(m_messageView->currentIndex(),
555                         QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
556         }
557         // Field insertions/removals are automatic, but not the re-fill
558         m_messageEditor->showMessage(m_currentIndex);
559         if (mc == 1)
560             updateLatestModel(0);
561         else if (m_currentIndex.model() >= mc)
562             updateLatestModel(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(-1, 0);
575 }
576 
577 struct OpenedFile {
OpenedFileOpenedFile578     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 
openFiles(const QStringList & names,bool globalReadWrite)585 bool MainWindow::openFiles(const QStringList &names, bool globalReadWrite)
586 {
587     if (names.isEmpty())
588         return false;
589 
590     bool waitCursor = false;
591     statusBar()->showMessage(tr("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(QLatin1Char('='))) {
604             name.remove(0, 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(name, &langGuessed, 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(this, tr("Loading File - Qt Linguist"),
624                     tr("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(DataModel::prettifyPlainFileName(name), m_dataModel->condensedSrcFileNames(true)),
627                     QMessageBox::Yes | QMessageBox::Default,
628                     QMessageBox::No,
629                     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(dm)) {
643                 QApplication::restoreOverrideCursor();
644                 waitCursor = false;
645                 switch (QMessageBox::information(this, tr("Loading File - Qt Linguist"),
646                     tr("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(DataModel::prettifyPlainFileName(name), opened.first().dataModel->srcFileName(true)),
650                     QMessageBox::Yes | QMessageBox::Default,
651                     QMessageBox::No,
652                     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(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(QHash<QString, QList<Phrase *> >());
702         m_dataModel->append(op.dataModel, op.readWrite);
703         if (op.readWrite)
704             updatePhraseDictInternal(m_phraseDict.size() - 1);
705         totalCount += op.dataModel->messageCount();
706     }
707     statusBar()->showMessage(tr("%n translation unit(s) loaded.", 0, totalCount), MessageMS);
708     modelCountChanged();
709     recentFiles().addFiles(m_dataModel->srcFileNames());
710 
711     revalidate();
712     QApplication::restoreOverrideCursor();
713     return true;
714 }
715 
recentFiles()716 RecentFiles &MainWindow::recentFiles()
717 {
718     static RecentFiles recentFiles(10);
719     return recentFiles;
720 }
721 
open()722 void MainWindow::open()
723 {
724     openFiles(pickTranslationFiles());
725 }
726 
openAux()727 void MainWindow::openAux()
728 {
729     openFiles(pickTranslationFiles(), false);
730 }
731 
closeFile()732 void MainWindow::closeFile()
733 {
734     int model = m_currentIndex.model();
735     if (model >= 0 && maybeSave(model)) {
736         m_phraseDict.removeAt(model);
737         m_contextView->setUpdatesEnabled(false);
738         m_messageView->setUpdatesEnabled(false);
739         m_dataModel->close(model);
740         modelCountChanged();
741     }
742 }
743 
closeAll()744 bool 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 
fileFilters(bool allFirst)759 static 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(pattern.arg(format.description(), format.extension));
767             allExtensions.append(QLatin1String("*.") + format.extension);
768         }
769     }
770     QString allFilter = QObject::tr("Translation files (%1);;").arg(allExtensions.join(QLatin1Char(' ')));
771     if (allFirst)
772         filter.prepend(allFilter);
773     else
774         filter.append(allFilter);
775     filter.append(QObject::tr("All files (*)"));
776     return filter;
777 }
778 
pickTranslationFiles()779 QStringList 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(0));
788         QString mainFileBase = mainFile.baseName();
789         int pos = mainFileBase.indexOf(QLatin1Char('_'));
790         if (pos > 0)
791             varFilt = tr("Related files (%1);;")
792                 .arg(mainFileBase.left(pos) + QLatin1String("_*.") + mainFile.completeSuffix());
793     }
794 
795     return QFileDialog::getOpenFileNames(this, tr("Open Translation Files"), dir,
796         varFilt +
797         fileFilters(true));
798 }
799 
saveInternal(int model)800 void MainWindow::saveInternal(int model)
801 {
802     QApplication::setOverrideCursor(Qt::WaitCursor);
803     if (m_dataModel->save(model, this)) {
804         updateCaption();
805         statusBar()->showMessage(tr("File saved."), MessageMS);
806     }
807     QApplication::restoreOverrideCursor();
808 }
809 
saveAll()810 void MainWindow::saveAll()
811 {
812     for (int i = 0; i < m_dataModel->modelCount(); ++i)
813         if (m_dataModel->isModelWritable(i))
814             saveInternal(i);
815     recentFiles().closeGroup();
816 }
817 
save()818 void MainWindow::save()
819 {
820     if (m_currentIndex.model() < 0)
821         return;
822 
823     saveInternal(m_currentIndex.model());
824 }
825 
saveAs()826 void MainWindow::saveAs()
827 {
828     if (m_currentIndex.model() < 0)
829         return;
830 
831     QString newFilename = QFileDialog::getSaveFileName(this, QString(), m_dataModel->srcFileName(m_currentIndex.model()),
832         fileFilters(false));
833     if (!newFilename.isEmpty()) {
834         if (m_dataModel->saveAs(m_currentIndex.model(), newFilename, this)) {
835             updateCaption();
836             statusBar()->showMessage(tr("File saved."), MessageMS);
837             recentFiles().addFiles(m_dataModel->srcFileNames());
838         }
839     }
840 }
841 
releaseAs()842 void MainWindow::releaseAs()
843 {
844     if (m_currentIndex.model() < 0)
845         return;
846 
847     QFileInfo oldFile(m_dataModel->srcFileName(m_currentIndex.model()));
848     QString newFilename = oldFile.path() + QLatin1String("/")
849                 + oldFile.completeBaseName() + QLatin1String(".qm");
850 
851     newFilename = QFileDialog::getSaveFileName(this, tr("Release"), newFilename,
852         tr("Qt message files for released applications (*.qm)\nAll files (*)"));
853     if (!newFilename.isEmpty()) {
854         if (m_dataModel->release(m_currentIndex.model(), newFilename, false, false, SaveEverything, this))
855             statusBar()->showMessage(tr("File created."), MessageMS);
856     }
857 }
858 
releaseInternal(int model)859 void 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, newFilename, false, false, SaveEverything, this))
867             statusBar()->showMessage(tr("File created."), MessageMS);
868     }
869 }
870 
871 // No-question
release()872 void MainWindow::release()
873 {
874     if (m_currentIndex.model() < 0)
875         return;
876 
877     releaseInternal(m_currentIndex.model());
878 }
879 
releaseAll()880 void MainWindow::releaseAll()
881 {
882     for (int i = 0; i < m_dataModel->modelCount(); ++i)
883         if (m_dataModel->isModelWritable(i))
884             releaseInternal(i);
885 }
886 
printer()887 QPrinter *MainWindow::printer()
888 {
889     if (!m_printer)
890         m_printer = new QPrinter;
891     return m_printer;
892 }
893 
print()894 void 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(true));
901         statusBar()->showMessage(tr("Printing..."));
902         PrintOut pout(printer());
903 
904         for (int i = 0; i < m_dataModel->contextCount(); ++i) {
905             MultiContextItem *mc = m_dataModel->multiContextItem(i);
906             pout.vskip();
907             pout.setRule(PrintOut::ThickRule);
908             pout.setGuide(mc->context());
909             pout.addBox(100, tr("Context: %1").arg(mc->context()),
910                 PrintOut::Strong);
911             pout.flushLine();
912             pout.addBox(4);
913             pout.addBox(92, mc->comment(), 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(k, j)) {
923                         if (!printedSrc) {
924                             pout.addBox(40, m->text());
925                             pout.addBox(4);
926                             comment = m->comment();
927                             printedSrc = true;
928                         } else {
929                             pout.addBox(44); // Maybe put the name of the translation here
930                         }
931                         if (m->message().isPlural() && m_dataModel->language(k) != QLocale::C) {
932                             QStringList transls = m->translations();
933                             pout.addBox(40, transls.join(QLatin1Char('\n')));
934                         } else {
935                             pout.addBox(40, m->translation());
936                         }
937                         pout.addBox(4);
938                         QString type;
939                         switch (m->message().type()) {
940                         case TranslatorMessage::Finished:
941                             type = tr("finished");
942                             break;
943                         case TranslatorMessage::Unfinished:
944                             type = m->danger() ? tr("unresolved") : QLatin1String("unfinished");
945                             break;
946                         case TranslatorMessage::Obsolete:
947                         case TranslatorMessage::Vanished:
948                             type = tr("obsolete");
949                             break;
950                         }
951                         pout.addBox(12, type, PrintOut::Normal, Qt::AlignRight);
952                         pout.flushLine();
953                     }
954                 }
955                 if (!comment.isEmpty()) {
956                     pout.addBox(4);
957                     pout.addBox(92, comment, PrintOut::Emphasis);
958                     pout.flushLine(true);
959                 }
960 
961                 if (pout.pageNum() != pageNum) {
962                     pageNum = pout.pageNum();
963                     statusBar()->showMessage(tr("Printing... (page %1)")
964                         .arg(pageNum));
965                 }
966             }
967         }
968         pout.flushLine(true);
969         QApplication::restoreOverrideCursor();
970         statusBar()->showMessage(tr("Printing completed"), MessageMS);
971     } else {
972         statusBar()->showMessage(tr("Printing aborted"), MessageMS);
973     }
974 }
975 
searchItem(DataModel::FindLocation where,const QString & searchWhat)976 bool 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(QLatin1Char('&'));
986 
987     if (m_findUseRegExp)
988         return m_findDialog->getRegExp().match(text).hasMatch();
989     else
990         return text.indexOf(m_findText, 0, m_findMatchCase) >= 0;
991 }
992 
findAgain()993 void MainWindow::findAgain()
994 {
995     if (m_dataModel->contextCount() == 0)
996         return;
997 
998     const QModelIndex &startIndex = m_messageView->currentIndex();
999     QModelIndex index = nextMessage(startIndex);
1000 
1001     while (index.isValid()) {
1002         QModelIndex realIndex = m_sortedMessagesModel->mapToSource(index);
1003         MultiDataIndex dataIndex = m_messageModel->dataIndex(realIndex, -1);
1004         bool hadMessage = false;
1005         for (int i = 0; i < m_dataModel->modelCount(); ++i) {
1006             if (MessageItem *m = m_dataModel->messageItem(dataIndex, i)) {
1007                 if (m_findSkipObsolete && m->isObsolete())
1008                     continue;
1009                 bool found = true;
1010                 do {
1011                     if (!hadMessage) {
1012                         if (searchItem(DataModel::SourceText, m->text()))
1013                             break;
1014                         if (searchItem(DataModel::SourceText, m->pluralText()))
1015                             break;
1016                         if (searchItem(DataModel::Comments, m->comment()))
1017                             break;
1018                         if (searchItem(DataModel::Comments, m->extraComment()))
1019                             break;
1020                     }
1021                     foreach (const QString &trans, m->translations())
1022                         if (searchItem(DataModel::Translations, trans))
1023                             goto didfind;
1024                     if (searchItem(DataModel::Comments, 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(realIndex, i);
1032 
1033                     // determine whether the search wrapped
1034                     const QModelIndex &c1 = m_sortedContextsModel->mapFromSource(
1035                             m_sortedMessagesModel->mapToSource(startIndex)).parent();
1036                     const QModelIndex &c2 = m_sortedContextsModel->mapFromSource(realIndex).parent();
1037                     const QModelIndex &m = m_sortedMessagesModel->mapFromSource(realIndex);
1038 
1039                     if (c2.row() < c1.row() || (c1.row() == c2.row() && m.row() <= startIndex.row()))
1040                         statusBar()->showMessage(tr("Search wrapped."), 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(index);
1054     }
1055 
1056     qApp->beep();
1057     QMessageBox::warning(m_findDialog, tr("Qt Linguist"),
1058                          tr("Cannot find the string '%1'.").arg(m_findText));
1059 }
1060 
showBatchTranslateDialog()1061 void MainWindow::showBatchTranslateDialog()
1062 {
1063     m_messageModel->blockSignals(true);
1064     m_batchTranslateDialog->setPhraseBooks(m_phraseBooks, m_currentIndex.model());
1065     if (m_batchTranslateDialog->exec() != QDialog::Accepted)
1066         m_messageModel->blockSignals(false);
1067     // else signal finished() calls refreshItemViews()
1068 }
1069 
showTranslateDialog()1070 void MainWindow::showTranslateDialog()
1071 {
1072     m_latestCaseSensitivity = -1;
1073     QModelIndex idx = m_messageView->currentIndex();
1074     QModelIndex idx2 = m_sortedMessagesModel->index(idx.row(), m_currentIndex.model() + 1, idx.parent());
1075     m_messageView->setCurrentIndex(idx2);
1076     QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
1077     m_translateDialog->setWindowTitle(tr("Search And Translate in '%1' - Qt Linguist").arg(fn));
1078     m_translateDialog->exec();
1079 }
1080 
updateTranslateHit(bool & hit)1081 void MainWindow::updateTranslateHit(bool &hit)
1082 {
1083     MessageItem *m;
1084     hit = (m = m_dataModel->messageItem(m_currentIndex))
1085           && !m->isObsolete()
1086           && m->compare(m_translateDialog->findText(), false, m_translateDialog->caseSensitivity());
1087 }
1088 
translate(int mode)1089 void 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, false, caseSensitivity)) {
1102                 if (!translatedCount)
1103                     m_messageModel->blockSignals(true);
1104                 m_dataModel->setTranslation(it, replaceText);
1105                 m_dataModel->setFinished(it, markFinished);
1106                 ++translatedCount;
1107             }
1108         }
1109         if (translatedCount) {
1110             refreshItemViews();
1111             QMessageBox::warning(m_translateDialog, tr("Translate - Qt Linguist"),
1112                     tr("Translated %n entry(s)", 0, translatedCount));
1113         }
1114     } else {
1115         if (mode == TranslateDialog::Translate) {
1116             m_dataModel->setTranslation(m_currentIndex, replaceText);
1117             m_dataModel->setFinished(m_currentIndex, 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(m_translateDialog, tr("Translate - Qt Linguist"),
1135                         tr("No more occurrences of '%1'. Start over?").arg(findText),
1136                         QMessageBox::Yes|QMessageBox::No) != QMessageBox::Yes)
1137                     return;
1138                 m_remainingCount -= prevRemained;
1139             }
1140 
1141             index = nextMessage(index);
1142 
1143             QModelIndex realIndex = m_sortedMessagesModel->mapToSource(index);
1144             MultiDataIndex dataIndex = m_messageModel->dataIndex(realIndex, m_currentIndex.model());
1145             if (MessageItem *m = m_dataModel->messageItem(dataIndex)) {
1146                 if (!m->isObsolete() && m->compare(findText, false, caseSensitivity)) {
1147                     setCurrentMessage(realIndex, m_currentIndex.model());
1148                     ++translatedCount;
1149                     ++m_hitCount;
1150                     break;
1151                 }
1152             }
1153         }
1154     }
1155 
1156     if (!translatedCount) {
1157         qApp->beep();
1158         QMessageBox::warning(m_translateDialog, tr("Translate - Qt Linguist"),
1159                 tr("Cannot find the string '%1'.").arg(findText));
1160     }
1161 }
1162 
newPhraseBook()1163 void MainWindow::newPhraseBook()
1164 {
1165     QString name = QFileDialog::getSaveFileName(this, tr("Create New Phrase Book"),
1166             m_phraseBookDir, tr("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, pb)) {
1176             if (openPhraseBook(name))
1177                 statusBar()->showMessage(tr("Phrase book created."), MessageMS);
1178         }
1179     }
1180 }
1181 
isPhraseBookOpen(const QString & name)1182 bool 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 
openPhraseBook()1192 void MainWindow::openPhraseBook()
1193 {
1194     QString name = QFileDialog::getOpenFileName(this, tr("Open Phrase Book"),
1195     m_phraseBookDir, tr("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(tr("%n phrase(s) loaded.", 0, n), MessageMS);
1203             }
1204         }
1205     }
1206 }
1207 
closePhraseBook(QAction * action)1208 void MainWindow::closePhraseBook(QAction *action)
1209 {
1210     PhraseBook *pb = m_phraseBookMenu[PhraseCloseMenu].value(action);
1211     if (!maybeSavePhraseBook(pb))
1212         return;
1213 
1214     m_phraseBookMenu[PhraseCloseMenu].remove(action);
1215     m_ui.menuClosePhraseBook->removeAction(action);
1216 
1217     QAction *act = m_phraseBookMenu[PhraseEditMenu].key(pb);
1218     m_phraseBookMenu[PhraseEditMenu].remove(act);
1219     m_ui.menuEditPhraseBook->removeAction(act);
1220 
1221     act = m_phraseBookMenu[PhrasePrintMenu].key(pb);
1222     m_ui.menuPrintPhraseBook->removeAction(act);
1223 
1224     m_phraseBooks.removeOne(pb);
1225     disconnect(pb, SIGNAL(listChanged()), this, SLOT(updatePhraseDicts()));
1226     updatePhraseDicts();
1227     delete pb;
1228     updatePhraseBookActions();
1229 }
1230 
editPhraseBook(QAction * action)1231 void MainWindow::editPhraseBook(QAction *action)
1232 {
1233     PhraseBook *pb = m_phraseBookMenu[PhraseEditMenu].value(action);
1234     PhraseBookBox box(pb, this);
1235     box.exec();
1236 
1237     updatePhraseDicts();
1238 }
1239 
printPhraseBook(QAction * action)1240 void MainWindow::printPhraseBook(QAction *action)
1241 {
1242     PhraseBook *phraseBook = m_phraseBookMenu[PhrasePrintMenu].value(action);
1243 
1244     int pageNum = 0;
1245 
1246     QPrintDialog dlg(printer(), this);
1247     if (dlg.exec()) {
1248         printer()->setDocName(phraseBook->fileName());
1249         statusBar()->showMessage(tr("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(29, p->source());
1255             pout.addBox(4);
1256             pout.addBox(29, p->target());
1257             pout.addBox(4);
1258             pout.addBox(34, p->definition(), PrintOut::Emphasis);
1259 
1260             if (pout.pageNum() != pageNum) {
1261                 pageNum = pout.pageNum();
1262                 statusBar()->showMessage(tr("Printing... (page %1)")
1263                     .arg(pageNum));
1264             }
1265             pout.setRule(PrintOut::NoRule);
1266             pout.flushLine(true);
1267         }
1268         pout.flushLine(true);
1269         statusBar()->showMessage(tr("Printing completed"), MessageMS);
1270     } else {
1271         statusBar()->showMessage(tr("Printing aborted"), MessageMS);
1272     }
1273 }
1274 
addToPhraseBook()1275 void 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(m_currentIndex.model()) != QLocale::C) {
1281             if (pb->language() != m_dataModel->language(m_currentIndex.model()))
1282                 continue;
1283             if (pb->country() == m_dataModel->model(m_currentIndex.model())->country())
1284                 phraseBookList.prepend(pb->friendlyPhraseBookName());
1285             else
1286                 phraseBookList.append(pb->friendlyPhraseBookName());
1287         } else {
1288             phraseBookList.append(pb->friendlyPhraseBookName());
1289         }
1290         phraseBookHash.insert(pb->friendlyPhraseBookName(), pb);
1291     }
1292     if (phraseBookList.isEmpty()) {
1293         QMessageBox::warning(this, tr("Add to phrase book"),
1294               tr("No appropriate phrasebook found."));
1295         return;
1296     }
1297 
1298     QString selectedPhraseBook;
1299     if (phraseBookList.size() == 1) {
1300         selectedPhraseBook = phraseBookList.at(0);
1301         if (QMessageBox::information(this, tr("Add to phrase book"),
1302               tr("Adding entry to phrasebook %1").arg(selectedPhraseBook),
1303                QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)
1304                               != QMessageBox::Ok)
1305             return;
1306     } else {
1307         bool okPressed = false;
1308         QString selectedPhraseBook = QInputDialog::getItem(this, tr("Add to phrase book"),
1309                                 tr("Select phrase book to add to"),
1310                                 phraseBookList, 0, false, &okPressed);
1311         if (!okPressed)
1312             return;
1313     }
1314 
1315     MessageItem *currentMessage = m_dataModel->messageItem(m_currentIndex);
1316     Phrase *phrase = new Phrase(currentMessage->text(), currentMessage->translation(),
1317                                 QString(), nullptr);
1318 
1319     phraseBookHash.value(selectedPhraseBook)->append(phrase);
1320 }
1321 
resetSorting()1322 void MainWindow::resetSorting()
1323 {
1324     m_contextView->sortByColumn(-1, Qt::AscendingOrder);
1325     m_messageView->sortByColumn(-1, Qt::AscendingOrder);
1326 }
1327 
manual()1328 void 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(app, QStringList() << QLatin1String("-enableRemoteControl"));
1342         if (!m_assistantProcess->waitForStarted()) {
1343             QMessageBox::critical(this, tr("Qt Linguist"),
1344                 tr("Unable to launch Qt Assistant (%1)").arg(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 
about()1356 void MainWindow::about()
1357 {
1358     QMessageBox box(this);
1359     box.setTextFormat(Qt::RichText);
1360     QString version = tr("Version %1");
1361     version = version.arg(QLatin1String(QT_VERSION_STR));
1362 
1363     const QString description
1364             = tr("Qt Linguist is a tool for adding translations to Qt applications.");
1365     const QString copyright
1366             = tr("Copyright (C) %1 The Qt Company Ltd.").arg(QStringLiteral("2020"));
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(version, description, copyright));
1370 
1371     box.setWindowTitle(QApplication::translate("AboutDialog", "Qt Linguist"));
1372     box.setIcon(QMessageBox::NoIcon);
1373     box.exec();
1374 }
1375 
aboutQt()1376 void MainWindow::aboutQt()
1377 {
1378     QMessageBox::aboutQt(this, tr("Qt Linguist"));
1379 }
1380 
setupPhrase()1381 void 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 
closeEvent(QCloseEvent * e)1389 void MainWindow::closeEvent(QCloseEvent *e)
1390 {
1391     if (maybeSaveAll() && maybeSavePhraseBooks())
1392         e->accept();
1393     else
1394         e->ignore();
1395 }
1396 
maybeSaveAll()1397 bool MainWindow::maybeSaveAll()
1398 {
1399     if (!m_dataModel->isModified())
1400         return true;
1401 
1402     switch (QMessageBox::information(this, tr("Qt Linguist"),
1403         tr("Do you want to save the modified files?"),
1404         QMessageBox::Yes | QMessageBox::Default,
1405         QMessageBox::No,
1406         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 
maybeSave(int model)1419 bool MainWindow::maybeSave(int model)
1420 {
1421     if (!m_dataModel->isModified(model))
1422         return true;
1423 
1424     switch (QMessageBox::information(this, tr("Qt Linguist"),
1425         tr("Do you want to save '%1'?").arg(m_dataModel->srcFileName(model, true)),
1426         QMessageBox::Yes | QMessageBox::Default,
1427         QMessageBox::No,
1428         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 
updateCaption()1441 void 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(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("Qt Linguist[*]");
1470     else
1471         cap = tr("%1[*] - Qt Linguist").arg(m_dataModel->condensedSrcFileNames(true));
1472     setWindowTitle(cap);
1473 }
1474 
selectedContextChanged(const QModelIndex & sortedIndex,const QModelIndex & oldIndex)1475 void 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(sortedIndex);
1482         if (m_messageModel->parent(currentMessageIndex()).row() == sourceIndex.row())
1483             return;
1484 
1485         QModelIndex contextIndex = setMessageViewRoot(sourceIndex);
1486         const QModelIndex &firstChild =
1487                 m_sortedMessagesModel->index(0, sourceIndex.column(), 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  */
selectedMessageChanged(const QModelIndex & sortedIndex,const QModelIndex & oldIndex)1497 void 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(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(m_currentIndex);
1513         if (model >= 0 && (m = m_dataModel->messageItem(m_currentIndex))) {
1514             if (m_dataModel->isModelWritable(model) && !m->isObsolete())
1515                 m_phraseView->setSourceText(m_currentIndex.model(), m->text());
1516             else
1517                 m_phraseView->setSourceText(-1, QString());
1518         } else {
1519             if (model < 0) {
1520                 model = m_dataModel->multiContextItem(m_currentIndex.context())
1521                                 ->firstNonobsoleteMessageIndex(m_currentIndex.message());
1522                 if (model >= 0)
1523                     m = m_dataModel->messageItem(m_currentIndex, model);
1524             }
1525             m_phraseView->setSourceText(-1, QString());
1526         }
1527         m_errorsView->setEnabled(m != 0);
1528         updateDanger(m_currentIndex, true);
1529     } else {
1530         m_currentIndex = MultiDataIndex();
1531         m_messageEditor->showNothing();
1532         m_phraseView->setSourceText(-1, QString());
1533     }
1534     updateSourceView(model, m);
1535 
1536     updatePhraseBookActions();
1537     m_ui.actionSelectAll->setEnabled(index.isValid());
1538 }
1539 
translationChanged(const MultiDataIndex & index)1540 void 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, true);
1549 
1550     MessageItem *m = m_dataModel->messageItem(index);
1551     if (hasFormPreview(m->fileName()))
1552         m_formPreviewView->setSourceContext(index.model(), m);
1553 }
1554 
1555 // This and the following function operate directly on the messageitem,
1556 // so the model does not emit modification notifications.
updateTranslation(const QStringList & translations)1557 void MainWindow::updateTranslation(const QStringList &translations)
1558 {
1559     MessageItem *m = m_dataModel->messageItem(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(m->fileName()))
1567         m_formPreviewView->setSourceContext(m_currentIndex.model(), m);
1568     updateDanger(m_currentIndex, true);
1569 
1570     if (m->isFinished())
1571         m_dataModel->setFinished(m_currentIndex, false);
1572     else
1573         m_dataModel->setModified(m_currentIndex.model(), true);
1574 }
1575 
updateTranslatorComment(const QString & comment)1576 void MainWindow::updateTranslatorComment(const QString &comment)
1577 {
1578     MessageItem *m = m_dataModel->messageItem(m_currentIndex);
1579     if (!m)
1580         return;
1581     if (comment == m->translatorComment())
1582         return;
1583 
1584     m->setTranslatorComment(comment);
1585 
1586     m_dataModel->setModified(m_currentIndex.model(), true);
1587 }
1588 
refreshItemViews()1589 void MainWindow::refreshItemViews()
1590 {
1591     m_messageModel->blockSignals(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 
done()1599 void MainWindow::done()
1600 {
1601     int model = m_messageEditor->activeModel();
1602     if (model >= 0 && m_dataModel->isModelWritable(model))
1603         m_dataModel->setFinished(m_currentIndex, true);
1604 }
1605 
doneAndNext()1606 void MainWindow::doneAndNext()
1607 {
1608     done();
1609     if (!m_messageEditor->focusNextUnfinished())
1610         nextUnfinished();
1611 }
1612 
toggleFinished(const QModelIndex & index)1613 void MainWindow::toggleFinished(const QModelIndex &index)
1614 {
1615     if (!index.isValid() || index.column() - 1 >= m_dataModel->modelCount()
1616         || !m_dataModel->isModelWritable(index.column() - 1) || index.parent() == QModelIndex())
1617         return;
1618 
1619     QModelIndex item = m_sortedMessagesModel->mapToSource(index);
1620     MultiDataIndex dataIndex = m_messageModel->dataIndex(item);
1621     MessageItem *m = m_dataModel->messageItem(dataIndex);
1622 
1623     if (!m || m->message().type() == TranslatorMessage::Obsolete
1624            || m->message().type() == TranslatorMessage::Vanished)
1625         return;
1626 
1627     m_dataModel->setFinished(dataIndex, !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  */
nextContext(const QModelIndex & index) const1635 QModelIndex MainWindow::nextContext(const QModelIndex &index) const
1636 {
1637     QModelIndex sortedContextIndex = m_sortedContextsModel->mapFromSource(
1638             m_sortedMessagesModel->mapToSource(index));
1639 
1640     int nextRow = sortedContextIndex.row() + 1;
1641     if (nextRow >= m_sortedContextsModel->rowCount())
1642         nextRow = 0;
1643     sortedContextIndex = m_sortedContextsModel->index(nextRow, index.column());
1644 
1645     return m_sortedMessagesModel->mapFromSource(
1646             m_sortedContextsModel->mapToSource(sortedContextIndex));
1647 }
1648 
1649 /*
1650  * See nextContext.
1651  */
prevContext(const QModelIndex & index) const1652 QModelIndex MainWindow::prevContext(const QModelIndex &index) const
1653 {
1654     QModelIndex sortedContextIndex = m_sortedContextsModel->mapFromSource(
1655             m_sortedMessagesModel->mapToSource(index));
1656 
1657     int prevRow = sortedContextIndex.row() - 1;
1658     if (prevRow < 0) prevRow = m_sortedContextsModel->rowCount() - 1;
1659     sortedContextIndex = m_sortedContextsModel->index(prevRow, index.column());
1660 
1661     return m_sortedMessagesModel->mapFromSource(
1662             m_sortedContextsModel->mapToSource(sortedContextIndex));
1663 }
1664 
nextMessage(const QModelIndex & currentIndex,bool checkUnfinished) const1665 QModelIndex MainWindow::nextMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
1666 {
1667     QModelIndex idx = currentIndex.isValid() ? currentIndex : m_sortedMessagesModel->index(0, 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(par)) {
1678             par = nextContext(par);
1679             row = 0;
1680         }
1681         idx = m_sortedMessagesModel->index(row, idx.column(), par);
1682 
1683         if (!checkUnfinished)
1684             return idx;
1685 
1686         QModelIndex item = m_sortedMessagesModel->mapToSource(idx);
1687         MultiDataIndex index = m_messageModel->dataIndex(item, -1);
1688         if (m_dataModel->multiMessageItem(index)->isUnfinished())
1689             return idx;
1690     } while (idx != currentIndex);
1691     return QModelIndex();
1692 }
1693 
prevMessage(const QModelIndex & currentIndex,bool checkUnfinished) const1694 QModelIndex MainWindow::prevMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
1695 {
1696     QModelIndex idx = currentIndex.isValid() ? currentIndex : m_sortedMessagesModel->index(0, 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(par);
1707             row = m_sortedMessagesModel->rowCount(par) - 1;
1708         }
1709         idx = m_sortedMessagesModel->index(row, idx.column(), par);
1710 
1711         if (!checkUnfinished)
1712             return idx;
1713 
1714         QModelIndex item = m_sortedMessagesModel->mapToSource(idx);
1715         MultiDataIndex index = m_messageModel->dataIndex(item, -1);
1716         if (m_dataModel->multiMessageItem(index)->isUnfinished())
1717             return idx;
1718     } while (idx != currentIndex);
1719     return QModelIndex();
1720 }
1721 
nextUnfinished()1722 void MainWindow::nextUnfinished()
1723 {
1724     if (m_ui.actionNextUnfinished->isEnabled()) {
1725         if (!next(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(tr("No untranslated translation units left."), MessageMS);
1729             qApp->beep();
1730         }
1731     }
1732 }
1733 
prevUnfinished()1734 void MainWindow::prevUnfinished()
1735 {
1736     if (m_ui.actionNextUnfinished->isEnabled()) {
1737         if (!prev(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(tr("No untranslated translation units left."), MessageMS);
1741             qApp->beep();
1742         }
1743     }
1744 }
1745 
prev()1746 void MainWindow::prev()
1747 {
1748     prev(false);
1749 }
1750 
next()1751 void MainWindow::next()
1752 {
1753     next(false);
1754 }
1755 
prev(bool checkUnfinished)1756 bool MainWindow::prev(bool checkUnfinished)
1757 {
1758     QModelIndex index = prevMessage(m_messageView->currentIndex(), checkUnfinished);
1759     if (index.isValid())
1760         setCurrentMessage(m_sortedMessagesModel->mapToSource(index));
1761     if (checkUnfinished)
1762         m_messageEditor->setUnfinishedEditorFocus();
1763     else
1764         m_messageEditor->setEditorFocus();
1765     return index.isValid();
1766 }
1767 
next(bool checkUnfinished)1768 bool MainWindow::next(bool checkUnfinished)
1769 {
1770     QModelIndex index = nextMessage(m_messageView->currentIndex(), checkUnfinished);
1771     if (index.isValid())
1772         setCurrentMessage(m_sortedMessagesModel->mapToSource(index));
1773     if (checkUnfinished)
1774         m_messageEditor->setUnfinishedEditorFocus();
1775     else
1776         m_messageEditor->setEditorFocus();
1777     return index.isValid();
1778 }
1779 
findNext(const QString & text,DataModel::FindLocation where,bool matchCase,bool ignoreAccelerators,bool skipObsolete,bool useRegExp)1780 void 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 
revalidate()1800 void MainWindow::revalidate()
1801 {
1802     for (MultiDataModelIterator it(m_dataModel, -1); it.isValid(); ++it)
1803         updateDanger(it, false);
1804 
1805     if (m_currentIndex.isValid())
1806         updateDanger(m_currentIndex, true);
1807 }
1808 
friendlyString(const QString & str)1809 QString MainWindow::friendlyString(const QString& str)
1810 {
1811     QString f = str.toLower();
1812     f.replace(QRegExp(QString(QLatin1String("[.,:;!?()-]"))), QString(QLatin1String(" ")));
1813     f.remove(QLatin1Char('&'));
1814     return f.simplified();
1815 }
1816 
setupMenuBar()1817 void MainWindow::setupMenuBar()
1818 {
1819 
1820     const bool hasThemeIcons = !QApplication::platformName().compare(QStringLiteral("xcb"), 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"), Qt::CaseInsensitive) ?
1830                            QStringLiteral(":/images/win") : QStringLiteral(":/images/mac");
1831 
1832     m_ui.actionOpen->setIcon(QIcon::fromTheme(QStringLiteral("document-open"),
1833                                               QIcon(prefix + QStringLiteral("/fileopen.png"))));
1834     m_ui.actionOpenAux->setIcon(QIcon::fromTheme(QStringLiteral("document-open"),
1835                                                  QIcon(prefix + QStringLiteral("/fileopen.png"))));
1836     m_ui.actionSave->setIcon(QIcon::fromTheme(QStringLiteral("document-save"),
1837                                               QIcon(prefix + QStringLiteral("/filesave.png"))));
1838     m_ui.actionSaveAll->setIcon(QIcon::fromTheme(QStringLiteral("document-save"),
1839                                                  QIcon(prefix + QStringLiteral("/filesave.png"))));
1840     m_ui.actionPrint->setIcon(QIcon::fromTheme(QStringLiteral("document-print"),
1841                                                QIcon(prefix + QStringLiteral("/print.png"))));
1842     m_ui.actionRedo->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo"),
1843                                               QIcon(prefix + QStringLiteral("/redo.png"))));
1844     m_ui.actionUndo->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"),
1845                                               QIcon(prefix + QStringLiteral("/undo.png"))));
1846     m_ui.actionCut->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut"),
1847                                              QIcon(prefix + QStringLiteral("/editcut.png"))));
1848     m_ui.actionCopy->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"),
1849                                               QIcon(prefix + QStringLiteral("/editcopy.png"))));
1850     m_ui.actionPaste->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"),
1851                                                QIcon(prefix + QStringLiteral("/editpaste.png"))));
1852     m_ui.actionFind->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"),
1853                                               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(m_ui.menuFile, SIGNAL(aboutToShow()), SLOT(fileAboutToShow()));
1872     connect(m_ui.actionOpen, SIGNAL(triggered()), this, SLOT(open()));
1873     connect(m_ui.actionOpenAux, SIGNAL(triggered()), this, SLOT(openAux()));
1874     connect(m_ui.actionSaveAll, SIGNAL(triggered()), this, SLOT(saveAll()));
1875     connect(m_ui.actionSave, SIGNAL(triggered()), this, SLOT(save()));
1876     connect(m_ui.actionSaveAs, SIGNAL(triggered()), this, SLOT(saveAs()));
1877     connect(m_ui.actionReleaseAll, SIGNAL(triggered()), this, SLOT(releaseAll()));
1878     connect(m_ui.actionRelease, SIGNAL(triggered()), this, SLOT(release()));
1879     connect(m_ui.actionReleaseAs, SIGNAL(triggered()), this, SLOT(releaseAs()));
1880     connect(m_ui.actionPrint, SIGNAL(triggered()), this, SLOT(print()));
1881     connect(m_ui.actionClose, SIGNAL(triggered()), this, SLOT(closeFile()));
1882     connect(m_ui.actionCloseAll, SIGNAL(triggered()), this, SLOT(closeAll()));
1883     connect(m_ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));
1884 
1885     // Edit menu
1886     connect(m_ui.menuEdit, SIGNAL(aboutToShow()), SLOT(editAboutToShow()));
1887 
1888     connect(m_ui.actionUndo, SIGNAL(triggered()), m_messageEditor, SLOT(undo()));
1889     connect(m_messageEditor, SIGNAL(undoAvailable(bool)), m_ui.actionUndo, SLOT(setEnabled(bool)));
1890 
1891     connect(m_ui.actionRedo, SIGNAL(triggered()), m_messageEditor, SLOT(redo()));
1892     connect(m_messageEditor, SIGNAL(redoAvailable(bool)), m_ui.actionRedo, SLOT(setEnabled(bool)));
1893 
1894     connect(m_ui.actionCopy, SIGNAL(triggered()), m_messageEditor, SLOT(copy()));
1895     connect(m_messageEditor, SIGNAL(copyAvailable(bool)), m_ui.actionCopy, SLOT(setEnabled(bool)));
1896 
1897     connect(m_messageEditor, SIGNAL(cutAvailable(bool)), m_ui.actionCut, SLOT(setEnabled(bool)));
1898     connect(m_ui.actionCut, SIGNAL(triggered()), m_messageEditor, SLOT(cut()));
1899 
1900     connect(m_messageEditor, SIGNAL(pasteAvailable(bool)), m_ui.actionPaste, SLOT(setEnabled(bool)));
1901     connect(m_ui.actionPaste, SIGNAL(triggered()), m_messageEditor, SLOT(paste()));
1902 
1903     connect(m_ui.actionSelectAll, SIGNAL(triggered()), m_messageEditor, SLOT(selectAll()));
1904     connect(m_ui.actionFind, SIGNAL(triggered()), m_findDialog, SLOT(find()));
1905     connect(m_ui.actionFindNext, SIGNAL(triggered()), this, SLOT(findAgain()));
1906     connect(m_ui.actionSearchAndTranslate, SIGNAL(triggered()), this, SLOT(showTranslateDialog()));
1907     connect(m_ui.actionBatchTranslation, SIGNAL(triggered()), this, SLOT(showBatchTranslateDialog()));
1908     connect(m_ui.actionTranslationFileSettings, SIGNAL(triggered()), this, SLOT(showTranslationSettings()));
1909 
1910     connect(m_batchTranslateDialog, SIGNAL(finished()), SLOT(refreshItemViews()));
1911 
1912     // Translation menu
1913     // when updating the accelerators, remember the status bar
1914     connect(m_ui.actionPrevUnfinished, SIGNAL(triggered()), this, SLOT(prevUnfinished()));
1915     connect(m_ui.actionNextUnfinished, SIGNAL(triggered()), this, SLOT(nextUnfinished()));
1916     connect(m_ui.actionNext, SIGNAL(triggered()), this, SLOT(next()));
1917     connect(m_ui.actionPrev, SIGNAL(triggered()), this, SLOT(prev()));
1918     connect(m_ui.actionDone, SIGNAL(triggered()), this, SLOT(done()));
1919     connect(m_ui.actionDoneAndNext, SIGNAL(triggered()), this, SLOT(doneAndNext()));
1920     connect(m_ui.actionBeginFromSource, SIGNAL(triggered()), m_messageEditor, SLOT(beginFromSource()));
1921     connect(m_messageEditor, SIGNAL(beginFromSourceAvailable(bool)), m_ui.actionBeginFromSource, SLOT(setEnabled(bool)));
1922 
1923     // Phrasebook menu
1924     connect(m_ui.actionNewPhraseBook, SIGNAL(triggered()), this, SLOT(newPhraseBook()));
1925     connect(m_ui.actionOpenPhraseBook, SIGNAL(triggered()), this, SLOT(openPhraseBook()));
1926     connect(m_ui.menuClosePhraseBook, SIGNAL(triggered(QAction*)),
1927         this, SLOT(closePhraseBook(QAction*)));
1928     connect(m_ui.menuEditPhraseBook, SIGNAL(triggered(QAction*)),
1929         this, SLOT(editPhraseBook(QAction*)));
1930     connect(m_ui.menuPrintPhraseBook, SIGNAL(triggered(QAction*)),
1931         this, SLOT(printPhraseBook(QAction*)));
1932     connect(m_ui.actionAddToPhraseBook, SIGNAL(triggered()), this, SLOT(addToPhraseBook()));
1933 
1934     // Validation menu
1935     connect(m_ui.actionAccelerators, SIGNAL(triggered()), this, SLOT(revalidate()));
1936     connect(m_ui.actionSurroundingWhitespace, SIGNAL(triggered()), this, SLOT(revalidate()));
1937     connect(m_ui.actionEndingPunctuation, SIGNAL(triggered()), this, SLOT(revalidate()));
1938     connect(m_ui.actionPhraseMatches, SIGNAL(triggered()), this, SLOT(revalidate()));
1939     connect(m_ui.actionPlaceMarkerMatches, SIGNAL(triggered()), this, SLOT(revalidate()));
1940 
1941     // View menu
1942     connect(m_ui.actionResetSorting, SIGNAL(triggered()), this, SLOT(resetSorting()));
1943     connect(m_ui.actionDisplayGuesses, SIGNAL(triggered()), m_phraseView, SLOT(toggleGuessing()));
1944     connect(m_ui.actionStatistics, SIGNAL(triggered()), this, SLOT(toggleStatistics()));
1945     connect(m_ui.actionVisualizeWhitespace, SIGNAL(triggered()), this, SLOT(toggleVisualizeWhitespace()));
1946     connect(m_ui.menuView, SIGNAL(aboutToShow()), this, SLOT(updateViewMenu()));
1947     connect(m_ui.actionIncreaseZoom, SIGNAL(triggered()), m_messageEditor, SLOT(increaseFontSize()));
1948     connect(m_ui.actionDecreaseZoom, SIGNAL(triggered()), m_messageEditor, SLOT(decreaseFontSize()));
1949     connect(m_ui.actionResetZoomToDefault, SIGNAL(triggered()), m_messageEditor, SLOT(resetFontSize()));
1950     connect(m_ui.actionShowMoreGuesses, SIGNAL(triggered()), m_phraseView, SLOT(moreGuesses()));
1951     connect(m_ui.actionShowFewerGuesses, SIGNAL(triggered()), m_phraseView, SLOT(fewerGuesses()));
1952     connect(m_phraseView, SIGNAL(showFewerGuessesAvailable(bool)), m_ui.actionShowFewerGuesses, SLOT(setEnabled(bool)));
1953     connect(m_ui.actionResetGuessesToDefault, SIGNAL(triggered()), m_phraseView, SLOT(resetNumGuesses()));
1954     m_ui.menuViewViews->addAction(m_contextDock->toggleViewAction());
1955     m_ui.menuViewViews->addAction(m_messagesDock->toggleViewAction());
1956     m_ui.menuViewViews->addAction(m_phrasesDock->toggleViewAction());
1957     m_ui.menuViewViews->addAction(m_sourceAndFormDock->toggleViewAction());
1958     m_ui.menuViewViews->addAction(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(m_ui.actionManual, SIGNAL(triggered()), this, SLOT(manual()));
1970     connect(m_ui.actionAbout, SIGNAL(triggered()), this, SLOT(about()));
1971     connect(m_ui.actionAboutQt, SIGNAL(triggered()), this, SLOT(aboutQt()));
1972     connect(m_ui.actionWhatsThis, SIGNAL(triggered()), this, SLOT(onWhatsThis()));
1973 
1974     connect(m_ui.menuRecentlyOpenedFiles, SIGNAL(triggered(QAction*)), this,
1975         SLOT(recentFileActivated(QAction*)));
1976 
1977     m_ui.actionManual->setWhatsThis(tr("Display the manual for %1.").arg(tr("Qt Linguist")));
1978     m_ui.actionAbout->setWhatsThis(tr("Display information about %1.").arg(tr("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(m_ui.menuPhrases, SIGNAL(aboutToShow()), this, SLOT(setupPhrase()));
1988 
1989     connect(m_ui.menuRecentlyOpenedFiles, SIGNAL(aboutToShow()), SLOT(setupRecentFilesMenu()));
1990 }
1991 
updateActiveModel(int model)1992 void 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
updateLatestModel(const QModelIndex & index)1999 void MainWindow::updateLatestModel(const QModelIndex &index)
2000 {
2001     if (index.column() && (index.column() - 1 < m_dataModel->modelCount()))
2002         updateLatestModel(index.column() - 1);
2003 }
2004 
updateLatestModel(int model)2005 void 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(m_currentIndex))) {
2018                 if (enableRw && !item->isObsolete())
2019                     m_phraseView->setSourceText(model, item->text());
2020                 else
2021                     m_phraseView->setSourceText(-1, QString());
2022             } else {
2023                 m_phraseView->setSourceText(-1, 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 
updateSourceView(int model,MessageItem * item)2040 void MainWindow::updateSourceView(int model, MessageItem *item)
2041 {
2042     if (item && !item->fileName().isEmpty()) {
2043         if (hasFormPreview(item->fileName())) {
2044             m_sourceAndFormView->setCurrentWidget(m_formPreviewView);
2045             m_formPreviewView->setSourceContext(model, item);
2046         } else {
2047             m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2048             QDir dir = QFileInfo(m_dataModel->srcFileName(model)).dir();
2049             QString fileName = QDir::cleanPath(dir.absoluteFilePath(item->fileName()));
2050             m_sourceCodeView->setSourceContext(fileName, item->lineNumber());
2051         }
2052     } else {
2053         m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
2054         m_sourceCodeView->setSourceContext(QString(), 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 
fileAboutToShow()2061 void 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(m_currentIndex.model())).baseName();
2069                 m_ui.actionSave->setText(tr("&Save '%1'").arg(fn));
2070                 m_ui.actionSaveAs->setText(tr("Save '%1' &As...").arg(fn));
2071                 m_ui.actionRelease->setText(tr("Release '%1'").arg(fn));
2072                 m_ui.actionReleaseAs->setText(tr("Release '%1' As...").arg(fn));
2073                 m_ui.actionClose->setText(tr("&Close '%1'").arg(fn));
2074             } else {
2075                 m_ui.actionSave->setText(tr("&Save"));
2076                 m_ui.actionSaveAs->setText(tr("Save &As..."));
2077                 m_ui.actionRelease->setText(tr("Release"));
2078                 m_ui.actionReleaseAs->setText(tr("Release As..."));
2079                 m_ui.actionClose->setText(tr("&Close"));
2080             }
2081 
2082             m_ui.actionSaveAll->setText(tr("Save All"));
2083             m_ui.actionReleaseAll->setText(tr("&Release All"));
2084             m_ui.actionCloseAll->setText(tr("Close All"));
2085             en = true;
2086         } else {
2087             m_ui.actionSaveAs->setText(tr("Save &As..."));
2088             m_ui.actionReleaseAs->setText(tr("Release As..."));
2089 
2090             m_ui.actionSaveAll->setText(tr("&Save"));
2091             m_ui.actionReleaseAll->setText(tr("&Release"));
2092             m_ui.actionCloseAll->setText(tr("&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 
editAboutToShow()2102 void 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(m_currentIndex.model())).baseName();
2107             m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings for '%1'...").arg(fn));
2108             m_ui.actionBatchTranslation->setText(tr("&Batch Translation of '%1'...").arg(fn));
2109             m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate in '%1'...").arg(fn));
2110         } else {
2111             m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings..."));
2112             m_ui.actionBatchTranslation->setText(tr("&Batch Translation..."));
2113             m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate..."));
2114         }
2115         m_editActiveModel = m_currentIndex.model();
2116     }
2117 }
2118 
updateViewMenu()2119 void MainWindow::updateViewMenu()
2120 {
2121     bool check = m_statistics ? m_statistics->isVisible() : false;
2122     m_ui.actionStatistics->setChecked(check);
2123 }
2124 
showContextDock()2125 void MainWindow::showContextDock()
2126 {
2127     m_contextDock->show();
2128     m_contextDock->raise();
2129 }
2130 
showMessagesDock()2131 void MainWindow::showMessagesDock()
2132 {
2133     m_messagesDock->show();
2134     m_messagesDock->raise();
2135 }
2136 
showPhrasesDock()2137 void MainWindow::showPhrasesDock()
2138 {
2139     m_phrasesDock->show();
2140     m_phrasesDock->raise();
2141 }
2142 
showSourceCodeDock()2143 void MainWindow::showSourceCodeDock()
2144 {
2145     m_sourceAndFormDock->show();
2146     m_sourceAndFormDock->raise();
2147 }
2148 
showErrorDock()2149 void MainWindow::showErrorDock()
2150 {
2151     m_errorsDock->show();
2152     m_errorsDock->raise();
2153 }
2154 
onWhatsThis()2155 void MainWindow::onWhatsThis()
2156 {
2157     QWhatsThis::enterWhatsThisMode();
2158 }
2159 
setupToolBars()2160 void MainWindow::setupToolBars()
2161 {
2162     QToolBar *filet = new QToolBar(this);
2163     filet->setObjectName(QLatin1String("FileToolbar"));
2164     filet->setWindowTitle(tr("File"));
2165     this->addToolBar(filet);
2166     m_ui.menuToolbars->addAction(filet->toggleViewAction());
2167 
2168     QToolBar *editt = new QToolBar(this);
2169     editt->setVisible(false);
2170     editt->setObjectName(QLatin1String("EditToolbar"));
2171     editt->setWindowTitle(tr("Edit"));
2172     this->addToolBar(editt);
2173     m_ui.menuToolbars->addAction(editt->toggleViewAction());
2174 
2175     QToolBar *translationst = new QToolBar(this);
2176     translationst->setObjectName(QLatin1String("TranslationToolbar"));
2177     translationst->setWindowTitle(tr("Translation"));
2178     this->addToolBar(translationst);
2179     m_ui.menuToolbars->addAction(translationst->toggleViewAction());
2180 
2181     QToolBar *validationt = new QToolBar(this);
2182     validationt->setObjectName(QLatin1String("ValidationToolbar"));
2183     validationt->setWindowTitle(tr("Validation"));
2184     this->addToolBar(validationt);
2185     m_ui.menuToolbars->addAction(validationt->toggleViewAction());
2186 
2187     QToolBar *helpt = new QToolBar(this);
2188     helpt->setVisible(false);
2189     helpt->setObjectName(QLatin1String("HelpToolbar"));
2190     helpt->setWindowTitle(tr("Help"));
2191     this->addToolBar(helpt);
2192     m_ui.menuToolbars->addAction(helpt->toggleViewAction());
2193 
2194 
2195     filet->addAction(m_ui.actionOpen);
2196     filet->addAction(m_ui.actionSaveAll);
2197     filet->addAction(m_ui.actionPrint);
2198     filet->addSeparator();
2199     filet->addAction(m_ui.actionOpenPhraseBook);
2200 
2201     editt->addAction(m_ui.actionUndo);
2202     editt->addAction(m_ui.actionRedo);
2203     editt->addSeparator();
2204     editt->addAction(m_ui.actionCut);
2205     editt->addAction(m_ui.actionCopy);
2206     editt->addAction(m_ui.actionPaste);
2207     editt->addSeparator();
2208     editt->addAction(m_ui.actionFind);
2209 
2210     translationst->addAction(m_ui.actionPrev);
2211     translationst->addAction(m_ui.actionNext);
2212     translationst->addAction(m_ui.actionPrevUnfinished);
2213     translationst->addAction(m_ui.actionNextUnfinished);
2214     translationst->addAction(m_ui.actionDone);
2215     translationst->addAction(m_ui.actionDoneAndNext);
2216 
2217     validationt->addAction(m_ui.actionAccelerators);
2218     validationt->addAction(m_ui.actionSurroundingWhitespace);
2219     validationt->addAction(m_ui.actionEndingPunctuation);
2220     validationt->addAction(m_ui.actionPhraseMatches);
2221     validationt->addAction(m_ui.actionPlaceMarkerMatches);
2222 
2223     helpt->addAction(m_ui.actionWhatsThis);
2224 }
2225 
setMessageViewRoot(const QModelIndex & index)2226 QModelIndex MainWindow::setMessageViewRoot(const QModelIndex &index)
2227 {
2228     const QModelIndex &sortedContextIndex = m_sortedMessagesModel->mapFromSource(index);
2229     const QModelIndex &trueContextIndex = m_sortedMessagesModel->index(sortedContextIndex.row(), 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  */
setCurrentMessage(const QModelIndex & index)2238 void 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(contextIndex.row(), index.column(), QModelIndex());
2245     m_settingCurrentMessage = true;
2246     m_contextView->setCurrentIndex(m_sortedContextsModel->mapFromSource(trueIndex));
2247     m_settingCurrentMessage = false;
2248 
2249     setMessageViewRoot(contextIndex);
2250     m_messageView->setCurrentIndex(m_sortedMessagesModel->mapFromSource(index));
2251 }
2252 
setCurrentMessage(const QModelIndex & index,int model)2253 void MainWindow::setCurrentMessage(const QModelIndex &index, int model)
2254 {
2255     const QModelIndex &theIndex = m_messageModel->index(index.row(), model + 1, index.parent());
2256     setCurrentMessage(theIndex);
2257     m_messageEditor->setEditorFocus(model);
2258 }
2259 
setCurrentMessage(int modelIndex,const Candidate & cand)2260 void MainWindow::setCurrentMessage(int modelIndex, const Candidate &cand)
2261 {
2262     int contextIndex = m_dataModel->findContextIndex(cand.context);
2263     int messageIndex = m_dataModel->multiContextItem(contextIndex)->findMessage(cand.source,
2264                                                                                 cand.disambiguation);
2265     setCurrentMessage(m_messageModel->modelIndex(MultiDataIndex(modelIndex, contextIndex,
2266                                                                 messageIndex)));
2267 }
2268 
currentContextIndex() const2269 QModelIndex MainWindow::currentContextIndex() const
2270 {
2271     return m_sortedContextsModel->mapToSource(m_contextView->currentIndex());
2272 }
2273 
currentMessageIndex() const2274 QModelIndex MainWindow::currentMessageIndex() const
2275 {
2276     return m_sortedMessagesModel->mapToSource(m_messageView->currentIndex());
2277 }
2278 
openPhraseBook(const QString & name)2279 PhraseBook *MainWindow::openPhraseBook(const QString& name)
2280 {
2281     PhraseBook *pb = new PhraseBook();
2282     bool langGuessed;
2283     if (!pb->load(name, &langGuessed)) {
2284         QMessageBox::warning(this, tr("Qt Linguist"),
2285             tr("Cannot read from phrase book '%1'.").arg(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(pb);
2297 
2298     QAction *a = m_ui.menuClosePhraseBook->addAction(pb->friendlyPhraseBookName());
2299     m_phraseBookMenu[PhraseCloseMenu].insert(a, pb);
2300     a->setWhatsThis(tr("Close this phrase book."));
2301 
2302     a = m_ui.menuEditPhraseBook->addAction(pb->friendlyPhraseBookName());
2303     m_phraseBookMenu[PhraseEditMenu].insert(a, pb);
2304     a->setWhatsThis(tr("Enables you to add, modify, or delete"
2305         " entries in this phrase book."));
2306 
2307     a = m_ui.menuPrintPhraseBook->addAction(pb->friendlyPhraseBookName());
2308     m_phraseBookMenu[PhrasePrintMenu].insert(a, pb);
2309     a->setWhatsThis(tr("Print the entries in this phrase book."));
2310 
2311     connect(pb, SIGNAL(listChanged()), this, SLOT(updatePhraseDicts()));
2312     updatePhraseDicts();
2313     updatePhraseBookActions();
2314 
2315     return pb;
2316 }
2317 
savePhraseBook(QString * name,PhraseBook & pb)2318 bool MainWindow::savePhraseBook(QString *name, PhraseBook &pb)
2319 {
2320     if (!name->contains(QLatin1Char('.')))
2321         *name += QLatin1String(".qph");
2322 
2323     if (!pb.save(*name)) {
2324         QMessageBox::warning(this, tr("Qt Linguist"),
2325             tr("Cannot create phrase book '%1'.").arg(*name));
2326         return false;
2327     }
2328     return true;
2329 }
2330 
maybeSavePhraseBook(PhraseBook * pb)2331 bool MainWindow::maybeSavePhraseBook(PhraseBook *pb)
2332 {
2333     if (pb->isModified())
2334         switch (QMessageBox::information(this, tr("Qt Linguist"),
2335             tr("Do you want to save phrase book '%1'?").arg(pb->friendlyPhraseBookName()),
2336             QMessageBox::Yes | QMessageBox::Default,
2337             QMessageBox::No,
2338             QMessageBox::Cancel | QMessageBox::Escape))
2339         {
2340             case QMessageBox::Cancel:
2341                 return false;
2342             case QMessageBox::Yes:
2343                 if (!pb->save(pb->fileName()))
2344                     return false;
2345                 break;
2346             case QMessageBox::No:
2347                 break;
2348         }
2349     return true;
2350 }
2351 
maybeSavePhraseBooks()2352 bool MainWindow::maybeSavePhraseBooks()
2353 {
2354     foreach(PhraseBook *phraseBook, m_phraseBooks)
2355         if (!maybeSavePhraseBook(phraseBook))
2356             return false;
2357     return true;
2358 }
2359 
updateProgress()2360 void 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(numFinished).arg(numEditable));
2369         m_progressLabel->setToolTip(tr("%n unfinished message(s) left.", 0,
2370                                        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 
updatePhraseBookActions()2382 void 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(m_currentIndex.model()));
2387     m_ui.actionAddToPhraseBook->setEnabled(currentMessageIndex().isValid() && phraseBookLoaded);
2388 }
2389 
updatePhraseDictInternal(int model)2390 void 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(model)->country());
2401         } else {
2402             before = false;
2403         }
2404         foreach (Phrase *p, pb->phrases()) {
2405             QString f = friendlyString(p->source());
2406             if (f.length() > 0) {
2407                 f = f.split(QLatin1Char(' ')).first();
2408                 if (!pd.contains(f)) {
2409                     pd.insert(f, QList<Phrase *>());
2410                 }
2411                 if (before)
2412                     pd[f].prepend(p);
2413                 else
2414                     pd[f].append(p);
2415             }
2416         }
2417     }
2418 }
2419 
updatePhraseDict(int model)2420 void MainWindow::updatePhraseDict(int model)
2421 {
2422     updatePhraseDictInternal(model);
2423     m_phraseView->update();
2424 }
2425 
updatePhraseDicts()2426 void MainWindow::updatePhraseDicts()
2427 {
2428     for (int i = 0; i < m_phraseDict.size(); ++i)
2429         if (!m_dataModel->isModelWritable(i))
2430             m_phraseDict[i].clear();
2431         else
2432             updatePhraseDictInternal(i);
2433     revalidate();
2434     m_phraseView->update();
2435 }
2436 
haveMnemonic(const QString & str)2437 static 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 
updateDanger(const MultiDataIndex & index,bool verbose)2463 void 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(mi))
2471             continue;
2472         curIdx.setModel(mi);
2473         MessageItem *m = m_dataModel->messageItem(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(QChar(Translator::BinaryVariantSeparator));
2489                 if (sep >= 0)
2490                     translations[i].truncate(sep);
2491             }
2492 
2493             if (m_ui.actionAccelerators->isChecked()) {
2494                 bool sk = haveMnemonic(source);
2495                 bool tk = true;
2496                 for (int i = 0; i < translations.count() && tk; ++i) {
2497                     tk &= haveMnemonic(translations[i]);
2498                 }
2499 
2500                 if (!sk && tk) {
2501                     if (verbose)
2502                         m_errorsView->addError(mi, ErrorsView::SuperfluousAccelerator);
2503                     danger = true;
2504                 } else if (sk && !tk) {
2505                     if (verbose)
2506                         m_errorsView->addError(mi, 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(source) == leadingWhitespace(translations[i]));
2514                     whitespaceok &= (trailingWhitespace(source) == trailingWhitespace(translations[i]));
2515                 }
2516 
2517                 if (!whitespaceok) {
2518                     if (verbose)
2519                         m_errorsView->addError(mi, 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(source, m_dataModel->sourceLanguage(mi)) ==
2527                                 ending(translations[i], m_dataModel->language(mi)));
2528                 }
2529 
2530                 if (!endingok) {
2531                     if (verbose)
2532                         m_errorsView->addError(mi, ErrorsView::PunctuationDiffers);
2533                     danger = true;
2534                 }
2535             }
2536             if (m_ui.actionPhraseMatches->isChecked()) {
2537                 QString fsource = friendlyString(source);
2538                 QString ftranslation = friendlyString(translations.first());
2539                 QStringList lookupWords = fsource.split(QLatin1Char(' '));
2540 
2541                 bool phraseFound;
2542                 foreach (const QString &s, lookupWords) {
2543                     if (m_phraseDict[mi].contains(s)) {
2544                         phraseFound = true;
2545                         foreach (const Phrase *p, m_phraseDict[mi].value(s)) {
2546                             if (fsource == friendlyString(p->source())) {
2547                                 if (ftranslation.indexOf(friendlyString(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(mi, ErrorsView::IgnoredPhrasebook, 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, escape_end - escape_start).toInt(&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(mi, 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(mi)->countRefNeeds().at(i)
2614                             && !(translations[i].contains(QLatin1String("%n"))
2615                             || translations[i].contains(QLatin1String("%Ln")))) {
2616                             if (verbose)
2617                                 m_errorsView->addError(mi, ErrorsView::NumerusMarkerMissing);
2618                             danger = true;
2619                             break;
2620                         }
2621                 }
2622             }
2623         }
2624 
2625         if (danger != m->danger())
2626             m_dataModel->setDanger(curIdx, danger);
2627     }
2628 
2629     if (verbose)
2630         statusBar()->showMessage(m_errorsView->firstError());
2631 }
2632 
readConfig()2633 void MainWindow::readConfig()
2634 {
2635     QSettings config;
2636 
2637     restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray());
2638     restoreState(config.value(settingPath("MainWindowState")).toByteArray());
2639 
2640     m_ui.actionAccelerators->setChecked(
2641         config.value(settingPath("Validators/Accelerator"), true).toBool());
2642     m_ui.actionSurroundingWhitespace->setChecked(
2643         config.value(settingPath("Validators/SurroundingWhitespace"), true).toBool());
2644     m_ui.actionEndingPunctuation->setChecked(
2645         config.value(settingPath("Validators/EndingPunctuation"), true).toBool());
2646     m_ui.actionPhraseMatches->setChecked(
2647         config.value(settingPath("Validators/PhraseMatch"), true).toBool());
2648     m_ui.actionPlaceMarkerMatches->setChecked(
2649         config.value(settingPath("Validators/PlaceMarkers"), true).toBool());
2650     m_ui.actionLengthVariants->setChecked(
2651         config.value(settingPath("Options/LengthVariants"), false).toBool());
2652     m_ui.actionVisualizeWhitespace->setChecked(
2653         config.value(settingPath("Options/VisualizeWhitespace"), true).toBool());
2654 
2655     m_messageEditor->setFontSize(
2656                 config.value(settingPath("Options/EditorFontsize"), font().pointSize()).toReal());
2657     m_phraseView->setMaxCandidates(config.value(settingPath("Options/NumberOfGuesses"),
2658                                                 PhraseView::getDefaultMaxCandidates()).toInt());
2659 
2660     recentFiles().readConfig();
2661 
2662     int size = config.beginReadArray(settingPath("OpenedPhraseBooks"));
2663     for (int i = 0; i < size; ++i) {
2664         config.setArrayIndex(i);
2665         openPhraseBook(config.value(QLatin1String("FileName")).toString());
2666     }
2667     config.endArray();
2668 }
2669 
writeConfig()2670 void MainWindow::writeConfig()
2671 {
2672     QSettings config;
2673     config.setValue(settingPath("Geometry/WindowGeometry"),
2674         saveGeometry());
2675     config.setValue(settingPath("Validators/Accelerator"),
2676         m_ui.actionAccelerators->isChecked());
2677     config.setValue(settingPath("Validators/SurroundingWhitespace"),
2678         m_ui.actionSurroundingWhitespace->isChecked());
2679     config.setValue(settingPath("Validators/EndingPunctuation"),
2680         m_ui.actionEndingPunctuation->isChecked());
2681     config.setValue(settingPath("Validators/PhraseMatch"),
2682         m_ui.actionPhraseMatches->isChecked());
2683     config.setValue(settingPath("Validators/PlaceMarkers"),
2684         m_ui.actionPlaceMarkerMatches->isChecked());
2685     config.setValue(settingPath("Options/LengthVariants"),
2686         m_ui.actionLengthVariants->isChecked());
2687     config.setValue(settingPath("Options/VisualizeWhitespace"),
2688         m_ui.actionVisualizeWhitespace->isChecked());
2689     config.setValue(settingPath("MainWindowState"),
2690         saveState());
2691     recentFiles().writeConfig();
2692 
2693     config.setValue(settingPath("Options/EditorFontsize"), m_messageEditor->fontSize());
2694     config.setValue(settingPath("Options/NumberOfGuesses"), m_phraseView->getMaxCandidates());
2695 
2696     config.beginWriteArray(settingPath("OpenedPhraseBooks"),
2697         m_phraseBooks.size());
2698     for (int i = 0; i < m_phraseBooks.size(); ++i) {
2699         config.setArrayIndex(i);
2700         config.setValue(QLatin1String("FileName"), m_phraseBooks.at(i)->fileName());
2701     }
2702     config.endArray();
2703 }
2704 
setupRecentFilesMenu()2705 void 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                     DataModel::prettifyFileName(str))->setData(str);
2713         } else {
2714             QMenu *menu = m_ui.menuRecentlyOpenedFiles->addMenu(
2715                            MultiDataModel::condenseFileNames(
2716                                 MultiDataModel::prettifyFileNames(strList)));
2717             menu->addAction(tr("All"))->setData(strList);
2718             foreach (const QString &str, strList)
2719                 menu->addAction(DataModel::prettifyFileName(str))->setData(str);
2720         }
2721 }
2722 
recentFileActivated(QAction * action)2723 void MainWindow::recentFileActivated(QAction *action)
2724 {
2725     openFiles(action->data().toStringList());
2726 }
2727 
toggleStatistics()2728 void MainWindow::toggleStatistics()
2729 {
2730     if (m_ui.actionStatistics->isChecked()) {
2731         if (!m_statistics) {
2732             m_statistics = new Statistics(this);
2733             connect(m_dataModel, SIGNAL(statsChanged(int,int,int,int,int,int)),
2734                 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 
toggleVisualizeWhitespace()2744 void MainWindow::toggleVisualizeWhitespace()
2745 {
2746     m_messageEditor->setVisualizeWhitespace(m_ui.actionVisualizeWhitespace->isChecked());
2747 }
2748 
maybeUpdateStatistics(const MultiDataIndex & index)2749 void MainWindow::maybeUpdateStatistics(const MultiDataIndex &index)
2750 {
2751     if (index.model() == m_currentIndex.model())
2752         updateStatistics();
2753 }
2754 
updateStatistics()2755 void 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(m_currentIndex.model())->updateStatistics();
2763 }
2764 
showTranslationSettings(int model)2765 void MainWindow::showTranslationSettings(int model)
2766 {
2767     if (!m_translationSettingsDialog)
2768         m_translationSettingsDialog = new TranslationSettingsDialog(this);
2769     m_translationSettingsDialog->setDataModel(m_dataModel->model(model));
2770     m_translationSettingsDialog->exec();
2771 }
2772 
showTranslationSettings()2773 void MainWindow::showTranslationSettings()
2774 {
2775     showTranslationSettings(m_currentIndex.model());
2776 }
2777 
eventFilter(QObject * object,QEvent * event)2778 bool 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(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(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(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 
2824 QT_END_NAMESPACE
2825