blob: 1a8e60e194c919d7b1e896e0dbeb2375c4f6b642 [file] [log] [blame]
/****************************************************************************
**
** 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$
** GNU Lesser General Public License Usage
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $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 =
qVariantFromValue(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