summaryrefslogtreecommitdiffstats
path: root/src/linguist/linguist/mainwindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/linguist/linguist/mainwindow.cpp')
-rw-r--r--src/linguist/linguist/mainwindow.cpp2724
1 files changed, 2724 insertions, 0 deletions
diff --git a/src/linguist/linguist/mainwindow.cpp b/src/linguist/linguist/mainwindow.cpp
new file mode 100644
index 000000000..e6fad1b38
--- /dev/null
+++ b/src/linguist/linguist/mainwindow.cpp
@@ -0,0 +1,2724 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Linguist of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/* TRANSLATOR MainWindow
+
+ This is the application's main window.
+*/
+
+#include "mainwindow.h"
+
+#include "batchtranslationdialog.h"
+#include "errorsview.h"
+#include "finddialog.h"
+#include "formpreviewview.h"
+#include "globals.h"
+#include "messageeditor.h"
+#include "messagemodel.h"
+#include "phrasebookbox.h"
+#include "phrasemodel.h"
+#include "phraseview.h"
+#include "printout.h"
+#include "sourcecodeview.h"
+#include "statistics.h"
+#include "translatedialog.h"
+#include "translationsettingsdialog.h"
+
+#include <QAction>
+#include <QApplication>
+#include <QBitmap>
+#include <QCloseEvent>
+#include <QDebug>
+#include <QDesktopWidget>
+#include <QDockWidget>
+#include <QFile>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QHeaderView>
+#include <QInputDialog>
+#include <QItemDelegate>
+#include <QLabel>
+#include <QLayout>
+#include <QLibraryInfo>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QPrintDialog>
+#include <QPrinter>
+#include <QProcess>
+#include <QRegExp>
+#include <QSettings>
+#include <QSortFilterProxyModel>
+#include <QStackedWidget>
+#include <QStatusBar>
+#include <QTextStream>
+#include <QToolBar>
+#include <QUrl>
+#include <QWhatsThis>
+
+#include <ctype.h>
+
+QT_BEGIN_NAMESPACE
+
+static const int MessageMS = 2500;
+
+enum Ending {
+ End_None,
+ End_FullStop,
+ End_Interrobang,
+ End_Colon,
+ End_Ellipsis
+};
+
+static bool hasFormPreview(const QString &fileName)
+{
+ return fileName.endsWith(QLatin1String(".ui"))
+ || fileName.endsWith(QLatin1String(".jui"));
+}
+
+static Ending ending(QString str, QLocale::Language lang)
+{
+ str = str.simplified();
+ if (str.isEmpty())
+ return End_None;
+
+ switch (str.at(str.length() - 1).unicode()) {
+ case 0x002e: // full stop
+ if (str.endsWith(QLatin1String("...")))
+ return End_Ellipsis;
+ else
+ return End_FullStop;
+ case 0x0589: // armenian full stop
+ case 0x06d4: // arabic full stop
+ case 0x3002: // ideographic full stop
+ return End_FullStop;
+ case 0x0021: // exclamation mark
+ case 0x003f: // question mark
+ case 0x00a1: // inverted exclamation mark
+ case 0x00bf: // inverted question mark
+ case 0x01c3: // latin letter retroflex click
+ case 0x037e: // greek question mark
+ case 0x061f: // arabic question mark
+ case 0x203c: // double exclamation mark
+ case 0x203d: // interrobang
+ case 0x2048: // question exclamation mark
+ case 0x2049: // exclamation question mark
+ case 0x2762: // heavy exclamation mark ornament
+ case 0xff01: // full width exclamation mark
+ case 0xff1f: // full width question mark
+ return End_Interrobang;
+ case 0x003b: // greek 'compatibility' questionmark
+ return lang == QLocale::Greek ? End_Interrobang : End_None;
+ case 0x003a: // colon
+ case 0xff1a: // full width colon
+ return End_Colon;
+ case 0x2026: // horizontal ellipsis
+ return End_Ellipsis;
+ default:
+ return End_None;
+ }
+}
+
+
+class ContextItemDelegate : public QItemDelegate
+{
+public:
+ ContextItemDelegate(QObject *parent, MultiDataModel *model) : QItemDelegate(parent), m_dataModel(model) {}
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+ {
+ const QAbstractItemModel *model = index.model();
+ Q_ASSERT(model);
+
+ if (!model->parent(index).isValid()) {
+ if (index.column() - 1 == m_dataModel->modelCount()) {
+ QStyleOptionViewItem opt = option;
+ opt.font.setBold(true);
+ QItemDelegate::paint(painter, opt, index);
+ return;
+ }
+ }
+ QItemDelegate::paint(painter, option, index);
+ }
+
+private:
+ MultiDataModel *m_dataModel;
+};
+
+static const QVariant &pxObsolete()
+{
+ static const QVariant v =
+ QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_obsolete.png")));
+ return v;
+}
+
+
+class SortedMessagesModel : public QSortFilterProxyModel
+{
+public:
+ SortedMessagesModel(QObject *parent, MultiDataModel *model) : QSortFilterProxyModel(parent), m_dataModel(model) {}
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const
+ {
+ if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
+ switch (section - m_dataModel->modelCount()) {
+ case 0: return QString();
+ case 1: return MainWindow::tr("Source text");
+ case 2: return MainWindow::tr("Index");
+ }
+
+ if (role == Qt::DecorationRole && orientation == Qt::Horizontal && section - 1 < m_dataModel->modelCount())
+ return pxObsolete();
+
+ return QVariant();
+ }
+
+private:
+ MultiDataModel *m_dataModel;
+};
+
+class SortedContextsModel : public QSortFilterProxyModel
+{
+public:
+ SortedContextsModel(QObject *parent, MultiDataModel *model) : QSortFilterProxyModel(parent), m_dataModel(model) {}
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const
+ {
+ if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
+ switch (section - m_dataModel->modelCount()) {
+ case 0: return QString();
+ case 1: return MainWindow::tr("Context");
+ case 2: return MainWindow::tr("Items");
+ case 3: return MainWindow::tr("Index");
+ }
+
+ if (role == Qt::DecorationRole && orientation == Qt::Horizontal && section - 1 < m_dataModel->modelCount())
+ return pxObsolete();
+
+ return QVariant();
+ }
+
+private:
+ MultiDataModel *m_dataModel;
+};
+
+class FocusWatcher : public QObject
+{
+public:
+ FocusWatcher(MessageEditor *msgedit, QObject *parent) : QObject(parent), m_messageEditor(msgedit) {}
+
+protected:
+ bool eventFilter(QObject *object, QEvent *event);
+
+private:
+ MessageEditor *m_messageEditor;
+};
+
+bool FocusWatcher::eventFilter(QObject *, QEvent *event)
+{
+ if (event->type() == QEvent::FocusIn)
+ m_messageEditor->setEditorFocus(-1);
+ return false;
+}
+
+MainWindow::MainWindow()
+ : QMainWindow(0, Qt::Window),
+ m_assistantProcess(0),
+ m_printer(0),
+ m_findMatchCase(Qt::CaseInsensitive),
+ m_findIgnoreAccelerators(true),
+ m_findWhere(DataModel::NoLocation),
+ m_foundWhere(DataModel::NoLocation),
+ m_translationSettingsDialog(0),
+ m_settingCurrentMessage(false),
+ m_fileActiveModel(-1),
+ m_editActiveModel(-1),
+ m_statistics(0)
+{
+ setUnifiedTitleAndToolBarOnMac(true);
+ m_ui.setupUi(this);
+
+#ifndef Q_WS_MAC
+ setWindowIcon(QPixmap(QLatin1String(":/images/appicon.png") ));
+#endif
+
+ m_dataModel = new MultiDataModel(this);
+ m_messageModel = new MessageModel(this, m_dataModel);
+
+ // Set up the context dock widget
+ m_contextDock = new QDockWidget(this);
+ m_contextDock->setObjectName(QLatin1String("ContextDockWidget"));
+ m_contextDock->setAllowedAreas(Qt::AllDockWidgetAreas);
+ m_contextDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
+ m_contextDock->setWindowTitle(tr("Context"));
+ m_contextDock->setAcceptDrops(true);
+ m_contextDock->installEventFilter(this);
+
+ m_sortedContextsModel = new SortedContextsModel(this, m_dataModel);
+ m_sortedContextsModel->setSortRole(MessageModel::SortRole);
+ m_sortedContextsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+ m_sortedContextsModel->setSourceModel(m_messageModel);
+
+ m_contextView = new QTreeView(this);
+ m_contextView->setRootIsDecorated(false);
+ m_contextView->setItemsExpandable(false);
+ m_contextView->setUniformRowHeights(true);
+ m_contextView->setAlternatingRowColors(true);
+ m_contextView->setAllColumnsShowFocus(true);
+ m_contextView->setItemDelegate(new ContextItemDelegate(this, m_dataModel));
+ m_contextView->setSortingEnabled(true);
+ m_contextView->setWhatsThis(tr("This panel lists the source contexts."));
+ m_contextView->setModel(m_sortedContextsModel);
+ m_contextView->header()->setMovable(false);
+ m_contextView->setColumnHidden(0, true);
+ m_contextView->header()->setStretchLastSection(false);
+
+ m_contextDock->setWidget(m_contextView);
+
+ // Set up the messages dock widget
+ m_messagesDock = new QDockWidget(this);
+ m_messagesDock->setObjectName(QLatin1String("StringsDockWidget"));
+ m_messagesDock->setAllowedAreas(Qt::AllDockWidgetAreas);
+ m_messagesDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
+ m_messagesDock->setWindowTitle(tr("Strings"));
+ m_messagesDock->setAcceptDrops(true);
+ m_messagesDock->installEventFilter(this);
+
+ m_sortedMessagesModel = new SortedMessagesModel(this, m_dataModel);
+ m_sortedMessagesModel->setSortRole(MessageModel::SortRole);
+ m_sortedMessagesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+ m_sortedMessagesModel->setSortLocaleAware(true);
+ m_sortedMessagesModel->setSourceModel(m_messageModel);
+
+ m_messageView = new QTreeView(m_messagesDock);
+ m_messageView->setSortingEnabled(true);
+ m_messageView->setRootIsDecorated(false);
+ m_messageView->setUniformRowHeights(true);
+ m_messageView->setAllColumnsShowFocus(true);
+ m_messageView->setItemsExpandable(false);
+ m_messageView->setModel(m_sortedMessagesModel);
+ m_messageView->header()->setMovable(false);
+ m_messageView->setColumnHidden(0, true);
+
+ m_messagesDock->setWidget(m_messageView);
+
+ // Set up main message view
+ m_messageEditor = new MessageEditor(m_dataModel, this);
+ m_messageEditor->setAcceptDrops(true);
+ m_messageEditor->installEventFilter(this);
+ // We can't call setCentralWidget(m_messageEditor), since it is already called in m_ui.setupUi()
+ QBoxLayout *lout = new QBoxLayout(QBoxLayout::TopToBottom, m_ui.centralwidget);
+ lout->addWidget(m_messageEditor);
+ lout->setMargin(0);
+ m_ui.centralwidget->setLayout(lout);
+
+ // Set up the phrases & guesses dock widget
+ m_phrasesDock = new QDockWidget(this);
+ m_phrasesDock->setObjectName(QLatin1String("PhrasesDockwidget"));
+ m_phrasesDock->setAllowedAreas(Qt::AllDockWidgetAreas);
+ m_phrasesDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
+ m_phrasesDock->setWindowTitle(tr("Phrases and guesses"));
+
+ m_phraseView = new PhraseView(m_dataModel, &m_phraseDict, this);
+ m_phrasesDock->setWidget(m_phraseView);
+
+ // Set up source code and form preview dock widget
+ m_sourceAndFormDock = new QDockWidget(this);
+ m_sourceAndFormDock->setObjectName(QLatin1String("SourceAndFormDock"));
+ m_sourceAndFormDock->setAllowedAreas(Qt::AllDockWidgetAreas);
+ m_sourceAndFormDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
+ m_sourceAndFormDock->setWindowTitle(tr("Sources and Forms"));
+ m_sourceAndFormView = new QStackedWidget(this);
+ m_sourceAndFormDock->setWidget(m_sourceAndFormView);
+ //connect(m_sourceAndDock, SIGNAL(visibilityChanged(bool)),
+ // m_sourceCodeView, SLOT(setActivated(bool)));
+ m_formPreviewView = new FormPreviewView(0, m_dataModel);
+ m_sourceCodeView = new SourceCodeView(0);
+ m_sourceAndFormView->addWidget(m_sourceCodeView);
+ m_sourceAndFormView->addWidget(m_formPreviewView);
+
+ // Set up errors dock widget
+ m_errorsDock = new QDockWidget(this);
+ m_errorsDock->setObjectName(QLatin1String("ErrorsDockWidget"));
+ m_errorsDock->setAllowedAreas(Qt::AllDockWidgetAreas);
+ m_errorsDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
+ m_errorsDock->setWindowTitle(tr("Warnings"));
+ m_errorsView = new ErrorsView(m_dataModel, this);
+ m_errorsDock->setWidget(m_errorsView);
+
+ // Arrange dock widgets
+ setDockNestingEnabled(true);
+ setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
+ setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
+ setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
+ setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
+ addDockWidget(Qt::LeftDockWidgetArea, m_contextDock);
+ addDockWidget(Qt::TopDockWidgetArea, m_messagesDock);
+ addDockWidget(Qt::BottomDockWidgetArea, m_phrasesDock);
+ addDockWidget(Qt::TopDockWidgetArea, m_sourceAndFormDock);
+ addDockWidget(Qt::BottomDockWidgetArea, m_errorsDock);
+ //tabifyDockWidget(m_errorsDock, m_sourceAndFormDock);
+ //tabifyDockWidget(m_sourceCodeDock, m_phrasesDock);
+
+ // Allow phrases doc to intercept guesses shortcuts
+ m_messageEditor->installEventFilter(m_phraseView);
+
+ // Set up shortcuts for the dock widgets
+ QShortcut *contextShortcut = new QShortcut(QKeySequence(Qt::Key_F6), this);
+ connect(contextShortcut, SIGNAL(activated()), this, SLOT(showContextDock()));
+ QShortcut *messagesShortcut = new QShortcut(QKeySequence(Qt::Key_F7), this);
+ connect(messagesShortcut, SIGNAL(activated()), this, SLOT(showMessagesDock()));
+ QShortcut *errorsShortcut = new QShortcut(QKeySequence(Qt::Key_F8), this);
+ connect(errorsShortcut, SIGNAL(activated()), this, SLOT(showErrorDock()));
+ QShortcut *sourceCodeShortcut = new QShortcut(QKeySequence(Qt::Key_F9), this);
+ connect(sourceCodeShortcut, SIGNAL(activated()), this, SLOT(showSourceCodeDock()));
+ QShortcut *phrasesShortcut = new QShortcut(QKeySequence(Qt::Key_F10), this);
+ connect(phrasesShortcut, SIGNAL(activated()), this, SLOT(showPhrasesDock()));
+
+ connect(m_phraseView, SIGNAL(phraseSelected(int,QString)),
+ m_messageEditor, SLOT(setTranslation(int,QString)));
+ connect(m_contextView->selectionModel(),
+ SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
+ this, SLOT(selectedContextChanged(QModelIndex,QModelIndex)));
+ connect(m_messageView->selectionModel(),
+ SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
+ this, SLOT(selectedMessageChanged(QModelIndex,QModelIndex)));
+ connect(m_contextView->selectionModel(),
+ SIGNAL(currentColumnChanged(QModelIndex,QModelIndex)),
+ SLOT(updateLatestModel(QModelIndex)));
+ connect(m_messageView->selectionModel(),
+ SIGNAL(currentColumnChanged(QModelIndex,QModelIndex)),
+ SLOT(updateLatestModel(QModelIndex)));
+
+ connect(m_messageEditor, SIGNAL(activeModelChanged(int)), SLOT(updateActiveModel(int)));
+
+ m_translateDialog = new TranslateDialog(this);
+ m_batchTranslateDialog = new BatchTranslationDialog(m_dataModel, this);
+ m_findDialog = new FindDialog(this);
+
+ setupMenuBar();
+ setupToolBars();
+
+ m_progressLabel = new QLabel();
+ statusBar()->addPermanentWidget(m_progressLabel);
+ m_modifiedLabel = new QLabel(tr(" MOD ", "status bar: file(s) modified"));
+ statusBar()->addPermanentWidget(m_modifiedLabel);
+
+ modelCountChanged();
+ initViewHeaders();
+ resetSorting();
+
+ connect(m_dataModel, SIGNAL(modifiedChanged(bool)),
+ this, SLOT(setWindowModified(bool)));
+ connect(m_dataModel, SIGNAL(modifiedChanged(bool)),
+ m_modifiedLabel, SLOT(setVisible(bool)));
+ connect(m_dataModel, SIGNAL(multiContextDataChanged(MultiDataIndex)),
+ SLOT(updateProgress()));
+ connect(m_dataModel, SIGNAL(messageDataChanged(MultiDataIndex)),
+ SLOT(maybeUpdateStatistics(MultiDataIndex)));
+ connect(m_dataModel, SIGNAL(translationChanged(MultiDataIndex)),
+ SLOT(translationChanged(MultiDataIndex)));
+ connect(m_dataModel, SIGNAL(languageChanged(int)),
+ SLOT(updatePhraseDict(int)));
+
+ setWindowModified(m_dataModel->isModified());
+ m_modifiedLabel->setVisible(m_dataModel->isModified());
+
+ connect(m_messageView, SIGNAL(clicked(QModelIndex)),
+ this, SLOT(toggleFinished(QModelIndex)));
+ connect(m_messageView, SIGNAL(activated(QModelIndex)),
+ m_messageEditor, SLOT(setEditorFocus()));
+ connect(m_contextView, SIGNAL(activated(QModelIndex)),
+ m_messageView, SLOT(setFocus()));
+ connect(m_messageEditor, SIGNAL(translationChanged(QStringList)),
+ this, SLOT(updateTranslation(QStringList)));
+ connect(m_messageEditor, SIGNAL(translatorCommentChanged(QString)),
+ this, SLOT(updateTranslatorComment(QString)));
+ connect(m_findDialog, SIGNAL(findNext(QString,DataModel::FindLocation,bool,bool)),
+ this, SLOT(findNext(QString,DataModel::FindLocation,bool,bool)));
+ connect(m_translateDialog, SIGNAL(requestMatchUpdate(bool&)), SLOT(updateTranslateHit(bool&)));
+ connect(m_translateDialog, SIGNAL(activated(int)), SLOT(translate(int)));
+
+ QSize as(qApp->desktop()->size());
+ as -= QSize(30, 30);
+ resize(QSize(1000, 800).boundedTo(as));
+ show();
+ readConfig();
+ m_statistics = 0;
+
+ connect(m_ui.actionLengthVariants, SIGNAL(toggled(bool)),
+ m_messageEditor, SLOT(setLengthVariants(bool)));
+ m_messageEditor->setLengthVariants(m_ui.actionLengthVariants->isChecked());
+
+ m_focusWatcher = new FocusWatcher(m_messageEditor, this);
+ m_contextView->installEventFilter(m_focusWatcher);
+ m_messageView->installEventFilter(m_focusWatcher);
+ m_messageEditor->installEventFilter(m_focusWatcher);
+ m_sourceAndFormView->installEventFilter(m_focusWatcher);
+ m_phraseView->installEventFilter(m_focusWatcher);
+ m_errorsView->installEventFilter(m_focusWatcher);
+}
+
+MainWindow::~MainWindow()
+{
+ writeConfig();
+ if (m_assistantProcess && m_assistantProcess->state() == QProcess::Running) {
+ m_assistantProcess->terminate();
+ m_assistantProcess->waitForFinished(3000);
+ }
+ qDeleteAll(m_phraseBooks);
+ delete m_dataModel;
+ delete m_statistics;
+ delete m_printer;
+}
+
+void MainWindow::initViewHeaders()
+{
+ m_contextView->header()->setResizeMode(1, QHeaderView::Stretch);
+ m_contextView->header()->setResizeMode(2, QHeaderView::ResizeToContents);
+ m_messageView->setColumnHidden(2, true);
+ // last visible column auto-stretches
+}
+
+void MainWindow::modelCountChanged()
+{
+ int mc = m_dataModel->modelCount();
+
+ for (int i = 0; i < mc; ++i) {
+ m_contextView->header()->setResizeMode(i + 1, QHeaderView::Fixed);
+ m_contextView->header()->resizeSection(i + 1, 24);
+
+ m_messageView->header()->setResizeMode(i + 1, QHeaderView::Fixed);
+ m_messageView->header()->resizeSection(i + 1, 24);
+ }
+
+ if (!mc) {
+ selectedMessageChanged(QModelIndex(), QModelIndex());
+ updateLatestModel(-1);
+ } else {
+ if (!m_contextView->currentIndex().isValid()) {
+ // Ensure that something is selected
+ m_contextView->setCurrentIndex(m_sortedContextsModel->index(0, 0));
+ } else {
+ // Plug holes that turn up in the selection due to inserting columns
+ m_contextView->selectionModel()->select(m_contextView->currentIndex(),
+ QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
+ m_messageView->selectionModel()->select(m_messageView->currentIndex(),
+ QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
+ }
+ // Field insertions/removals are automatic, but not the re-fill
+ m_messageEditor->showMessage(m_currentIndex);
+ if (mc == 1)
+ updateLatestModel(0);
+ else if (m_currentIndex.model() >= mc)
+ updateLatestModel(mc - 1);
+ }
+
+ m_contextView->setUpdatesEnabled(true);
+ m_messageView->setUpdatesEnabled(true);
+
+ updateProgress();
+ updateCaption();
+
+ m_ui.actionFind->setEnabled(m_dataModel->contextCount() > 0);
+ m_ui.actionFindNext->setEnabled(false);
+
+ m_formPreviewView->setSourceContext(-1, 0);
+}
+
+struct OpenedFile {
+ OpenedFile(DataModel *_dataModel, bool _readWrite, bool _langGuessed)
+ { dataModel = _dataModel; readWrite = _readWrite; langGuessed = _langGuessed; }
+ DataModel *dataModel;
+ bool readWrite;
+ bool langGuessed;
+};
+
+bool MainWindow::openFiles(const QStringList &names, bool globalReadWrite)
+{
+ if (names.isEmpty())
+ return false;
+
+ bool waitCursor = false;
+ statusBar()->showMessage(tr("Loading..."));
+ qApp->processEvents();
+
+ QList<OpenedFile> opened;
+ bool closeOld = false;
+ foreach (QString name, names) {
+ if (!waitCursor) {
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+ waitCursor = true;
+ }
+
+ bool readWrite = globalReadWrite;
+ if (name.startsWith(QLatin1Char('='))) {
+ name.remove(0, 1);
+ readWrite = false;
+ }
+ QFileInfo fi(name);
+ if (fi.exists()) // Make the loader error out instead of reading stdin
+ name = fi.canonicalFilePath();
+ if (m_dataModel->isFileLoaded(name) >= 0)
+ continue;
+
+ bool langGuessed;
+ DataModel *dm = new DataModel(m_dataModel);
+ if (!dm->load(name, &langGuessed, this)) {
+ delete dm;
+ continue;
+ }
+ if (opened.isEmpty()) {
+ if (!m_dataModel->isWellMergeable(dm)) {
+ QApplication::restoreOverrideCursor();
+ waitCursor = false;
+ switch (QMessageBox::information(this, tr("Loading File - Qt Linguist"),
+ tr("The file '%1' does not seem to be related to the currently open file(s) '%2'.\n\n"
+ "Close the open file(s) first?")
+ .arg(DataModel::prettifyPlainFileName(name), m_dataModel->condensedSrcFileNames(true)),
+ QMessageBox::Yes | QMessageBox::Default,
+ QMessageBox::No,
+ QMessageBox::Cancel | QMessageBox::Escape))
+ {
+ case QMessageBox::Cancel:
+ delete dm;
+ return false;
+ case QMessageBox::Yes:
+ closeOld = true;
+ break;
+ case QMessageBox::No:
+ break;
+ }
+ }
+ } else {
+ if (!opened.first().dataModel->isWellMergeable(dm)) {
+ QApplication::restoreOverrideCursor();
+ waitCursor = false;
+ switch (QMessageBox::information(this, tr("Loading File - Qt Linguist"),
+ tr("The file '%1' does not seem to be related to the file '%2'"
+ " which is being loaded as well.\n\n"
+ "Skip loading the first named file?")
+ .arg(DataModel::prettifyPlainFileName(name), opened.first().dataModel->srcFileName(true)),
+ QMessageBox::Yes | QMessageBox::Default,
+ QMessageBox::No,
+ QMessageBox::Cancel | QMessageBox::Escape))
+ {
+ case QMessageBox::Cancel:
+ delete dm;
+ foreach (const OpenedFile &op, opened)
+ delete op.dataModel;
+ return false;
+ case QMessageBox::Yes:
+ delete dm;
+ continue;
+ case QMessageBox::No:
+ break;
+ }
+ }
+ }
+ opened.append(OpenedFile(dm, readWrite, langGuessed));
+ }
+
+ if (closeOld) {
+ if (waitCursor) {
+ QApplication::restoreOverrideCursor();
+ waitCursor = false;
+ }
+ if (!closeAll()) {
+ foreach (const OpenedFile &op, opened)
+ delete op.dataModel;
+ return false;
+ }
+ }
+
+ foreach (const OpenedFile &op, opened) {
+ if (op.langGuessed) {
+ if (waitCursor) {
+ QApplication::restoreOverrideCursor();
+ waitCursor = false;
+ }
+ if (!m_translationSettingsDialog)
+ m_translationSettingsDialog = new TranslationSettingsDialog(this);
+ m_translationSettingsDialog->setDataModel(op.dataModel);
+ m_translationSettingsDialog->exec();
+ }
+ }
+
+ if (!waitCursor)
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+ m_contextView->setUpdatesEnabled(false);
+ m_messageView->setUpdatesEnabled(false);
+ int totalCount = 0;
+ foreach (const OpenedFile &op, opened) {
+ m_phraseDict.append(QHash<QString, QList<Phrase *> >());
+ m_dataModel->append(op.dataModel, op.readWrite);
+ if (op.readWrite)
+ updatePhraseDictInternal(m_phraseDict.size() - 1);
+ totalCount += op.dataModel->messageCount();
+ }
+ statusBar()->showMessage(tr("%n translation unit(s) loaded.", 0, totalCount), MessageMS);
+ modelCountChanged();
+ recentFiles().addFiles(m_dataModel->srcFileNames());
+
+ revalidate();
+ QApplication::restoreOverrideCursor();
+ return true;
+}
+
+RecentFiles &MainWindow::recentFiles()
+{
+ static RecentFiles recentFiles(10);
+ return recentFiles;
+}
+
+const QString &MainWindow::resourcePrefix()
+{
+#ifdef Q_WS_MAC
+ static const QString prefix(QLatin1String(":/images/mac"));
+#else
+ static const QString prefix(QLatin1String(":/images/win"));
+#endif
+ return prefix;
+}
+
+void MainWindow::open()
+{
+ openFiles(pickTranslationFiles());
+}
+
+void MainWindow::openAux()
+{
+ openFiles(pickTranslationFiles(), false);
+}
+
+void MainWindow::closeFile()
+{
+ int model = m_currentIndex.model();
+ if (model >= 0 && maybeSave(model)) {
+ m_phraseDict.removeAt(model);
+ m_contextView->setUpdatesEnabled(false);
+ m_messageView->setUpdatesEnabled(false);
+ m_dataModel->close(model);
+ modelCountChanged();
+ }
+}
+
+bool MainWindow::closeAll()
+{
+ if (maybeSaveAll()) {
+ m_phraseDict.clear();
+ m_contextView->setUpdatesEnabled(false);
+ m_messageView->setUpdatesEnabled(false);
+ m_dataModel->closeAll();
+ modelCountChanged();
+ initViewHeaders();
+ recentFiles().closeGroup();
+ return true;
+ }
+ return false;
+}
+
+static QString fileFilters(bool allFirst)
+{
+ static const QString pattern(QLatin1String("%1 (*.%2);;"));
+ QStringList allExtensions;
+ QString filter;
+ foreach (const Translator::FileFormat &format, Translator::registeredFileFormats()) {
+ if (format.fileType == Translator::FileFormat::TranslationSource && format.priority >= 0) {
+ filter.append(pattern.arg(format.description).arg(format.extension));
+ allExtensions.append(QLatin1String("*.") + format.extension);
+ }
+ }
+ QString allFilter = QObject::tr("Translation files (%1);;").arg(allExtensions.join(QLatin1String(" ")));
+ if (allFirst)
+ filter.prepend(allFilter);
+ else
+ filter.append(allFilter);
+ filter.append(QObject::tr("All files (*)"));
+ return filter;
+}
+
+QStringList MainWindow::pickTranslationFiles()
+{
+ QString dir;
+ if (!recentFiles().isEmpty())
+ dir = QFileInfo(recentFiles().lastOpenedFile()).path();
+
+ QString varFilt;
+ if (m_dataModel->modelCount()) {
+ QFileInfo mainFile(m_dataModel->srcFileName(0));
+ QString mainFileBase = mainFile.baseName();
+ int pos = mainFileBase.indexOf(QLatin1Char('_'));
+ if (pos > 0)
+ varFilt = tr("Related files (%1);;")
+ .arg(mainFileBase.left(pos) + QLatin1String("_*.") + mainFile.completeSuffix());
+ }
+
+ return QFileDialog::getOpenFileNames(this, tr("Open Translation Files"), dir,
+ varFilt +
+ fileFilters(true));
+}
+
+void MainWindow::saveInternal(int model)
+{
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+ if (m_dataModel->save(model, this)) {
+ updateCaption();
+ statusBar()->showMessage(tr("File saved."), MessageMS);
+ }
+ QApplication::restoreOverrideCursor();
+}
+
+void MainWindow::saveAll()
+{
+ for (int i = 0; i < m_dataModel->modelCount(); ++i)
+ if (m_dataModel->isModelWritable(i))
+ saveInternal(i);
+ recentFiles().closeGroup();
+}
+
+void MainWindow::save()
+{
+ if (m_currentIndex.model() < 0)
+ return;
+
+ saveInternal(m_currentIndex.model());
+}
+
+void MainWindow::saveAs()
+{
+ if (m_currentIndex.model() < 0)
+ return;
+
+ QString newFilename = QFileDialog::getSaveFileName(this, QString(), m_dataModel->srcFileName(m_currentIndex.model()),
+ fileFilters(false));
+ if (!newFilename.isEmpty()) {
+ if (m_dataModel->saveAs(m_currentIndex.model(), newFilename, this)) {
+ updateCaption();
+ statusBar()->showMessage(tr("File saved."), MessageMS);
+ recentFiles().addFiles(m_dataModel->srcFileNames());
+ }
+ }
+}
+
+void MainWindow::releaseAs()
+{
+ if (m_currentIndex.model() < 0)
+ return;
+
+ QFileInfo oldFile(m_dataModel->srcFileName(m_currentIndex.model()));
+ QString newFilename = oldFile.path() + QLatin1String("/")
+ + oldFile.completeBaseName() + QLatin1String(".qm");
+
+ newFilename = QFileDialog::getSaveFileName(this, tr("Release"), newFilename,
+ tr("Qt message files for released applications (*.qm)\nAll files (*)"));
+ if (!newFilename.isEmpty()) {
+ if (m_dataModel->release(m_currentIndex.model(), newFilename, false, false, SaveEverything, this))
+ statusBar()->showMessage(tr("File created."), MessageMS);
+ }
+}
+
+void MainWindow::releaseInternal(int model)
+{
+ QFileInfo oldFile(m_dataModel->srcFileName(model));
+ QString newFilename = oldFile.path() + QLatin1Char('/')
+ + oldFile.completeBaseName() + QLatin1String(".qm");
+
+ if (!newFilename.isEmpty()) {
+ if (m_dataModel->release(model, newFilename, false, false, SaveEverything, this))
+ statusBar()->showMessage(tr("File created."), MessageMS);
+ }
+}
+
+// No-question
+void MainWindow::release()
+{
+ if (m_currentIndex.model() < 0)
+ return;
+
+ releaseInternal(m_currentIndex.model());
+}
+
+void MainWindow::releaseAll()
+{
+ for (int i = 0; i < m_dataModel->modelCount(); ++i)
+ if (m_dataModel->isModelWritable(i))
+ releaseInternal(i);
+}
+
+QPrinter *MainWindow::printer()
+{
+ if (!m_printer)
+ m_printer = new QPrinter;
+ return m_printer;
+}
+
+void MainWindow::print()
+{
+ int pageNum = 0;
+ QPrintDialog dlg(printer(), this);
+ if (dlg.exec()) {
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+ printer()->setDocName(m_dataModel->condensedSrcFileNames(true));
+ statusBar()->showMessage(tr("Printing..."));
+ PrintOut pout(printer());
+
+ for (int i = 0; i < m_dataModel->contextCount(); ++i) {
+ MultiContextItem *mc = m_dataModel->multiContextItem(i);
+ pout.vskip();
+ pout.setRule(PrintOut::ThickRule);
+ pout.setGuide(mc->context());
+ pout.addBox(100, tr("Context: %1").arg(mc->context()),
+ PrintOut::Strong);
+ pout.flushLine();
+ pout.addBox(4);
+ pout.addBox(92, mc->comment(), PrintOut::Emphasis);
+ pout.flushLine();
+ pout.setRule(PrintOut::ThickRule);
+
+ for (int j = 0; j < mc->messageCount(); ++j) {
+ pout.setRule(PrintOut::ThinRule);
+ bool printedSrc = false;
+ QString comment;
+ for (int k = 0; k < m_dataModel->modelCount(); ++k) {
+ if (const MessageItem *m = mc->messageItem(k, j)) {
+ if (!printedSrc) {
+ pout.addBox(40, m->text());
+ pout.addBox(4);
+ comment = m->comment();
+ printedSrc = true;
+ } else {
+ pout.addBox(44); // Maybe put the name of the translation here
+ }
+ if (m->message().isPlural() && m_dataModel->language(k) != QLocale::C) {
+ QStringList transls = m->translations();
+ pout.addBox(40, transls.join(QLatin1String("\n")));
+ } else {
+ pout.addBox(40, m->translation());
+ }
+ pout.addBox(4);
+ QString type;
+ switch (m->message().type()) {
+ case TranslatorMessage::Finished:
+ type = tr("finished");
+ break;
+ case TranslatorMessage::Unfinished:
+ type = m->danger() ? tr("unresolved") : QLatin1String("unfinished");
+ break;
+ case TranslatorMessage::Obsolete:
+ type = tr("obsolete");
+ break;
+ }
+ pout.addBox(12, type, PrintOut::Normal, Qt::AlignRight);
+ pout.flushLine();
+ }
+ }
+ if (!comment.isEmpty()) {
+ pout.addBox(4);
+ pout.addBox(92, comment, PrintOut::Emphasis);
+ pout.flushLine(true);
+ }
+
+ if (pout.pageNum() != pageNum) {
+ pageNum = pout.pageNum();
+ statusBar()->showMessage(tr("Printing... (page %1)")
+ .arg(pageNum));
+ }
+ }
+ }
+ pout.flushLine(true);
+ QApplication::restoreOverrideCursor();
+ statusBar()->showMessage(tr("Printing completed"), MessageMS);
+ } else {
+ statusBar()->showMessage(tr("Printing aborted"), MessageMS);
+ }
+}
+
+bool MainWindow::searchItem(const QString &searchWhat)
+{
+ if ((m_findWhere & m_foundWhere) == 0)
+ return false;
+
+ QString text = searchWhat;
+
+ if (m_findIgnoreAccelerators)
+ // FIXME: This removes too much. The proper solution might be too slow, though.
+ text.remove(QLatin1Char('&'));
+
+ int foundOffset = text.indexOf(m_findText, 0, m_findMatchCase);
+ return foundOffset >= 0;
+}
+
+void MainWindow::findAgain()
+{
+ if (m_dataModel->contextCount() == 0)
+ return;
+
+ const QModelIndex &startIndex = m_messageView->currentIndex();
+ QModelIndex index = nextMessage(startIndex);
+
+ while (index.isValid()) {
+ QModelIndex realIndex = m_sortedMessagesModel->mapToSource(index);
+ MultiDataIndex dataIndex = m_messageModel->dataIndex(realIndex, -1);
+ bool hadMessage = false;
+ for (int i = 0; i < m_dataModel->modelCount(); ++i) {
+ if (MessageItem *m = m_dataModel->messageItem(dataIndex, i)) {
+ // Note: we do not look into plurals on grounds of them not
+ // containing anything much different from the singular.
+ if (hadMessage) {
+ m_foundWhere = DataModel::Translations;
+ if (!searchItem(m->translation()))
+ m_foundWhere = DataModel::NoLocation;
+ } else {
+ switch (m_foundWhere) {
+ case 0:
+ m_foundWhere = DataModel::SourceText;
+ // fall-through to search source text
+ case DataModel::SourceText:
+ if (searchItem(m->text()))
+ break;
+ if (searchItem(m->pluralText()))
+ break;
+ m_foundWhere = DataModel::Translations;
+ // fall-through to search translation
+ case DataModel::Translations:
+ if (searchItem(m->translation()))
+ break;
+ m_foundWhere = DataModel::Comments;
+ // fall-through to search comment
+ case DataModel::Comments:
+ if (searchItem(m->comment()))
+ break;
+ if (searchItem(m->extraComment()))
+ break;
+ if (searchItem(m->translatorComment()))
+ break;
+ m_foundWhere = DataModel::NoLocation;
+ // did not find the search string in this message
+ }
+ }
+ if (m_foundWhere != DataModel::NoLocation) {
+ setCurrentMessage(realIndex, i);
+
+ // determine whether the search wrapped
+ const QModelIndex &c1 = m_sortedContextsModel->mapFromSource(
+ m_sortedMessagesModel->mapToSource(startIndex)).parent();
+ const QModelIndex &c2 = m_sortedContextsModel->mapFromSource(realIndex).parent();
+ const QModelIndex &m = m_sortedMessagesModel->mapFromSource(realIndex);
+
+ if (c2.row() < c1.row() || (c1.row() == c2.row() && m.row() <= startIndex.row()))
+ statusBar()->showMessage(tr("Search wrapped."), MessageMS);
+
+ m_findDialog->hide();
+ return;
+ }
+ hadMessage = true;
+ }
+ }
+
+ // since we don't search startIndex at the beginning, only now we have searched everything
+ if (index == startIndex)
+ break;
+
+ index = nextMessage(index);
+ }
+
+ qApp->beep();
+ QMessageBox::warning(m_findDialog, tr("Qt Linguist"),
+ tr("Cannot find the string '%1'.").arg(m_findText));
+ m_foundWhere = DataModel::NoLocation;
+}
+
+void MainWindow::showBatchTranslateDialog()
+{
+ m_messageModel->blockSignals(true);
+ m_batchTranslateDialog->setPhraseBooks(m_phraseBooks, m_currentIndex.model());
+ if (m_batchTranslateDialog->exec() != QDialog::Accepted)
+ m_messageModel->blockSignals(false);
+ // else signal finished() calls refreshItemViews()
+}
+
+void MainWindow::showTranslateDialog()
+{
+ m_latestCaseSensitivity = -1;
+ QModelIndex idx = m_messageView->currentIndex();
+ QModelIndex idx2 = m_sortedMessagesModel->index(idx.row(), m_currentIndex.model() + 1, idx.parent());
+ m_messageView->setCurrentIndex(idx2);
+ QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
+ m_translateDialog->setWindowTitle(tr("Search And Translate in '%1' - Qt Linguist").arg(fn));
+ m_translateDialog->exec();
+}
+
+void MainWindow::updateTranslateHit(bool &hit)
+{
+ MessageItem *m;
+ hit = (m = m_dataModel->messageItem(m_currentIndex))
+ && !m->isObsolete()
+ && m->compare(m_translateDialog->findText(), false, m_translateDialog->caseSensitivity());
+}
+
+void MainWindow::translate(int mode)
+{
+ QString findText = m_translateDialog->findText();
+ QString replaceText = m_translateDialog->replaceText();
+ bool markFinished = m_translateDialog->markFinished();
+ Qt::CaseSensitivity caseSensitivity = m_translateDialog->caseSensitivity();
+
+ int translatedCount = 0;
+
+ if (mode == TranslateDialog::TranslateAll) {
+ for (MultiDataModelIterator it(m_dataModel, m_currentIndex.model()); it.isValid(); ++it) {
+ MessageItem *m = it.current();
+ if (m && !m->isObsolete() && m->compare(findText, false, caseSensitivity)) {
+ if (!translatedCount)
+ m_messageModel->blockSignals(true);
+ m_dataModel->setTranslation(it, replaceText);
+ m_dataModel->setFinished(it, markFinished);
+ ++translatedCount;
+ }
+ }
+ if (translatedCount) {
+ refreshItemViews();
+ QMessageBox::warning(m_translateDialog, tr("Translate - Qt Linguist"),
+ tr("Translated %n entry(s)", 0, translatedCount));
+ }
+ } else {
+ if (mode == TranslateDialog::Translate) {
+ m_dataModel->setTranslation(m_currentIndex, replaceText);
+ m_dataModel->setFinished(m_currentIndex, markFinished);
+ }
+
+ if (findText != m_latestFindText || caseSensitivity != m_latestCaseSensitivity) {
+ m_latestFindText = findText;
+ m_latestCaseSensitivity = caseSensitivity;
+ m_remainingCount = m_dataModel->messageCount();
+ m_hitCount = 0;
+ }
+
+ QModelIndex index = m_messageView->currentIndex();
+ int prevRemained = m_remainingCount;
+ forever {
+ if (--m_remainingCount <= 0) {
+ if (!m_hitCount)
+ break;
+ m_remainingCount = m_dataModel->messageCount() - 1;
+ if (QMessageBox::question(m_translateDialog, tr("Translate - Qt Linguist"),
+ tr("No more occurrences of '%1'. Start over?").arg(findText),
+ QMessageBox::Yes|QMessageBox::No) != QMessageBox::Yes)
+ return;
+ m_remainingCount -= prevRemained;
+ }
+
+ index = nextMessage(index);
+
+ QModelIndex realIndex = m_sortedMessagesModel->mapToSource(index);
+ MultiDataIndex dataIndex = m_messageModel->dataIndex(realIndex, m_currentIndex.model());
+ if (MessageItem *m = m_dataModel->messageItem(dataIndex)) {
+ if (!m->isObsolete() && m->compare(findText, false, caseSensitivity)) {
+ setCurrentMessage(realIndex, m_currentIndex.model());
+ ++translatedCount;
+ ++m_hitCount;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!translatedCount) {
+ qApp->beep();
+ QMessageBox::warning(m_translateDialog, tr("Translate - Qt Linguist"),
+ tr("Cannot find the string '%1'.").arg(findText));
+ }
+}
+
+void MainWindow::newPhraseBook()
+{
+ QString name = QFileDialog::getSaveFileName(this, tr("Create New Phrase Book"),
+ m_phraseBookDir, tr("Qt phrase books (*.qph)\nAll files (*)"));
+ if (!name.isEmpty()) {
+ PhraseBook pb;
+ if (!m_translationSettingsDialog)
+ m_translationSettingsDialog = new TranslationSettingsDialog(this);
+ m_translationSettingsDialog->setPhraseBook(&pb);
+ if (!m_translationSettingsDialog->exec())
+ return;
+ m_phraseBookDir = QFileInfo(name).absolutePath();
+ if (savePhraseBook(&name, pb)) {
+ if (openPhraseBook(name))
+ statusBar()->showMessage(tr("Phrase book created."), MessageMS);
+ }
+ }
+}
+
+bool MainWindow::isPhraseBookOpen(const QString &name)
+{
+ foreach(const PhraseBook *pb, m_phraseBooks) {
+ if (pb->fileName() == name)
+ return true;
+ }
+
+ return false;
+}
+
+void MainWindow::openPhraseBook()
+{
+ QString name = QFileDialog::getOpenFileName(this, tr("Open Phrase Book"),
+ m_phraseBookDir, tr("Qt phrase books (*.qph);;All files (*)"));
+
+ if (!name.isEmpty()) {
+ m_phraseBookDir = QFileInfo(name).absolutePath();
+ if (!isPhraseBookOpen(name)) {
+ if (PhraseBook *phraseBook = openPhraseBook(name)) {
+ int n = phraseBook->phrases().count();
+ statusBar()->showMessage(tr("%n phrase(s) loaded.", 0, n), MessageMS);
+ }
+ }
+ }
+}
+
+void MainWindow::closePhraseBook(QAction *action)
+{
+ PhraseBook *pb = m_phraseBookMenu[PhraseCloseMenu].value(action);
+ if (!maybeSavePhraseBook(pb))
+ return;
+
+ m_phraseBookMenu[PhraseCloseMenu].remove(action);
+ m_ui.menuClosePhraseBook->removeAction(action);
+
+ QAction *act = m_phraseBookMenu[PhraseEditMenu].key(pb);
+ m_phraseBookMenu[PhraseEditMenu].remove(act);
+ m_ui.menuEditPhraseBook->removeAction(act);
+
+ act = m_phraseBookMenu[PhrasePrintMenu].key(pb);
+ m_ui.menuPrintPhraseBook->removeAction(act);
+
+ m_phraseBooks.removeOne(pb);
+ disconnect(pb, SIGNAL(listChanged()), this, SLOT(updatePhraseDicts()));
+ updatePhraseDicts();
+ delete pb;
+ updatePhraseBookActions();
+}
+
+void MainWindow::editPhraseBook(QAction *action)
+{
+ PhraseBook *pb = m_phraseBookMenu[PhraseEditMenu].value(action);
+ PhraseBookBox box(pb, this);
+ box.exec();
+
+ updatePhraseDicts();
+}
+
+void MainWindow::printPhraseBook(QAction *action)
+{
+ PhraseBook *phraseBook = m_phraseBookMenu[PhrasePrintMenu].value(action);
+
+ int pageNum = 0;
+
+ QPrintDialog dlg(printer(), this);
+ if (dlg.exec()) {
+ printer()->setDocName(phraseBook->fileName());
+ statusBar()->showMessage(tr("Printing..."));
+ PrintOut pout(printer());
+ pout.setRule(PrintOut::ThinRule);
+ foreach (const Phrase *p, phraseBook->phrases()) {
+ pout.setGuide(p->source());
+ pout.addBox(29, p->source());
+ pout.addBox(4);
+ pout.addBox(29, p->target());
+ pout.addBox(4);
+ pout.addBox(34, p->definition(), PrintOut::Emphasis);
+
+ if (pout.pageNum() != pageNum) {
+ pageNum = pout.pageNum();
+ statusBar()->showMessage(tr("Printing... (page %1)")
+ .arg(pageNum));
+ }
+ pout.setRule(PrintOut::NoRule);
+ pout.flushLine(true);
+ }
+ pout.flushLine(true);
+ statusBar()->showMessage(tr("Printing completed"), MessageMS);
+ } else {
+ statusBar()->showMessage(tr("Printing aborted"), MessageMS);
+ }
+}
+
+void MainWindow::addToPhraseBook()
+{
+ MessageItem *currentMessage = m_dataModel->messageItem(m_currentIndex);
+ Phrase *phrase = new Phrase(currentMessage->text(), currentMessage->translation(), QString());
+ QStringList phraseBookList;
+ QHash<QString, PhraseBook *> phraseBookHash;
+ foreach (PhraseBook *pb, m_phraseBooks) {
+ if (pb->language() != QLocale::C && m_dataModel->language(m_currentIndex.model()) != QLocale::C) {
+ if (pb->language() != m_dataModel->language(m_currentIndex.model()))
+ continue;
+ if (pb->country() == m_dataModel->model(m_currentIndex.model())->country())
+ phraseBookList.prepend(pb->friendlyPhraseBookName());
+ else
+ phraseBookList.append(pb->friendlyPhraseBookName());
+ } else {
+ phraseBookList.append(pb->friendlyPhraseBookName());
+ }
+ phraseBookHash.insert(pb->friendlyPhraseBookName(), pb);
+ }
+ if (phraseBookList.isEmpty()) {
+ QMessageBox::warning(this, tr("Add to phrase book"),
+ tr("No appropriate phrasebook found."));
+ } else if (phraseBookList.size() == 1) {
+ if (QMessageBox::information(this, tr("Add to phrase book"),
+ tr("Adding entry to phrasebook %1").arg(phraseBookList.at(0)),
+ QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)
+ == QMessageBox::Ok)
+ phraseBookHash.value(phraseBookList.at(0))->append(phrase);
+ } else {
+ bool okPressed = false;
+ QString selection = QInputDialog::getItem(this, tr("Add to phrase book"),
+ tr("Select phrase book to add to"),
+ phraseBookList, 0, false, &okPressed);
+ if (okPressed)
+ phraseBookHash.value(selection)->append(phrase);
+ }
+}
+
+void MainWindow::resetSorting()
+{
+ m_contextView->sortByColumn(-1, Qt::AscendingOrder);
+ m_messageView->sortByColumn(-1, Qt::AscendingOrder);
+}
+
+void MainWindow::manual()
+{
+ if (!m_assistantProcess)
+ m_assistantProcess = new QProcess();
+
+ if (m_assistantProcess->state() != QProcess::Running) {
+ QString app = QLibraryInfo::location(QLibraryInfo::BinariesPath) + QDir::separator();
+#if !defined(Q_OS_MAC)
+ app += QLatin1String("assistant");
+#else
+ app += QLatin1String("Assistant.app/Contents/MacOS/Assistant");
+#endif
+
+ m_assistantProcess->start(app, QStringList() << QLatin1String("-enableRemoteControl"));
+ if (!m_assistantProcess->waitForStarted()) {
+ QMessageBox::critical(this, tr("Qt Linguist"),
+ tr("Unable to launch Qt Assistant (%1)").arg(app));
+ return;
+ }
+ }
+
+ QTextStream str(m_assistantProcess);
+ str << QLatin1String("SetSource qthelp://com.trolltech.linguist.")
+ << (QT_VERSION >> 16) << ((QT_VERSION >> 8) & 0xFF)
+ << (QT_VERSION & 0xFF)
+ << QLatin1String("/qdoc/linguist-manual.html")
+ << QLatin1Char('\n') << endl;
+}
+
+void MainWindow::about()
+{
+ QMessageBox box(this);
+ box.setTextFormat(Qt::RichText);
+ QString version = tr("Version %1");
+ version = version.arg(QLatin1String(QT_VERSION_STR));
+
+ box.setText(tr("<center><img src=\":/images/splash.png\"/></img><p>%1</p></center>"
+ "<p>Qt Linguist is a tool for adding translations to Qt "
+ "applications.</p>"
+ "<p>Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)."
+ ).arg(version));
+
+ box.setWindowTitle(QApplication::translate("AboutDialog", "Qt Linguist"));
+ box.setIcon(QMessageBox::NoIcon);
+ box.exec();
+}
+
+void MainWindow::aboutQt()
+{
+ QMessageBox::aboutQt(this, tr("Qt Linguist"));
+}
+
+void MainWindow::setupPhrase()
+{
+ bool enabled = !m_phraseBooks.isEmpty();
+ m_ui.menuClosePhraseBook->setEnabled(enabled);
+ m_ui.menuEditPhraseBook->setEnabled(enabled);
+ m_ui.menuPrintPhraseBook->setEnabled(enabled);
+}
+
+void MainWindow::closeEvent(QCloseEvent *e)
+{
+ if (maybeSaveAll() && closePhraseBooks())
+ e->accept();
+ else
+ e->ignore();
+}
+
+bool MainWindow::maybeSaveAll()
+{
+ if (!m_dataModel->isModified())
+ return true;
+
+ switch (QMessageBox::information(this, tr("Qt Linguist"),
+ tr("Do you want to save the modified files?"),
+ QMessageBox::Yes | QMessageBox::Default,
+ QMessageBox::No,
+ QMessageBox::Cancel | QMessageBox::Escape))
+ {
+ case QMessageBox::Cancel:
+ return false;
+ case QMessageBox::Yes:
+ saveAll();
+ return !m_dataModel->isModified();
+ case QMessageBox::No:
+ break;
+ }
+ return true;
+}
+
+bool MainWindow::maybeSave(int model)
+{
+ if (!m_dataModel->isModified(model))
+ return true;
+
+ switch (QMessageBox::information(this, tr("Qt Linguist"),
+ tr("Do you want to save '%1'?").arg(m_dataModel->srcFileName(model, true)),
+ QMessageBox::Yes | QMessageBox::Default,
+ QMessageBox::No,
+ QMessageBox::Cancel | QMessageBox::Escape))
+ {
+ case QMessageBox::Cancel:
+ return false;
+ case QMessageBox::Yes:
+ saveInternal(model);
+ return !m_dataModel->isModified(model);
+ case QMessageBox::No:
+ break;
+ }
+ return true;
+}
+
+void MainWindow::updateCaption()
+{
+ QString cap;
+ bool enable = false;
+ bool enableRw = false;
+ for (int i = 0; i < m_dataModel->modelCount(); ++i) {
+ enable = true;
+ if (m_dataModel->isModelWritable(i)) {
+ enableRw = true;
+ break;
+ }
+ }
+ m_ui.actionSaveAll->setEnabled(enableRw);
+ m_ui.actionReleaseAll->setEnabled(enableRw);
+ m_ui.actionCloseAll->setEnabled(enable);
+ m_ui.actionPrint->setEnabled(enable);
+ m_ui.actionAccelerators->setEnabled(enable);
+ m_ui.actionEndingPunctuation->setEnabled(enable);
+ m_ui.actionPhraseMatches->setEnabled(enable);
+ m_ui.actionPlaceMarkerMatches->setEnabled(enable);
+ m_ui.actionResetSorting->setEnabled(enable);
+
+ updateActiveModel(m_messageEditor->activeModel());
+ // Ensure that the action labels get updated
+ m_fileActiveModel = m_editActiveModel = -2;
+
+ if (!enable)
+ cap = tr("Qt Linguist[*]");
+ else
+ cap = tr("%1[*] - Qt Linguist").arg(m_dataModel->condensedSrcFileNames(true));
+ setWindowTitle(cap);
+}
+
+void MainWindow::selectedContextChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
+{
+ if (sortedIndex.isValid()) {
+ if (m_settingCurrentMessage)
+ return; // Avoid playing ping-pong with the current message
+
+ QModelIndex sourceIndex = m_sortedContextsModel->mapToSource(sortedIndex);
+ if (m_messageModel->parent(currentMessageIndex()).row() == sourceIndex.row())
+ return;
+
+ QModelIndex contextIndex = setMessageViewRoot(sourceIndex);
+ const QModelIndex &firstChild =
+ m_sortedMessagesModel->index(0, sourceIndex.column(), contextIndex);
+ m_messageView->setCurrentIndex(firstChild);
+ } else if (oldIndex.isValid()) {
+ m_contextView->setCurrentIndex(oldIndex);
+ }
+}
+
+/*
+ * Updates the message displayed in the message editor and related actions.
+ */
+void MainWindow::selectedMessageChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex)
+{
+ // Keep a valid selection whenever possible
+ if (!sortedIndex.isValid() && oldIndex.isValid()) {
+ m_messageView->setCurrentIndex(oldIndex);
+ return;
+ }
+
+ QModelIndex index = m_sortedMessagesModel->mapToSource(sortedIndex);
+ if (index.isValid()) {
+ int model = (index.column() && (index.column() - 1 < m_dataModel->modelCount())) ?
+ index.column() - 1 : m_currentIndex.model();
+ m_currentIndex = m_messageModel->dataIndex(index, model);
+ m_messageEditor->showMessage(m_currentIndex);
+ MessageItem *m = 0;
+ if (model >= 0 && (m = m_dataModel->messageItem(m_currentIndex))) {
+ if (m_dataModel->isModelWritable(model) && !m->isObsolete())
+ m_phraseView->setSourceText(m_currentIndex.model(), m->text());
+ else
+ m_phraseView->setSourceText(-1, QString());
+ } else {
+ if (model < 0) {
+ model = m_dataModel->multiContextItem(m_currentIndex.context())
+ ->firstNonobsoleteMessageIndex(m_currentIndex.message());
+ if (model >= 0)
+ m = m_dataModel->messageItem(m_currentIndex, model);
+ }
+ m_phraseView->setSourceText(-1, QString());
+ }
+ if (m && !m->fileName().isEmpty()) {
+ if (hasFormPreview(m->fileName())) {
+ m_sourceAndFormView->setCurrentWidget(m_formPreviewView);
+ m_formPreviewView->setSourceContext(model, m);
+ } else {
+ m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
+ QDir dir = QFileInfo(m_dataModel->srcFileName(model)).dir();
+ QString fileName = QDir::cleanPath(dir.absoluteFilePath(m->fileName()));
+ m_sourceCodeView->setSourceContext(fileName, m->lineNumber());
+ }
+ m_errorsView->setEnabled(true);
+ } else {
+ m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
+ m_sourceCodeView->setSourceContext(QString(), 0);
+ m_errorsView->setEnabled(false);
+ }
+ updateDanger(m_currentIndex, true);
+ } else {
+ m_currentIndex = MultiDataIndex();
+ m_messageEditor->showNothing();
+ m_phraseView->setSourceText(-1, QString());
+ m_sourceAndFormView->setCurrentWidget(m_sourceCodeView);
+ m_sourceCodeView->setSourceContext(QString(), 0);
+ }
+
+ updatePhraseBookActions();
+ m_ui.actionSelectAll->setEnabled(index.isValid());
+}
+
+void MainWindow::translationChanged(const MultiDataIndex &index)
+{
+ // We get that as a result of batch translation or search & translate,
+ // so the current model is known to match.
+ if (index != m_currentIndex)
+ return;
+
+ m_messageEditor->showMessage(index);
+ updateDanger(index, true);
+
+ MessageItem *m = m_dataModel->messageItem(index);
+ if (hasFormPreview(m->fileName()))
+ m_formPreviewView->setSourceContext(index.model(), m);
+}
+
+// This and the following function operate directly on the messageitem,
+// so the model does not emit modification notifications.
+void MainWindow::updateTranslation(const QStringList &translations)
+{
+ MessageItem *m = m_dataModel->messageItem(m_currentIndex);
+ if (!m)
+ return;
+ if (translations == m->translations())
+ return;
+
+ m->setTranslations(translations);
+ if (!m->fileName().isEmpty() && hasFormPreview(m->fileName()))
+ m_formPreviewView->setSourceContext(m_currentIndex.model(), m);
+ updateDanger(m_currentIndex, true);
+
+ if (m->isFinished())
+ m_dataModel->setFinished(m_currentIndex, false);
+ else
+ m_dataModel->setModified(m_currentIndex.model(), true);
+}
+
+void MainWindow::updateTranslatorComment(const QString &comment)
+{
+ MessageItem *m = m_dataModel->messageItem(m_currentIndex);
+ if (!m)
+ return;
+ if (comment == m->translatorComment())
+ return;
+
+ m->setTranslatorComment(comment);
+
+ m_dataModel->setModified(m_currentIndex.model(), true);
+}
+
+void MainWindow::refreshItemViews()
+{
+ m_messageModel->blockSignals(false);
+ m_contextView->update();
+ m_messageView->update();
+ setWindowModified(m_dataModel->isModified());
+ m_modifiedLabel->setVisible(m_dataModel->isModified());
+ updateStatistics();
+}
+
+void MainWindow::doneAndNext()
+{
+ int model = m_messageEditor->activeModel();
+ if (model >= 0 && m_dataModel->isModelWritable(model))
+ m_dataModel->setFinished(m_currentIndex, true);
+
+ if (!m_messageEditor->focusNextUnfinished())
+ nextUnfinished();
+}
+
+void MainWindow::toggleFinished(const QModelIndex &index)
+{
+ if (!index.isValid() || index.column() - 1 >= m_dataModel->modelCount()
+ || !m_dataModel->isModelWritable(index.column() - 1) || index.parent() == QModelIndex())
+ return;
+
+ QModelIndex item = m_sortedMessagesModel->mapToSource(index);
+ MultiDataIndex dataIndex = m_messageModel->dataIndex(item);
+ MessageItem *m = m_dataModel->messageItem(dataIndex);
+
+ if (!m || m->message().type() == TranslatorMessage::Obsolete)
+ return;
+
+ m_dataModel->setFinished(dataIndex, !m->isFinished());
+}
+
+/*
+ * Receives a context index in the sorted messages model and returns the next
+ * logical context index in the same model, based on the sort order of the
+ * contexts in the sorted contexts model.
+ */
+QModelIndex MainWindow::nextContext(const QModelIndex &index) const
+{
+ QModelIndex sortedContextIndex = m_sortedContextsModel->mapFromSource(
+ m_sortedMessagesModel->mapToSource(index));
+
+ int nextRow = sortedContextIndex.row() + 1;
+ if (nextRow >= m_sortedContextsModel->rowCount())
+ nextRow = 0;
+ sortedContextIndex = m_sortedContextsModel->index(nextRow, index.column());
+
+ return m_sortedMessagesModel->mapFromSource(
+ m_sortedContextsModel->mapToSource(sortedContextIndex));
+}
+
+/*
+ * See nextContext.
+ */
+QModelIndex MainWindow::prevContext(const QModelIndex &index) const
+{
+ QModelIndex sortedContextIndex = m_sortedContextsModel->mapFromSource(
+ m_sortedMessagesModel->mapToSource(index));
+
+ int prevRow = sortedContextIndex.row() - 1;
+ if (prevRow < 0) prevRow = m_sortedContextsModel->rowCount() - 1;
+ sortedContextIndex = m_sortedContextsModel->index(prevRow, index.column());
+
+ return m_sortedMessagesModel->mapFromSource(
+ m_sortedContextsModel->mapToSource(sortedContextIndex));
+}
+
+QModelIndex MainWindow::nextMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
+{
+ QModelIndex idx = currentIndex.isValid() ? currentIndex : m_sortedMessagesModel->index(0, 0);
+ do {
+ int row = 0;
+ QModelIndex par = idx.parent();
+ if (par.isValid()) {
+ row = idx.row() + 1;
+ } else { // In case we are located on a top-level node
+ par = idx;
+ }
+
+ if (row >= m_sortedMessagesModel->rowCount(par)) {
+ par = nextContext(par);
+ row = 0;
+ }
+ idx = m_sortedMessagesModel->index(row, idx.column(), par);
+
+ if (!checkUnfinished)
+ return idx;
+
+ QModelIndex item = m_sortedMessagesModel->mapToSource(idx);
+ MultiDataIndex index = m_messageModel->dataIndex(item, -1);
+ if (m_dataModel->multiMessageItem(index)->isUnfinished())
+ return idx;
+ } while (idx != currentIndex);
+ return QModelIndex();
+}
+
+QModelIndex MainWindow::prevMessage(const QModelIndex &currentIndex, bool checkUnfinished) const
+{
+ QModelIndex idx = currentIndex.isValid() ? currentIndex : m_sortedMessagesModel->index(0, 0);
+ do {
+ int row = idx.row() - 1;
+ QModelIndex par = idx.parent();
+ if (!par.isValid()) { // In case we are located on a top-level node
+ par = idx;
+ row = -1;
+ }
+
+ if (row < 0) {
+ par = prevContext(par);
+ row = m_sortedMessagesModel->rowCount(par) - 1;
+ }
+ idx = m_sortedMessagesModel->index(row, idx.column(), par);
+
+ if (!checkUnfinished)
+ return idx;
+
+ QModelIndex item = m_sortedMessagesModel->mapToSource(idx);
+ MultiDataIndex index = m_messageModel->dataIndex(item, -1);
+ if (m_dataModel->multiMessageItem(index)->isUnfinished())
+ return idx;
+ } while (idx != currentIndex);
+ return QModelIndex();
+}
+
+void MainWindow::nextUnfinished()
+{
+ if (m_ui.actionNextUnfinished->isEnabled()) {
+ if (!next(true)) {
+ // If no Unfinished message is left, the user has finished the job. We
+ // congratulate on a job well done with this ringing bell.
+ statusBar()->showMessage(tr("No untranslated translation units left."), MessageMS);
+ qApp->beep();
+ }
+ }
+}
+
+void MainWindow::prevUnfinished()
+{
+ if (m_ui.actionNextUnfinished->isEnabled()) {
+ if (!prev(true)) {
+ // If no Unfinished message is left, the user has finished the job. We
+ // congratulate on a job well done with this ringing bell.
+ statusBar()->showMessage(tr("No untranslated translation units left."), MessageMS);
+ qApp->beep();
+ }
+ }
+}
+
+void MainWindow::prev()
+{
+ prev(false);
+}
+
+void MainWindow::next()
+{
+ next(false);
+}
+
+bool MainWindow::prev(bool checkUnfinished)
+{
+ QModelIndex index = prevMessage(m_messageView->currentIndex(), checkUnfinished);
+ if (index.isValid())
+ setCurrentMessage(m_sortedMessagesModel->mapToSource(index));
+ if (checkUnfinished)
+ m_messageEditor->setUnfinishedEditorFocus();
+ else
+ m_messageEditor->setEditorFocus();
+ return index.isValid();
+}
+
+bool MainWindow::next(bool checkUnfinished)
+{
+ QModelIndex index = nextMessage(m_messageView->currentIndex(), checkUnfinished);
+ if (index.isValid())
+ setCurrentMessage(m_sortedMessagesModel->mapToSource(index));
+ if (checkUnfinished)
+ m_messageEditor->setUnfinishedEditorFocus();
+ else
+ m_messageEditor->setEditorFocus();
+ return index.isValid();
+}
+
+void MainWindow::findNext(const QString &text, DataModel::FindLocation where, bool matchCase, bool ignoreAccelerators)
+{
+ if (text.isEmpty())
+ return;
+ m_findText = text;
+ m_findWhere = where;
+ m_findMatchCase = matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive;
+ m_findIgnoreAccelerators = ignoreAccelerators;
+ m_ui.actionFindNext->setEnabled(true);
+ findAgain();
+}
+
+void MainWindow::revalidate()
+{
+ for (MultiDataModelIterator it(m_dataModel, -1); it.isValid(); ++it)
+ updateDanger(it, false);
+
+ if (m_currentIndex.isValid())
+ updateDanger(m_currentIndex, true);
+}
+
+QString MainWindow::friendlyString(const QString& str)
+{
+ QString f = str.toLower();
+ f.replace(QRegExp(QString(QLatin1String("[.,:;!?()-]"))), QString(QLatin1String(" ")));
+ f.remove(QLatin1Char('&'));
+ return f.simplified();
+}
+
+static inline void setThemeIcon(QAction *action, const char *name, const char *fallback)
+{
+ const QIcon fallbackIcon(MainWindow::resourcePrefix() + QLatin1String(fallback));
+#ifdef Q_WS_X11
+ action->setIcon(QIcon::fromTheme(QLatin1String(name), fallbackIcon));
+#else
+ Q_UNUSED(name)
+ action->setIcon(fallbackIcon);
+#endif
+}
+
+void MainWindow::setupMenuBar()
+{
+ // There are no fallback icons for these
+#ifdef Q_WS_X11
+ m_ui.menuRecentlyOpenedFiles->setIcon(QIcon::fromTheme(QLatin1String("document-open-recent")));
+ m_ui.actionCloseAll->setIcon(QIcon::fromTheme(QLatin1String("window-close")));
+ m_ui.actionExit->setIcon(QIcon::fromTheme(QLatin1String("application-exit")));
+ m_ui.actionSelectAll->setIcon(QIcon::fromTheme(QLatin1String("edit-select-all")));
+#endif
+
+ // Prefer theme icons when available for these actions
+ setThemeIcon(m_ui.actionOpen, "document-open", "/fileopen.png");
+ setThemeIcon(m_ui.actionOpenAux, "document-open", "/fileopen.png");
+ setThemeIcon(m_ui.actionSave, "document-save", "/filesave.png");
+ setThemeIcon(m_ui.actionSaveAll, "document-save", "/filesave.png");
+ setThemeIcon(m_ui.actionPrint, "document-print", "/print.png");
+ setThemeIcon(m_ui.actionRedo, "edit-redo", "/redo.png");
+ setThemeIcon(m_ui.actionUndo, "edit-undo", "/undo.png");
+ setThemeIcon(m_ui.actionCut, "edit-cut", "/editcut.png");
+ setThemeIcon(m_ui.actionCopy, "edit-copy", "/editcopy.png");
+ setThemeIcon(m_ui.actionPaste, "edit-paste", "/editpaste.png");
+ setThemeIcon(m_ui.actionFind, "edit-find", "/searchfind.png");
+
+ // No well defined theme icons for these actions
+ m_ui.actionAccelerators->setIcon(QIcon(resourcePrefix() + QLatin1String("/accelerator.png")));
+ m_ui.actionOpenPhraseBook->setIcon(QIcon(resourcePrefix() + QLatin1String("/book.png")));
+ m_ui.actionDoneAndNext->setIcon(QIcon(resourcePrefix() + QLatin1String("/doneandnext.png")));
+ m_ui.actionNext->setIcon(QIcon(resourcePrefix() + QLatin1String("/next.png")));
+ m_ui.actionNextUnfinished->setIcon(QIcon(resourcePrefix() + QLatin1String("/nextunfinished.png")));
+ m_ui.actionPhraseMatches->setIcon(QIcon(resourcePrefix() + QLatin1String("/phrase.png")));
+ m_ui.actionEndingPunctuation->setIcon(QIcon(resourcePrefix() + QLatin1String("/punctuation.png")));
+ m_ui.actionPrev->setIcon(QIcon(resourcePrefix() + QLatin1String("/prev.png")));
+ m_ui.actionPrevUnfinished->setIcon(QIcon(resourcePrefix() + QLatin1String("/prevunfinished.png")));
+ m_ui.actionPlaceMarkerMatches->setIcon(QIcon(resourcePrefix() + QLatin1String("/validateplacemarkers.png")));
+ m_ui.actionWhatsThis->setIcon(QIcon(resourcePrefix() + QLatin1String("/whatsthis.png")));
+
+ // File menu
+ connect(m_ui.menuFile, SIGNAL(aboutToShow()), SLOT(fileAboutToShow()));
+ connect(m_ui.actionOpen, SIGNAL(triggered()), this, SLOT(open()));
+ connect(m_ui.actionOpenAux, SIGNAL(triggered()), this, SLOT(openAux()));
+ connect(m_ui.actionSaveAll, SIGNAL(triggered()), this, SLOT(saveAll()));
+ connect(m_ui.actionSave, SIGNAL(triggered()), this, SLOT(save()));
+ connect(m_ui.actionSaveAs, SIGNAL(triggered()), this, SLOT(saveAs()));
+ connect(m_ui.actionReleaseAll, SIGNAL(triggered()), this, SLOT(releaseAll()));
+ connect(m_ui.actionRelease, SIGNAL(triggered()), this, SLOT(release()));
+ connect(m_ui.actionReleaseAs, SIGNAL(triggered()), this, SLOT(releaseAs()));
+ connect(m_ui.actionPrint, SIGNAL(triggered()), this, SLOT(print()));
+ connect(m_ui.actionClose, SIGNAL(triggered()), this, SLOT(closeFile()));
+ connect(m_ui.actionCloseAll, SIGNAL(triggered()), this, SLOT(closeAll()));
+ connect(m_ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));
+
+ // Edit menu
+ connect(m_ui.menuEdit, SIGNAL(aboutToShow()), SLOT(editAboutToShow()));
+
+ connect(m_ui.actionUndo, SIGNAL(triggered()), m_messageEditor, SLOT(undo()));
+ connect(m_messageEditor, SIGNAL(undoAvailable(bool)), m_ui.actionUndo, SLOT(setEnabled(bool)));
+
+ connect(m_ui.actionRedo, SIGNAL(triggered()), m_messageEditor, SLOT(redo()));
+ connect(m_messageEditor, SIGNAL(redoAvailable(bool)), m_ui.actionRedo, SLOT(setEnabled(bool)));
+
+ connect(m_ui.actionCopy, SIGNAL(triggered()), m_messageEditor, SLOT(copy()));
+ connect(m_messageEditor, SIGNAL(copyAvailable(bool)), m_ui.actionCopy, SLOT(setEnabled(bool)));
+
+ connect(m_messageEditor, SIGNAL(cutAvailable(bool)), m_ui.actionCut, SLOT(setEnabled(bool)));
+ connect(m_ui.actionCut, SIGNAL(triggered()), m_messageEditor, SLOT(cut()));
+
+ connect(m_messageEditor, SIGNAL(pasteAvailable(bool)), m_ui.actionPaste, SLOT(setEnabled(bool)));
+ connect(m_ui.actionPaste, SIGNAL(triggered()), m_messageEditor, SLOT(paste()));
+
+ connect(m_ui.actionSelectAll, SIGNAL(triggered()), m_messageEditor, SLOT(selectAll()));
+ connect(m_ui.actionFind, SIGNAL(triggered()), m_findDialog, SLOT(find()));
+ connect(m_ui.actionFindNext, SIGNAL(triggered()), this, SLOT(findAgain()));
+ connect(m_ui.actionSearchAndTranslate, SIGNAL(triggered()), this, SLOT(showTranslateDialog()));
+ connect(m_ui.actionBatchTranslation, SIGNAL(triggered()), this, SLOT(showBatchTranslateDialog()));
+ connect(m_ui.actionTranslationFileSettings, SIGNAL(triggered()), this, SLOT(showTranslationSettings()));
+
+ connect(m_batchTranslateDialog, SIGNAL(finished()), SLOT(refreshItemViews()));
+
+ // Translation menu
+ // when updating the accelerators, remember the status bar
+ connect(m_ui.actionPrevUnfinished, SIGNAL(triggered()), this, SLOT(prevUnfinished()));
+ connect(m_ui.actionNextUnfinished, SIGNAL(triggered()), this, SLOT(nextUnfinished()));
+ connect(m_ui.actionNext, SIGNAL(triggered()), this, SLOT(next()));
+ connect(m_ui.actionPrev, SIGNAL(triggered()), this, SLOT(prev()));
+ connect(m_ui.actionDoneAndNext, SIGNAL(triggered()), this, SLOT(doneAndNext()));
+ connect(m_ui.actionBeginFromSource, SIGNAL(triggered()), m_messageEditor, SLOT(beginFromSource()));
+ connect(m_messageEditor, SIGNAL(beginFromSourceAvailable(bool)), m_ui.actionBeginFromSource, SLOT(setEnabled(bool)));
+
+ // Phrasebook menu
+ connect(m_ui.actionNewPhraseBook, SIGNAL(triggered()), this, SLOT(newPhraseBook()));
+ connect(m_ui.actionOpenPhraseBook, SIGNAL(triggered()), this, SLOT(openPhraseBook()));
+ connect(m_ui.menuClosePhraseBook, SIGNAL(triggered(QAction*)),
+ this, SLOT(closePhraseBook(QAction*)));
+ connect(m_ui.menuEditPhraseBook, SIGNAL(triggered(QAction*)),
+ this, SLOT(editPhraseBook(QAction*)));
+ connect(m_ui.menuPrintPhraseBook, SIGNAL(triggered(QAction*)),
+ this, SLOT(printPhraseBook(QAction*)));
+ connect(m_ui.actionAddToPhraseBook, SIGNAL(triggered()), this, SLOT(addToPhraseBook()));
+
+ // Validation menu
+ connect(m_ui.actionAccelerators, SIGNAL(triggered()), this, SLOT(revalidate()));
+ connect(m_ui.actionEndingPunctuation, SIGNAL(triggered()), this, SLOT(revalidate()));
+ connect(m_ui.actionPhraseMatches, SIGNAL(triggered()), this, SLOT(revalidate()));
+ connect(m_ui.actionPlaceMarkerMatches, SIGNAL(triggered()), this, SLOT(revalidate()));
+
+ // View menu
+ connect(m_ui.actionResetSorting, SIGNAL(triggered()), this, SLOT(resetSorting()));
+ connect(m_ui.actionDisplayGuesses, SIGNAL(triggered()), m_phraseView, SLOT(toggleGuessing()));
+ connect(m_ui.actionStatistics, SIGNAL(triggered()), this, SLOT(toggleStatistics()));
+ connect(m_ui.menuView, SIGNAL(aboutToShow()), this, SLOT(updateViewMenu()));
+ m_ui.menuViewViews->addAction(m_contextDock->toggleViewAction());
+ m_ui.menuViewViews->addAction(m_messagesDock->toggleViewAction());
+ m_ui.menuViewViews->addAction(m_phrasesDock->toggleViewAction());
+ m_ui.menuViewViews->addAction(m_sourceAndFormDock->toggleViewAction());
+ m_ui.menuViewViews->addAction(m_errorsDock->toggleViewAction());
+
+#if defined(Q_WS_MAC)
+ // Window menu
+ QMenu *windowMenu = new QMenu(tr("&Window"), this);
+ menuBar()->insertMenu(m_ui.menuHelp->menuAction(), windowMenu);
+ windowMenu->addAction(tr("Minimize"), this,
+ SLOT(showMinimized()), QKeySequence(tr("Ctrl+M")));
+#endif
+
+ // Help
+ connect(m_ui.actionManual, SIGNAL(triggered()), this, SLOT(manual()));
+ connect(m_ui.actionAbout, SIGNAL(triggered()), this, SLOT(about()));
+ connect(m_ui.actionAboutQt, SIGNAL(triggered()), this, SLOT(aboutQt()));
+ connect(m_ui.actionWhatsThis, SIGNAL(triggered()), this, SLOT(onWhatsThis()));
+
+ connect(m_ui.menuRecentlyOpenedFiles, SIGNAL(triggered(QAction*)), this,
+ SLOT(recentFileActivated(QAction*)));
+
+ m_ui.actionManual->setWhatsThis(tr("Display the manual for %1.").arg(tr("Qt Linguist")));
+ m_ui.actionAbout->setWhatsThis(tr("Display information about %1.").arg(tr("Qt Linguist")));
+ m_ui.actionDoneAndNext->setShortcuts(QList<QKeySequence>()
+ << QKeySequence(QLatin1String("Ctrl+Return"))
+ << QKeySequence(QLatin1String("Ctrl+Enter")));
+
+ // Disable the Close/Edit/Print phrasebook menuitems if they are not loaded
+ connect(m_ui.menuPhrases, SIGNAL(aboutToShow()), this, SLOT(setupPhrase()));
+
+ connect(m_ui.menuRecentlyOpenedFiles, SIGNAL(aboutToShow()), SLOT(setupRecentFilesMenu()));
+}
+
+void MainWindow::updateActiveModel(int model)
+{
+ if (model >= 0)
+ updateLatestModel(model);
+}
+
+// Arriving here implies that the messageEditor does not have focus
+void MainWindow::updateLatestModel(const QModelIndex &index)
+{
+ if (index.column() && (index.column() - 1 < m_dataModel->modelCount()))
+ updateLatestModel(index.column() - 1);
+}
+
+void MainWindow::updateLatestModel(int model)
+{
+ m_currentIndex = MultiDataIndex(model, m_currentIndex.context(), m_currentIndex.message());
+ bool enable = false;
+ bool enableRw = false;
+ if (model >= 0) {
+ enable = true;
+ if (m_dataModel->isModelWritable(model))
+ enableRw = true;
+
+ if (m_currentIndex.isValid()) {
+ if (MessageItem *item = m_dataModel->messageItem(m_currentIndex)) {
+ if (!item->fileName().isEmpty() && hasFormPreview(item->fileName()))
+ m_formPreviewView->setSourceContext(model, item);
+ if (enableRw && !item->isObsolete())
+ m_phraseView->setSourceText(model, item->text());
+ else
+ m_phraseView->setSourceText(-1, QString());
+ } else {
+ m_phraseView->setSourceText(-1, QString());
+ }
+ }
+ }
+ m_ui.actionSave->setEnabled(enableRw);
+ m_ui.actionSaveAs->setEnabled(enableRw);
+ m_ui.actionRelease->setEnabled(enableRw);
+ m_ui.actionReleaseAs->setEnabled(enableRw);
+ m_ui.actionClose->setEnabled(enable);
+ m_ui.actionTranslationFileSettings->setEnabled(enableRw);
+ m_ui.actionSearchAndTranslate->setEnabled(enableRw);
+ // cut & paste - edit only
+ updatePhraseBookActions();
+ updateStatistics();
+}
+
+// Note for *AboutToShow: Due to the delayed nature, only actions without shortcuts
+// and representations outside the menu may be setEnabled()/setVisible() here.
+
+void MainWindow::fileAboutToShow()
+{
+ if (m_fileActiveModel != m_currentIndex.model()) {
+ // We rename the actions so the shortcuts need not be reassigned.
+ bool en;
+ if (m_dataModel->modelCount() > 1) {
+ if (m_currentIndex.model() >= 0) {
+ QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
+ m_ui.actionSave->setText(tr("&Save '%1'").arg(fn));
+ m_ui.actionSaveAs->setText(tr("Save '%1' &As...").arg(fn));
+ m_ui.actionRelease->setText(tr("Release '%1'").arg(fn));
+ m_ui.actionReleaseAs->setText(tr("Release '%1' As...").arg(fn));
+ m_ui.actionClose->setText(tr("&Close '%1'").arg(fn));
+ } else {
+ m_ui.actionSave->setText(tr("&Save"));
+ m_ui.actionSaveAs->setText(tr("Save &As..."));
+ m_ui.actionRelease->setText(tr("Release"));
+ m_ui.actionReleaseAs->setText(tr("Release As..."));
+ m_ui.actionClose->setText(tr("&Close"));
+ }
+
+ m_ui.actionSaveAll->setText(tr("Save All"));
+ m_ui.actionReleaseAll->setText(tr("&Release All"));
+ m_ui.actionCloseAll->setText(tr("Close All"));
+ en = true;
+ } else {
+ m_ui.actionSaveAs->setText(tr("Save &As..."));
+ m_ui.actionReleaseAs->setText(tr("Release As..."));
+
+ m_ui.actionSaveAll->setText(tr("&Save"));
+ m_ui.actionReleaseAll->setText(tr("&Release"));
+ m_ui.actionCloseAll->setText(tr("&Close"));
+ en = false;
+ }
+ m_ui.actionSave->setVisible(en);
+ m_ui.actionRelease->setVisible(en);
+ m_ui.actionClose->setVisible(en);
+ m_fileActiveModel = m_currentIndex.model();
+ }
+}
+
+void MainWindow::editAboutToShow()
+{
+ if (m_editActiveModel != m_currentIndex.model()) {
+ if (m_currentIndex.model() >= 0 && m_dataModel->modelCount() > 1) {
+ QString fn = QFileInfo(m_dataModel->srcFileName(m_currentIndex.model())).baseName();
+ m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings for '%1'...").arg(fn));
+ m_ui.actionBatchTranslation->setText(tr("&Batch Translation of '%1'...").arg(fn));
+ m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate in '%1'...").arg(fn));
+ } else {
+ m_ui.actionTranslationFileSettings->setText(tr("Translation File &Settings..."));
+ m_ui.actionBatchTranslation->setText(tr("&Batch Translation..."));
+ m_ui.actionSearchAndTranslate->setText(tr("Search And &Translate..."));
+ }
+ m_editActiveModel = m_currentIndex.model();
+ }
+}
+
+void MainWindow::updateViewMenu()
+{
+ bool check = m_statistics ? m_statistics->isVisible() : false;
+ m_ui.actionStatistics->setChecked(check);
+}
+
+void MainWindow::showContextDock()
+{
+ m_contextDock->show();
+ m_contextDock->raise();
+}
+
+void MainWindow::showMessagesDock()
+{
+ m_messagesDock->show();
+ m_messagesDock->raise();
+}
+
+void MainWindow::showPhrasesDock()
+{
+ m_phrasesDock->show();
+ m_phrasesDock->raise();
+}
+
+void MainWindow::showSourceCodeDock()
+{
+ m_sourceAndFormDock->show();
+ m_sourceAndFormDock->raise();
+}
+
+void MainWindow::showErrorDock()
+{
+ m_errorsDock->show();
+ m_errorsDock->raise();
+}
+
+void MainWindow::onWhatsThis()
+{
+ QWhatsThis::enterWhatsThisMode();
+}
+
+void MainWindow::setupToolBars()
+{
+ QToolBar *filet = new QToolBar(this);
+ filet->setObjectName(QLatin1String("FileToolbar"));
+ filet->setWindowTitle(tr("File"));
+ this->addToolBar(filet);
+ m_ui.menuToolbars->addAction(filet->toggleViewAction());
+
+ QToolBar *editt = new QToolBar(this);
+ editt->setVisible(false);
+ editt->setObjectName(QLatin1String("EditToolbar"));
+ editt->setWindowTitle(tr("Edit"));
+ this->addToolBar(editt);
+ m_ui.menuToolbars->addAction(editt->toggleViewAction());
+
+ QToolBar *translationst = new QToolBar(this);
+ translationst->setObjectName(QLatin1String("TranslationToolbar"));
+ translationst->setWindowTitle(tr("Translation"));
+ this->addToolBar(translationst);
+ m_ui.menuToolbars->addAction(translationst->toggleViewAction());
+
+ QToolBar *validationt = new QToolBar(this);
+ validationt->setObjectName(QLatin1String("ValidationToolbar"));
+ validationt->setWindowTitle(tr("Validation"));
+ this->addToolBar(validationt);
+ m_ui.menuToolbars->addAction(validationt->toggleViewAction());
+
+ QToolBar *helpt = new QToolBar(this);
+ helpt->setVisible(false);
+ helpt->setObjectName(QLatin1String("HelpToolbar"));
+ helpt->setWindowTitle(tr("Help"));
+ this->addToolBar(helpt);
+ m_ui.menuToolbars->addAction(helpt->toggleViewAction());
+
+
+ filet->addAction(m_ui.actionOpen);
+ filet->addAction(m_ui.actionSaveAll);
+ filet->addAction(m_ui.actionPrint);
+ filet->addSeparator();
+ filet->addAction(m_ui.actionOpenPhraseBook);
+
+ editt->addAction(m_ui.actionUndo);
+ editt->addAction(m_ui.actionRedo);
+ editt->addSeparator();
+ editt->addAction(m_ui.actionCut);
+ editt->addAction(m_ui.actionCopy);
+ editt->addAction(m_ui.actionPaste);
+ editt->addSeparator();
+ editt->addAction(m_ui.actionFind);
+
+ translationst->addAction(m_ui.actionPrev);
+ translationst->addAction(m_ui.actionNext);
+ translationst->addAction(m_ui.actionPrevUnfinished);
+ translationst->addAction(m_ui.actionNextUnfinished);
+ translationst->addAction(m_ui.actionDoneAndNext);
+
+ validationt->addAction(m_ui.actionAccelerators);
+ validationt->addAction(m_ui.actionEndingPunctuation);
+ validationt->addAction(m_ui.actionPhraseMatches);
+ validationt->addAction(m_ui.actionPlaceMarkerMatches);
+
+ helpt->addAction(m_ui.actionWhatsThis);
+}
+
+QModelIndex MainWindow::setMessageViewRoot(const QModelIndex &index)
+{
+ const QModelIndex &sortedContextIndex = m_sortedMessagesModel->mapFromSource(index);
+ const QModelIndex &trueContextIndex = m_sortedMessagesModel->index(sortedContextIndex.row(), 0);
+ if (m_messageView->rootIndex() != trueContextIndex)
+ m_messageView->setRootIndex(trueContextIndex);
+ return trueContextIndex;
+}
+
+/*
+ * Updates the selected entries in the context and message views.
+ */
+void MainWindow::setCurrentMessage(const QModelIndex &index)
+{
+ const QModelIndex &contextIndex = m_messageModel->parent(index);
+ if (!contextIndex.isValid())
+ return;
+
+ const QModelIndex &trueIndex = m_messageModel->index(contextIndex.row(), index.column(), QModelIndex());
+ m_settingCurrentMessage = true;
+ m_contextView->setCurrentIndex(m_sortedContextsModel->mapFromSource(trueIndex));
+ m_settingCurrentMessage = false;
+
+ setMessageViewRoot(contextIndex);
+ m_messageView->setCurrentIndex(m_sortedMessagesModel->mapFromSource(index));
+}
+
+void MainWindow::setCurrentMessage(const QModelIndex &index, int model)
+{
+ const QModelIndex &theIndex = m_messageModel->index(index.row(), model + 1, index.parent());
+ setCurrentMessage(theIndex);
+ m_messageEditor->setEditorFocus(model);
+}
+
+QModelIndex MainWindow::currentContextIndex() const
+{
+ return m_sortedContextsModel->mapToSource(m_contextView->currentIndex());
+}
+
+QModelIndex MainWindow::currentMessageIndex() const
+{
+ return m_sortedMessagesModel->mapToSource(m_messageView->currentIndex());
+}
+
+PhraseBook *MainWindow::openPhraseBook(const QString& name)
+{
+ PhraseBook *pb = new PhraseBook();
+ bool langGuessed;
+ if (!pb->load(name, &langGuessed)) {
+ QMessageBox::warning(this, tr("Qt Linguist"),
+ tr("Cannot read from phrase book '%1'.").arg(name));
+ delete pb;
+ return 0;
+ }
+ if (langGuessed) {
+ if (!m_translationSettingsDialog)
+ m_translationSettingsDialog = new TranslationSettingsDialog(this);
+ m_translationSettingsDialog->setPhraseBook(pb);
+ m_translationSettingsDialog->exec();
+ }
+
+ m_phraseBooks.append(pb);
+
+ QAction *a = m_ui.menuClosePhraseBook->addAction(pb->friendlyPhraseBookName());
+ m_phraseBookMenu[PhraseCloseMenu].insert(a, pb);
+ a->setWhatsThis(tr("Close this phrase book."));
+
+ a = m_ui.menuEditPhraseBook->addAction(pb->friendlyPhraseBookName());
+ m_phraseBookMenu[PhraseEditMenu].insert(a, pb);
+ a->setWhatsThis(tr("Enables you to add, modify, or delete"
+ " entries in this phrase book."));
+
+ a = m_ui.menuPrintPhraseBook->addAction(pb->friendlyPhraseBookName());
+ m_phraseBookMenu[PhrasePrintMenu].insert(a, pb);
+ a->setWhatsThis(tr("Print the entries in this phrase book."));
+
+ connect(pb, SIGNAL(listChanged()), this, SLOT(updatePhraseDicts()));
+ updatePhraseDicts();
+ updatePhraseBookActions();
+
+ return pb;
+}
+
+bool MainWindow::savePhraseBook(QString *name, PhraseBook &pb)
+{
+ if (!name->contains(QLatin1Char('.')))
+ *name += QLatin1String(".qph");
+
+ if (!pb.save(*name)) {
+ QMessageBox::warning(this, tr("Qt Linguist"),
+ tr("Cannot create phrase book '%1'.").arg(*name));
+ return false;
+ }
+ return true;
+}
+
+bool MainWindow::maybeSavePhraseBook(PhraseBook *pb)
+{
+ if (pb->isModified())
+ switch (QMessageBox::information(this, tr("Qt Linguist"),
+ tr("Do you want to save phrase book '%1'?").arg(pb->friendlyPhraseBookName()),
+ QMessageBox::Yes | QMessageBox::Default,
+ QMessageBox::No,
+ QMessageBox::Cancel | QMessageBox::Escape))
+ {
+ case QMessageBox::Cancel:
+ return false;
+ case QMessageBox::Yes:
+ if (!pb->save(pb->fileName()))
+ return false;
+ break;
+ case QMessageBox::No:
+ break;
+ }
+ return true;
+}
+
+bool MainWindow::closePhraseBooks()
+{
+ foreach(PhraseBook *phraseBook, m_phraseBooks)
+ if (!maybeSavePhraseBook(phraseBook))
+ return false;
+ return true;
+}
+
+void MainWindow::updateProgress()
+{
+ int numEditable = m_dataModel->getNumEditable();
+ int numFinished = m_dataModel->getNumFinished();
+ if (!m_dataModel->modelCount())
+ m_progressLabel->setText(QString(QLatin1String(" ")));
+ else
+ m_progressLabel->setText(QString(QLatin1String(" %1/%2 "))
+ .arg(numFinished).arg(numEditable));
+ bool enable = numFinished != numEditable;
+ m_ui.actionPrevUnfinished->setEnabled(enable);
+ m_ui.actionNextUnfinished->setEnabled(enable);
+ m_ui.actionDoneAndNext->setEnabled(enable);
+
+ m_ui.actionPrev->setEnabled(m_dataModel->contextCount() > 0);
+ m_ui.actionNext->setEnabled(m_dataModel->contextCount() > 0);
+}
+
+void MainWindow::updatePhraseBookActions()
+{
+ bool phraseBookLoaded = (m_currentIndex.model() >= 0) && !m_phraseBooks.isEmpty();
+ m_ui.actionBatchTranslation->setEnabled(m_dataModel->contextCount() > 0 && phraseBookLoaded
+ && m_dataModel->isModelWritable(m_currentIndex.model()));
+ m_ui.actionAddToPhraseBook->setEnabled(currentMessageIndex().isValid() && phraseBookLoaded);
+}
+
+void MainWindow::updatePhraseDictInternal(int model)
+{
+ QHash<QString, QList<Phrase *> > &pd = m_phraseDict[model];
+
+ pd.clear();
+ foreach (PhraseBook *pb, m_phraseBooks) {
+ bool before;
+ if (pb->language() != QLocale::C && m_dataModel->language(model) != QLocale::C) {
+ if (pb->language() != m_dataModel->language(model))
+ continue;
+ before = (pb->country() == m_dataModel->model(model)->country());
+ } else {
+ before = false;
+ }
+ foreach (Phrase *p, pb->phrases()) {
+ QString f = friendlyString(p->source());
+ if (f.length() > 0) {
+ f = f.split(QLatin1Char(' ')).first();
+ if (!pd.contains(f)) {
+ pd.insert(f, QList<Phrase *>());
+ }
+ if (before)
+ pd[f].prepend(p);
+ else
+ pd[f].append(p);
+ }
+ }
+ }
+}
+
+void MainWindow::updatePhraseDict(int model)
+{
+ updatePhraseDictInternal(model);
+ m_phraseView->update();
+}
+
+void MainWindow::updatePhraseDicts()
+{
+ for (int i = 0; i < m_phraseDict.size(); ++i)
+ if (!m_dataModel->isModelWritable(i))
+ m_phraseDict[i].clear();
+ else
+ updatePhraseDictInternal(i);
+ revalidate();
+ m_phraseView->update();
+}
+
+static bool haveMnemonic(const QString &str)
+{
+ for (const ushort *p = (ushort *)str.constData();; ) { // Assume null-termination
+ ushort c = *p++;
+ if (!c)
+ break;
+ if (c == '&') {
+ c = *p++;
+ if (!c)
+ return false;
+ // "Nobody" ever really uses these alt-space, and they are highly annoying
+ // because we get a lot of false positives.
+ if (c != '&' && c != ' ' && QChar(c).isPrint()) {
+ const ushort *pp = p;
+ for (; *p < 256 && isalpha(*p); p++) ;
+ if (pp == p || *p != ';')
+ return true;
+ // This looks like a HTML &entity;, so ignore it. As a HTML string
+ // won't contain accels anyway, we can stop scanning here.
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+void MainWindow::updateDanger(const MultiDataIndex &index, bool verbose)
+{
+ MultiDataIndex curIdx = index;
+ m_errorsView->clear();
+
+ QString source;
+ for (int mi = 0; mi < m_dataModel->modelCount(); ++mi) {
+ if (!m_dataModel->isModelWritable(mi))
+ continue;
+ curIdx.setModel(mi);
+ MessageItem *m = m_dataModel->messageItem(curIdx);
+ if (!m || m->isObsolete())
+ continue;
+
+ bool danger = false;
+ if (m->message().isTranslated()) {
+ if (source.isEmpty()) {
+ source = m->pluralText();
+ if (source.isEmpty())
+ source = m->text();
+ }
+ QStringList translations = m->translations();
+
+ // Truncated variants are permitted to be "denormalized"
+ for (int i = 0; i < translations.count(); ++i) {
+ int sep = translations.at(i).indexOf(QChar(Translator::BinaryVariantSeparator));
+ if (sep >= 0)
+ translations[i].truncate(sep);
+ }
+
+ if (m_ui.actionAccelerators->isChecked()) {
+ bool sk = haveMnemonic(source);
+ bool tk = true;
+ for (int i = 0; i < translations.count() && tk; ++i) {
+ tk &= haveMnemonic(translations[i]);
+ }
+
+ if (!sk && tk) {
+ if (verbose)
+ m_errorsView->addError(mi, ErrorsView::SuperfluousAccelerator);
+ danger = true;
+ } else if (sk && !tk) {
+ if (verbose)
+ m_errorsView->addError(mi, ErrorsView::MissingAccelerator);
+ danger = true;
+ }
+ }
+ if (m_ui.actionEndingPunctuation->isChecked()) {
+ bool endingok = true;
+ for (int i = 0; i < translations.count() && endingok; ++i) {
+ endingok &= (ending(source, m_dataModel->sourceLanguage(mi)) ==
+ ending(translations[i], m_dataModel->language(mi)));
+ }
+
+ if (!endingok) {
+ if (verbose)
+ m_errorsView->addError(mi, ErrorsView::PunctuationDiffer);
+ danger = true;
+ }
+ }
+ if (m_ui.actionPhraseMatches->isChecked()) {
+ QString fsource = friendlyString(source);
+ QString ftranslation = friendlyString(translations.first());
+ QStringList lookupWords = fsource.split(QLatin1Char(' '));
+
+ bool phraseFound;
+ foreach (const QString &s, lookupWords) {
+ if (m_phraseDict[mi].contains(s)) {
+ phraseFound = true;
+ foreach (const Phrase *p, m_phraseDict[mi].value(s)) {
+ if (fsource == friendlyString(p->source())) {
+ if (ftranslation.indexOf(friendlyString(p->target())) >= 0) {
+ phraseFound = true;
+ break;
+ } else {
+ phraseFound = false;
+ }
+ }
+ }
+ if (!phraseFound) {
+ if (verbose)
+ m_errorsView->addError(mi, ErrorsView::IgnoredPhrasebook, s);
+ danger = true;
+ }
+ }
+ }
+ }
+
+ if (m_ui.actionPlaceMarkerMatches->isChecked()) {
+ // Stores the occurrence count of the place markers in the map placeMarkerIndexes.
+ // i.e. the occurrence count of %1 is stored at placeMarkerIndexes[1],
+ // count of %2 is stored at placeMarkerIndexes[2] etc.
+ // In the first pass, it counts all place markers in the sourcetext.
+ // In the second pass it (de)counts all place markers in the translation.
+ // When finished, all elements should have returned to a count of 0,
+ // if not there is a mismatch
+ // between place markers in the source text and the translation text.
+ QHash<int, int> placeMarkerIndexes;
+ QString translation;
+ int numTranslations = translations.count();
+ for (int pass = 0; pass < numTranslations + 1; ++pass) {
+ const QChar *uc_begin = source.unicode();
+ const QChar *uc_end = uc_begin + source.length();
+ if (pass >= 1) {
+ translation = translations[pass - 1];
+ uc_begin = translation.unicode();
+ uc_end = uc_begin + translation.length();
+ }
+ const QChar *c = uc_begin;
+ while (c < uc_end) {
+ if (c->unicode() == '%') {
+ const QChar *escape_start = ++c;
+ while (c->isDigit())
+ ++c;
+ const QChar *escape_end = c;
+ bool ok = true;
+ int markerIndex = QString::fromRawData(
+ escape_start, escape_end - escape_start).toInt(&ok);
+ if (ok)
+ placeMarkerIndexes[markerIndex] += (pass == 0 ? numTranslations : -1);
+ }
+ ++c;
+ }
+ }
+
+ foreach (int i, placeMarkerIndexes) {
+ if (i != 0) {
+ if (verbose)
+ m_errorsView->addError(mi, ErrorsView::PlaceMarkersDiffer);
+ danger = true;
+ break;
+ }
+ }
+
+ // Piggy-backed on the general place markers, we check the plural count marker.
+ if (m->message().isPlural()) {
+ for (int i = 0; i < numTranslations; ++i)
+ if (m_dataModel->model(mi)->countRefNeeds().at(i)
+ && !translations[i].contains(QLatin1String("%n"))) {
+ if (verbose)
+ m_errorsView->addError(mi, ErrorsView::NumerusMarkerMissing);
+ danger = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (danger != m->danger())
+ m_dataModel->setDanger(curIdx, danger);
+ }
+
+ if (verbose)
+ statusBar()->showMessage(m_errorsView->firstError());
+}
+
+void MainWindow::readConfig()
+{
+ QSettings config;
+
+ QRect r(pos(), size());
+ restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray());
+ restoreState(config.value(settingPath("MainWindowState")).toByteArray());
+
+ m_ui.actionAccelerators->setChecked(
+ config.value(settingPath("Validators/Accelerator"), true).toBool());
+ m_ui.actionEndingPunctuation->setChecked(
+ config.value(settingPath("Validators/EndingPunctuation"), true).toBool());
+ m_ui.actionPhraseMatches->setChecked(
+ config.value(settingPath("Validators/PhraseMatch"), true).toBool());
+ m_ui.actionPlaceMarkerMatches->setChecked(
+ config.value(settingPath("Validators/PlaceMarkers"), true).toBool());
+ m_ui.actionLengthVariants->setChecked(
+ config.value(settingPath("Options/LengthVariants"), false).toBool());
+
+ recentFiles().readConfig();
+
+ int size = config.beginReadArray(settingPath("OpenedPhraseBooks"));
+ for (int i = 0; i < size; ++i) {
+ config.setArrayIndex(i);
+ openPhraseBook(config.value(QLatin1String("FileName")).toString());
+ }
+ config.endArray();
+}
+
+void MainWindow::writeConfig()
+{
+ QSettings config;
+ config.setValue(settingPath("Geometry/WindowGeometry"),
+ saveGeometry());
+ config.setValue(settingPath("Validators/Accelerator"),
+ m_ui.actionAccelerators->isChecked());
+ config.setValue(settingPath("Validators/EndingPunctuation"),
+ m_ui.actionEndingPunctuation->isChecked());
+ config.setValue(settingPath("Validators/PhraseMatch"),
+ m_ui.actionPhraseMatches->isChecked());
+ config.setValue(settingPath("Validators/PlaceMarkers"),
+ m_ui.actionPlaceMarkerMatches->isChecked());
+ config.setValue(settingPath("Options/LengthVariants"),
+ m_ui.actionLengthVariants->isChecked());
+ config.setValue(settingPath("MainWindowState"),
+ saveState());
+ recentFiles().writeConfig();
+
+ config.beginWriteArray(settingPath("OpenedPhraseBooks"),
+ m_phraseBooks.size());
+ for (int i = 0; i < m_phraseBooks.size(); ++i) {
+ config.setArrayIndex(i);
+ config.setValue(QLatin1String("FileName"), m_phraseBooks.at(i)->fileName());
+ }
+ config.endArray();
+}
+
+void MainWindow::setupRecentFilesMenu()
+{
+ m_ui.menuRecentlyOpenedFiles->clear();
+ foreach (const QStringList &strList, recentFiles().filesLists())
+ if (strList.size() == 1) {
+ const QString &str = strList.first();
+ m_ui.menuRecentlyOpenedFiles->addAction(
+ DataModel::prettifyFileName(str))->setData(str);
+ } else {
+ QMenu *menu = m_ui.menuRecentlyOpenedFiles->addMenu(
+ MultiDataModel::condenseFileNames(
+ MultiDataModel::prettifyFileNames(strList)));
+ menu->addAction(tr("All"))->setData(strList);
+ foreach (const QString &str, strList)
+ menu->addAction(DataModel::prettifyFileName(str))->setData(str);
+ }
+}
+
+void MainWindow::recentFileActivated(QAction *action)
+{
+ openFiles(action->data().toStringList());
+}
+
+void MainWindow::toggleStatistics()
+{
+ if (m_ui.actionStatistics->isChecked()) {
+ if (!m_statistics) {
+ m_statistics = new Statistics(this);
+ connect(m_dataModel, SIGNAL(statsChanged(int,int,int,int,int,int)),
+ m_statistics, SLOT(updateStats(int,int,int,int,int,int)));
+ }
+ m_statistics->show();
+ updateStatistics();
+ }
+ else if (m_statistics) {
+ m_statistics->close();
+ }
+}
+
+void MainWindow::maybeUpdateStatistics(const MultiDataIndex &index)
+{
+ if (index.model() == m_currentIndex.model())
+ updateStatistics();
+}
+
+void MainWindow::updateStatistics()
+{
+ // don't call this if stats dialog is not open
+ // because this can be slow...
+ if (!m_statistics || !m_statistics->isVisible() || m_currentIndex.model() < 0)
+ return;
+
+ m_dataModel->model(m_currentIndex.model())->updateStatistics();
+}
+
+void MainWindow::showTranslationSettings(int model)
+{
+ if (!m_translationSettingsDialog)
+ m_translationSettingsDialog = new TranslationSettingsDialog(this);
+ m_translationSettingsDialog->setDataModel(m_dataModel->model(model));
+ m_translationSettingsDialog->exec();
+}
+
+void MainWindow::showTranslationSettings()
+{
+ showTranslationSettings(m_currentIndex.model());
+}
+
+bool MainWindow::eventFilter(QObject *object, QEvent *event)
+{
+ if (event->type() == QEvent::DragEnter) {
+ QDragEnterEvent *e = static_cast<QDragEnterEvent*>(event);
+ if (e->mimeData()->hasFormat(QLatin1String("text/uri-list"))) {
+ e->acceptProposedAction();
+ return true;
+ }
+ } else if (event->type() == QEvent::Drop) {
+ QDropEvent *e = static_cast<QDropEvent*>(event);
+ if (!e->mimeData()->hasFormat(QLatin1String("text/uri-list")))
+ return false;
+ QStringList urls;
+ foreach (QUrl url, e->mimeData()->urls())
+ if (!url.toLocalFile().isEmpty())
+ urls << url.toLocalFile();
+ if (!urls.isEmpty())
+ openFiles(urls);
+ e->acceptProposedAction();
+ return true;
+ } else if (event->type() == QEvent::KeyPress) {
+ if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
+ if (object == m_messageEditor)
+ m_messageView->setFocus();
+ else if (object == m_messagesDock)
+ m_contextView->setFocus();
+ }
+ }
+ return false;
+}
+
+QT_END_NAMESPACE