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