diff options
Diffstat (limited to 'src/linguist/linguist/mainwindow.cpp')
-rw-r--r-- | src/linguist/linguist/mainwindow.cpp | 2724 |
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 ¤tIndex, 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 ¤tIndex, 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 |