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 ¤tIndex, 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 ¤tIndex, 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