diff options
Diffstat (limited to 'src/linguist/linguist')
120 files changed, 13789 insertions, 0 deletions
diff --git a/src/linguist/linguist/Info_mac.plist b/src/linguist/linguist/Info_mac.plist new file mode 100644 index 000000000..b11f493bd --- /dev/null +++ b/src/linguist/linguist/Info_mac.plist @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> +<plist version="0.1"> +<dict> + <key>CFBundleIconFile</key> + <string>linguist.icns</string> + <key>CFBundleIdentifier</key> + <string>com.trolltech.Linguist</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleGetInfoString</key> + <string>Created by Qt/QMake</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleExecutable</key> + <string>Linguist</string> +</dict> +</plist> diff --git a/src/linguist/linguist/batchtranslation.ui b/src/linguist/linguist/batchtranslation.ui new file mode 100644 index 000000000..659595622 --- /dev/null +++ b/src/linguist/linguist/batchtranslation.ui @@ -0,0 +1,260 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <comment>********************************************************************* +** +** 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$ +** +*********************************************************************</comment> + <class>BatchTranslationDialog</class> + <widget class="QDialog" name="batchTranslationDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>437</width> + <height>492</height> + </rect> + </property> + <property name="windowTitle"> + <string>Qt Linguist - Batch Translation</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Options</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QCheckBox" name="ckMarkFinished"> + <property name="toolTip"> + <string/> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>Set translated entries to finished</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="ckTranslateTranslated"> + <property name="checked"> + <bool>false</bool> + </property> + <property name="text"> + <string>Retranslate entries with existing translation</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="ckTranslateFinished"> + <property name="toolTip"> + <string>Note that the modified entries will be reset to unfinished if 'Set translated entries to finished' above is unchecked</string> + </property> + <property name="text"> + <string>Translate also finished entries</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Phrase book preference</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QListView" name="phrasebookList"> + <property name="uniformItemSizes"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="moveUpButton"> + <property name="text"> + <string>Move up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveDownButton"> + <property name="text"> + <string>Move down</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>The batch translator will search through the selected phrase books in the order given above</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="runButton"> + <property name="text"> + <string>&Run</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>cancelButton</sender> + <signal>clicked()</signal> + <receiver>batchTranslationDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>388</x> + <y>461</y> + </hint> + <hint type="destinationlabel"> + <x>188</x> + <y>465</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/linguist/linguist/batchtranslationdialog.cpp b/src/linguist/linguist/batchtranslationdialog.cpp new file mode 100644 index 000000000..e58316ff0 --- /dev/null +++ b/src/linguist/linguist/batchtranslationdialog.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "batchtranslationdialog.h" +#include "phrase.h" +#include "messagemodel.h" + +#include <QtGui/QMessageBox> +#include <QtGui/QProgressDialog> + +QT_BEGIN_NAMESPACE + +CheckableListModel::CheckableListModel(QObject *parent) + : QStandardItemModel(parent) +{ +} + +Qt::ItemFlags CheckableListModel::flags(const QModelIndex &index) const +{ + Q_UNUSED(index); + return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +BatchTranslationDialog::BatchTranslationDialog(MultiDataModel *dataModel, QWidget *w) + : QDialog(w), m_model(this), m_dataModel(dataModel) +{ + m_ui.setupUi(this); + connect(m_ui.runButton, SIGNAL(clicked()), this, SLOT(startTranslation())); + connect(m_ui.moveUpButton, SIGNAL(clicked()), this, SLOT(movePhraseBookUp())); + connect(m_ui.moveDownButton, SIGNAL(clicked()), this, SLOT(movePhraseBookDown())); + + m_ui.phrasebookList->setModel(&m_model); + m_ui.phrasebookList->setSelectionBehavior(QAbstractItemView::SelectItems); + m_ui.phrasebookList->setSelectionMode(QAbstractItemView::SingleSelection); +} + + +void BatchTranslationDialog::setPhraseBooks(const QList<PhraseBook *> &phrasebooks, int modelIndex) +{ + QString fn = QFileInfo(m_dataModel->srcFileName(modelIndex)).baseName(); + setWindowTitle(tr("Batch Translation of '%1' - Qt Linguist").arg(fn)); + m_model.clear(); + m_model.insertColumn(0); + m_phrasebooks = phrasebooks; + m_modelIndex = modelIndex; + int count = phrasebooks.count(); + m_model.insertRows(0, count); + for (int i = 0; i < count; ++i) { + QModelIndex idx(m_model.index(i, 0)); + m_model.setData(idx, phrasebooks[i]->friendlyPhraseBookName()); + int sortOrder; + if (phrasebooks[i]->language() != QLocale::C + && m_dataModel->language(m_modelIndex) != QLocale::C) { + if (phrasebooks[i]->language() != m_dataModel->language(m_modelIndex)) + sortOrder = 3; + else + sortOrder = (phrasebooks[i]->country() + == m_dataModel->model(m_modelIndex)->country()) ? 0 : 1; + } else { + sortOrder = 2; + } + m_model.setData(idx, sortOrder == 3 ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); + m_model.setData(idx, sortOrder, Qt::UserRole + 1); + m_model.setData(idx, i, Qt::UserRole); + } + m_model.setSortRole(Qt::UserRole + 1); + m_model.sort(0); +} + +void BatchTranslationDialog::startTranslation() +{ + int translatedcount = 0; + QCursor oldCursor = cursor(); + setCursor(Qt::BusyCursor); + int messageCount = m_dataModel->messageCount(); + + QProgressDialog *dlgProgress; + dlgProgress = new QProgressDialog(tr("Searching, please wait..."), tr("&Cancel"), 0, messageCount, this); + dlgProgress->show(); + + int msgidx = 0; + const bool translateTranslated = m_ui.ckTranslateTranslated->isChecked(); + const bool translateFinished = m_ui.ckTranslateFinished->isChecked(); + for (MultiDataModelIterator it(m_dataModel, m_modelIndex); it.isValid(); ++it) { + if (MessageItem *m = it.current()) { + if (!m->isObsolete() + && (translateTranslated || m->translation().isEmpty()) + && (translateFinished || !m->isFinished())) { + + // Go through them in the order the user specified in the phrasebookList + for (int b = 0; b < m_model.rowCount(); ++b) { + QModelIndex idx(m_model.index(b, 0)); + QVariant checkState = m_model.data(idx, Qt::CheckStateRole); + if (checkState == Qt::Checked) { + PhraseBook *pb = m_phrasebooks[m_model.data(idx, Qt::UserRole).toInt()]; + foreach (const Phrase *ph, pb->phrases()) { + if (ph->source() == m->text()) { + m_dataModel->setTranslation(it, ph->target()); + m_dataModel->setFinished(it, m_ui.ckMarkFinished->isChecked()); + ++translatedcount; + goto done; // break 2; + } + } + } + } + } + } + done: + ++msgidx; + if (!(msgidx & 15)) + dlgProgress->setValue(msgidx); + qApp->processEvents(); + if (dlgProgress->wasCanceled()) + break; + } + dlgProgress->hide(); + + setCursor(oldCursor); + emit finished(); + QMessageBox::information(this, tr("Linguist batch translator"), + tr("Batch translated %n entries", "", translatedcount), QMessageBox::Ok); +} + +void BatchTranslationDialog::movePhraseBookUp() +{ + QModelIndexList indexes = m_ui.phrasebookList->selectionModel()->selectedIndexes(); + if (indexes.count() <= 0) return; + + QModelIndex sel = indexes[0]; + int row = sel.row(); + if (row > 0) { + QModelIndex other = m_model.index(row - 1, 0); + QMap<int, QVariant> seldata = m_model.itemData(sel); + m_model.setItemData(sel, m_model.itemData(other)); + m_model.setItemData(other, seldata); + m_ui.phrasebookList->selectionModel()->setCurrentIndex(other, QItemSelectionModel::ClearAndSelect); + } +} + +void BatchTranslationDialog::movePhraseBookDown() +{ + QModelIndexList indexes = m_ui.phrasebookList->selectionModel()->selectedIndexes(); + if (indexes.count() <= 0) return; + + QModelIndex sel = indexes[0]; + int row = sel.row(); + if (row < m_model.rowCount() - 1) { + QModelIndex other = m_model.index(row + 1, 0); + QMap<int, QVariant> seldata = m_model.itemData(sel); + m_model.setItemData(sel, m_model.itemData(other)); + m_model.setItemData(other, seldata); + m_ui.phrasebookList->selectionModel()->setCurrentIndex(other, QItemSelectionModel::ClearAndSelect); + } +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/batchtranslationdialog.h b/src/linguist/linguist/batchtranslationdialog.h new file mode 100644 index 000000000..16d70cafe --- /dev/null +++ b/src/linguist/linguist/batchtranslationdialog.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef BATCHTRANSLATIONDIALOG_H +#define BATCHTRANSLATIONDIALOG_H + +#include "ui_batchtranslation.h" +#include "phrase.h" + +#include <QtGui/QDialog> +#include <QtGui/QStandardItemModel> + +QT_BEGIN_NAMESPACE + +class MultiDataModel; + +class CheckableListModel : public QStandardItemModel +{ +public: + CheckableListModel(QObject *parent = 0); + virtual Qt::ItemFlags flags(const QModelIndex &index) const; +}; + +class BatchTranslationDialog : public QDialog +{ + Q_OBJECT +public: + BatchTranslationDialog(MultiDataModel *model, QWidget *w = 0); + void setPhraseBooks(const QList<PhraseBook *> &phrasebooks, int modelIndex); + +signals: + void finished(); + +private slots: + void startTranslation(); + void movePhraseBookUp(); + void movePhraseBookDown(); + +private: + Ui::BatchTranslationDialog m_ui; + CheckableListModel m_model; + MultiDataModel *m_dataModel; + QList<PhraseBook *> m_phrasebooks; + int m_modelIndex; +}; + +QT_END_NAMESPACE + +#endif // BATCHTRANSLATIONDIALOG_H diff --git a/src/linguist/linguist/errorsview.cpp b/src/linguist/linguist/errorsview.cpp new file mode 100644 index 000000000..f24b3bede --- /dev/null +++ b/src/linguist/linguist/errorsview.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "errorsview.h" + +#include "messagemodel.h" + +#include <QtCore/QList> +#include <QtCore/QString> +#include <QtCore/QUrl> + +#include <QtGui/QListView> +#include <QtGui/QStandardItem> +#include <QtGui/QStandardItemModel> +#include <QtGui/QTextEdit> +#include <QtGui/QVBoxLayout> + +QT_BEGIN_NAMESPACE + +ErrorsView::ErrorsView(MultiDataModel *dataModel, QWidget *parent) : + QListView(parent), + m_dataModel(dataModel) +{ + m_list = new QStandardItemModel(this); + setModel(m_list); +} + +void ErrorsView::clear() +{ + m_list->clear(); +} + +void ErrorsView::addError(int model, const ErrorType type, const QString &arg) +{ + QString error; + switch (type) { + case SuperfluousAccelerator: + addError(model, tr("Accelerator possibly superfluous in translation.")); + break; + case MissingAccelerator: + addError(model, tr("Accelerator possibly missing in translation.")); + break; + case PunctuationDiffer: + addError(model, tr("Translation does not end with the same punctuation as the source text.")); + break; + case IgnoredPhrasebook: + addError(model, tr("A phrase book suggestion for '%1' was ignored.").arg(arg)); + break; + case PlaceMarkersDiffer: + addError(model, tr("Translation does not refer to the same place markers as in the source text.")); + break; + case NumerusMarkerMissing: + addError(model, tr("Translation does not contain the necessary %n place marker.")); + break; + default: + addError(model, tr("Unknown error")); + break; + } +} + +QString ErrorsView::firstError() +{ + return (m_list->rowCount() == 0) ? QString() : m_list->item(0)->text(); +} + +void ErrorsView::addError(int model, const QString &error) +{ + // NOTE: Three statics instead of one just for GCC 3.3.5 + static QLatin1String imageLocation(":/images/s_check_danger.png"); + static QPixmap image(imageLocation); + static QIcon pxDanger(image); + QString lang; + if (m_dataModel->modelCount() > 1) + lang = m_dataModel->model(model)->localizedLanguage() + QLatin1String(": "); + QStandardItem *item = new QStandardItem(pxDanger, lang + error); + item->setEditable(false); + m_list->appendRow(QList<QStandardItem*>() << item); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/errorsview.h b/src/linguist/linguist/errorsview.h new file mode 100644 index 000000000..a7a7dc1b7 --- /dev/null +++ b/src/linguist/linguist/errorsview.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef ERRORSVIEW_H +#define ERRORSVIEW_H + +#include <QListView> + +QT_BEGIN_NAMESPACE + +class QStandardItemModel; + +class MultiDataModel; + +class ErrorsView : public QListView +{ + Q_OBJECT +public: + enum ErrorType { + SuperfluousAccelerator, + MissingAccelerator, + PunctuationDiffer, + IgnoredPhrasebook, + PlaceMarkersDiffer, + NumerusMarkerMissing + }; + + ErrorsView(MultiDataModel *dataModel, QWidget *parent = 0); + void clear(); + void addError(int model, const ErrorType type, const QString &arg = QString()); + QString firstError(); +private: + void addError(int model, const QString &error); + QStandardItemModel *m_list; + MultiDataModel *m_dataModel; +}; + +QT_END_NAMESPACE + +#endif // ERRORSVIEW_H diff --git a/src/linguist/linguist/finddialog.cpp b/src/linguist/linguist/finddialog.cpp new file mode 100644 index 000000000..f05b710aa --- /dev/null +++ b/src/linguist/linguist/finddialog.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 FindDialog + + Choose Edit|Find from the menu bar or press Ctrl+F to pop up the + Find dialog +*/ + +#include "finddialog.h" + +QT_BEGIN_NAMESPACE + +FindDialog::FindDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + + findNxt->setEnabled(false); + + connect(findNxt, SIGNAL(clicked()), this, SLOT(emitFindNext())); + connect(led, SIGNAL(textChanged(QString)), this, SLOT(verifyText(QString))); + + led->setFocus(); +} + +void FindDialog::verifyText(const QString &text) +{ + findNxt->setEnabled(!text.isEmpty()); +} + +void FindDialog::emitFindNext() +{ + DataModel::FindLocation where; + if (sourceText != 0) + where = + DataModel::FindLocation( + (sourceText->isChecked() ? DataModel::SourceText : 0) | + (translations->isChecked() ? DataModel::Translations : 0) | + (comments->isChecked() ? DataModel::Comments : 0)); + else + where = DataModel::Translations; + emit findNext(led->text(), where, matchCase->isChecked(), ignoreAccelerators->isChecked()); + led->selectAll(); +} + +void FindDialog::find() +{ + led->setFocus(); + + show(); + activateWindow(); + raise(); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/finddialog.h b/src/linguist/linguist/finddialog.h new file mode 100644 index 000000000..afacd5b7d --- /dev/null +++ b/src/linguist/linguist/finddialog.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef FINDDIALOG_H +#define FINDDIALOG_H + +#include "ui_finddialog.h" +#include "messagemodel.h" + +#include <QDialog> + +QT_BEGIN_NAMESPACE + +class FindDialog : public QDialog, public Ui::FindDialog +{ + Q_OBJECT +public: + FindDialog(QWidget *parent = 0); + +signals: + void findNext(const QString& text, DataModel::FindLocation where, bool matchCase, bool ignoreAccelerators); + +private slots: + void emitFindNext(); + void verifyText(const QString &); + void find(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/linguist/linguist/finddialog.ui b/src/linguist/linguist/finddialog.ui new file mode 100644 index 000000000..2ecb110d7 --- /dev/null +++ b/src/linguist/linguist/finddialog.ui @@ -0,0 +1,266 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <comment>********************************************************************* +** +** 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$ +** +*********************************************************************</comment> + <class>FindDialog</class> + <widget class="QDialog" name="FindDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>414</width> + <height>175</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Find</string> + </property> + <property name="whatsThis"> + <string>This window allows you to search for some text in the translation source file.</string> + </property> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>11</number> + </property> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="findWhat"> + <property name="text"> + <string>&Find what:</string> + </property> + <property name="buddy"> + <cstring>led</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="led"> + <property name="whatsThis"> + <string>Type in the text to search for.</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Options</string> + </property> + <layout class="QGridLayout"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="1" column="0"> + <widget class="QCheckBox" name="sourceText"> + <property name="whatsThis"> + <string>Source texts are searched when checked.</string> + </property> + <property name="text"> + <string>&Source texts</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="translations"> + <property name="whatsThis"> + <string>Translations are searched when checked.</string> + </property> + <property name="text"> + <string>&Translations</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="matchCase"> + <property name="whatsThis"> + <string>Texts such as 'TeX' and 'tex' are considered as different when checked.</string> + </property> + <property name="text"> + <string>&Match case</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="comments"> + <property name="whatsThis"> + <string>Comments and contexts are searched when checked.</string> + </property> + <property name="text"> + <string>&Comments</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="ignoreAccelerators"> + <property name="text"> + <string>Ignore &accelerators</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="findNxt"> + <property name="whatsThis"> + <string>Click here to find the next occurrence of the text you typed in.</string> + </property> + <property name="text"> + <string>Find Next</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancel"> + <property name="whatsThis"> + <string>Click here to close this window.</string> + </property> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>51</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <tabstops> + <tabstop>led</tabstop> + <tabstop>findNxt</tabstop> + <tabstop>cancel</tabstop> + <tabstop>comments</tabstop> + <tabstop>sourceText</tabstop> + <tabstop>translations</tabstop> + <tabstop>matchCase</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>cancel</sender> + <signal>clicked()</signal> + <receiver>FindDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>372</x> + <y>58</y> + </hint> + <hint type="destinationlabel"> + <x>373</x> + <y>109</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/linguist/linguist/formpreviewview.cpp b/src/linguist/linguist/formpreviewview.cpp new file mode 100644 index 000000000..a2a600257 --- /dev/null +++ b/src/linguist/linguist/formpreviewview.cpp @@ -0,0 +1,537 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "formpreviewview.h" +#include "messagemodel.h" + +#include <quiloader.h> +#include <abstractformbuilder.h> + +#include <QtCore/QDebug> +#include <QtCore/QTime> + +#include <QtGui/QAction> +#include <QtGui/QApplication> +#include <QtGui/QFontComboBox> +#include <QtGui/QFrame> +#include <QtGui/QGridLayout> +#include <QtGui/QListWidget> +#include <QtGui/QMdiArea> +#include <QtGui/QMdiSubWindow> +#include <QtGui/QMenu> +#include <QtGui/QTableWidget> +#include <QtGui/QTabWidget> +#include <QtGui/QToolBox> +#include <QtGui/QTreeWidget> + +QT_BEGIN_NAMESPACE + +#if defined(Q_CC_SUN) || defined(Q_CC_HPACC) || defined(Q_CC_XLC) +int qHash(const QUiTranslatableStringValue &tsv) +#else +static int qHash(const QUiTranslatableStringValue &tsv) +#endif +{ + return qHash(tsv.value()) ^ qHash(tsv.comment()); +} + +static bool operator==(const QUiTranslatableStringValue &tsv1, const QUiTranslatableStringValue &tsv2) +{ + return tsv1.value() == tsv2.value() && tsv1.comment() == tsv2.comment(); +} + +#define INSERT_TARGET(_tsv, _type, _target, _prop) \ + do { \ + target.type = _type; \ + target.target._target; \ + target.prop._prop; \ + (*targets)[qvariant_cast<QUiTranslatableStringValue>(_tsv)].append(target); \ + } while (0) + +static void registerTreeItem(QTreeWidgetItem *item, TargetsHash *targets) +{ + const QUiItemRolePair *irs = QFormInternal::qUiItemRoles; + + int cnt = item->columnCount(); + for (int i = 0; i < cnt; ++i) { + for (unsigned j = 0; irs[j].shadowRole >= 0; j++) { + QVariant v = item->data(i, irs[j].shadowRole); + if (v.isValid()) { + TranslatableEntry target; + target.prop.treeIndex.column = i; + INSERT_TARGET(v, TranslatableTreeWidgetItem, treeWidgetItem = item, treeIndex.index = j); + } + } + } + + cnt = item->childCount(); + for (int j = 0; j < cnt; ++j) + registerTreeItem(item->child(j), targets); +} + +#define REGISTER_ITEM_CORE(item, propType, targetName) \ + const QUiItemRolePair *irs = QFormInternal::qUiItemRoles; \ + for (unsigned j = 0; irs[j].shadowRole >= 0; j++) { \ + QVariant v = item->data(irs[j].shadowRole); \ + if (v.isValid()) \ + INSERT_TARGET(v, propType, targetName = item, index = j); \ + } + +static void registerListItem(QListWidgetItem *item, TargetsHash *targets) +{ + TranslatableEntry target; + REGISTER_ITEM_CORE(item, TranslatableListWidgetItem, listWidgetItem); +} + +static void registerTableItem(QTableWidgetItem *item, TargetsHash *targets) +{ + if (!item) + return; + + TranslatableEntry target; + REGISTER_ITEM_CORE(item, TranslatableTableWidgetItem, tableWidgetItem); +} + +#define REGISTER_SUBWIDGET_PROP(mainWidget, propType, propName) \ + do { \ + QVariant v = mainWidget->widget(i)->property(propName); \ + if (v.isValid()) \ + INSERT_TARGET(v, propType, object = mainWidget, index = i); \ + } while (0) + +static void buildTargets(QObject *o, TargetsHash *targets) +{ + TranslatableEntry target; + + foreach (const QByteArray &prop, o->dynamicPropertyNames()) { + if (prop.startsWith(PROP_GENERIC_PREFIX)) { + const QByteArray propName = prop.mid(sizeof(PROP_GENERIC_PREFIX) - 1); + INSERT_TARGET(o->property(prop), + TranslatableProperty, object = o, name = qstrdup(propName.data())); + } + } + if (0) { +#ifndef QT_NO_TABWIDGET + } else if (QTabWidget *tabw = qobject_cast<QTabWidget*>(o)) { + const int cnt = tabw->count(); + for (int i = 0; i < cnt; ++i) { + REGISTER_SUBWIDGET_PROP(tabw, TranslatableTabPageText, PROP_TABPAGETEXT); +# ifndef QT_NO_TOOLTIP + REGISTER_SUBWIDGET_PROP(tabw, TranslatableTabPageToolTip, PROP_TABPAGETOOLTIP); +# endif +# ifndef QT_NO_WHATSTHIS + REGISTER_SUBWIDGET_PROP(tabw, TranslatableTabPageWhatsThis, PROP_TABPAGEWHATSTHIS); +# endif + } +#endif +#ifndef QT_NO_TOOLBOX + } else if (QToolBox *toolw = qobject_cast<QToolBox*>(o)) { + const int cnt = toolw->count(); + for (int i = 0; i < cnt; ++i) { + REGISTER_SUBWIDGET_PROP(toolw, TranslatableToolItemText, PROP_TOOLITEMTEXT); +# ifndef QT_NO_TOOLTIP + REGISTER_SUBWIDGET_PROP(toolw, TranslatableToolItemToolTip, PROP_TOOLITEMTOOLTIP); +# endif + } +#endif +#ifndef QT_NO_COMBOBOX + } else if (QComboBox *combow = qobject_cast<QComboBox*>(o)) { + if (!qobject_cast<QFontComboBox*>(o)) { + const int cnt = combow->count(); + for (int i = 0; i < cnt; ++i) { + const QVariant v = combow->itemData(i, Qt::DisplayPropertyRole); + if (v.isValid()) + INSERT_TARGET(v, TranslatableComboBoxItem, comboBox = combow, index = i); + } + } +#endif +#ifndef QT_NO_LISTWIDGET + } else if (QListWidget *listw = qobject_cast<QListWidget*>(o)) { + const int cnt = listw->count(); + for (int i = 0; i < cnt; ++i) + registerListItem(listw->item(i), targets); +#endif +#ifndef QT_NO_TABLEWIDGET + } else if (QTableWidget *tablew = qobject_cast<QTableWidget*>(o)) { + const int row_cnt = tablew->rowCount(); + const int col_cnt = tablew->columnCount(); + for (int j = 0; j < col_cnt; ++j) + registerTableItem(tablew->verticalHeaderItem(j), targets); + for (int i = 0; i < row_cnt; ++i) { + registerTableItem(tablew->horizontalHeaderItem(i), targets); + for (int j = 0; j < col_cnt; ++j) + registerTableItem(tablew->item(i, j), targets); + } +#endif +#ifndef QT_NO_TREEWIDGET + } else if (QTreeWidget *treew = qobject_cast<QTreeWidget*>(o)) { + if (QTreeWidgetItem *item = treew->headerItem()) + registerTreeItem(item, targets); + const int cnt = treew->topLevelItemCount(); + for (int i = 0; i < cnt; ++i) + registerTreeItem(treew->topLevelItem(i), targets); +#endif + } + foreach (QObject *co, o->children()) + buildTargets(co, targets); +} + +static void destroyTargets(TargetsHash *targets) +{ + for (TargetsHash::Iterator it = targets->begin(), end = targets->end(); it != end; ++it) + foreach (const TranslatableEntry &target, *it) + if (target.type == TranslatableProperty) + delete target.prop.name; + targets->clear(); +} + +static void retranslateTarget(const TranslatableEntry &target, const QString &text) +{ + switch (target.type) { + case TranslatableProperty: + target.target.object->setProperty(target.prop.name, text); + break; +#ifndef QT_NO_TABWIDGET + case TranslatableTabPageText: + target.target.tabWidget->setTabText(target.prop.index, text); + break; +# ifndef QT_NO_TOOLTIP + case TranslatableTabPageToolTip: + target.target.tabWidget->setTabToolTip(target.prop.index, text); + break; +# endif +# ifndef QT_NO_WHATSTHIS + case TranslatableTabPageWhatsThis: + target.target.tabWidget->setTabWhatsThis(target.prop.index, text); + break; +# endif +#endif // QT_NO_TABWIDGET +#ifndef QT_NO_TOOLBOX + case TranslatableToolItemText: + target.target.toolBox->setItemText(target.prop.index, text); + break; +# ifndef QT_NO_TOOLTIP + case TranslatableToolItemToolTip: + target.target.toolBox->setItemToolTip(target.prop.index, text); + break; +# endif +#endif // QT_NO_TOOLBOX +#ifndef QT_NO_COMBOBOX + case TranslatableComboBoxItem: + target.target.comboBox->setItemText(target.prop.index, text); + break; +#endif +#ifndef QT_NO_LISTWIDGET + case TranslatableListWidgetItem: + target.target.listWidgetItem->setData(target.prop.index, text); + break; +#endif +#ifndef QT_NO_TABLEWIDGET + case TranslatableTableWidgetItem: + target.target.tableWidgetItem->setData(target.prop.index, text); + break; +#endif +#ifndef QT_NO_TREEWIDGET + case TranslatableTreeWidgetItem: + target.target.treeWidgetItem->setData(target.prop.treeIndex.column, target.prop.treeIndex.index, text); + break; +#endif + } +} + +static void retranslateTargets( + const QList<TranslatableEntry> &targets, const QUiTranslatableStringValue &tsv, + const DataModel *dataModel, const QString &className) +{ + QString sourceText = QString::fromUtf8(tsv.value()); + QString text; + if (MessageItem *msg = dataModel->findMessage( + className, sourceText, QString::fromUtf8(tsv.comment()))) + text = msg->translation(); + if (text.isEmpty() && !tsv.value().isEmpty()) + text = QLatin1Char('#') + sourceText; + + foreach (const TranslatableEntry &target, targets) + retranslateTarget(target, text); +} + +static void highlightTreeWidgetItem(QTreeWidgetItem *item, int col, bool on) +{ + QVariant br = item->data(col, Qt::BackgroundRole + 500); + QVariant fr = item->data(col, Qt::ForegroundRole + 500); + if (on) { + if (!br.isValid() && !fr.isValid()) { + item->setData(col, Qt::BackgroundRole + 500, item->data(col, Qt::BackgroundRole)); + item->setData(col, Qt::ForegroundRole + 500, item->data(col, Qt::ForegroundRole)); + QPalette pal = qApp->palette(); + item->setData(col, Qt::BackgroundRole, pal.color(QPalette::Dark)); + item->setData(col, Qt::ForegroundRole, pal.color(QPalette::Light)); + } + } else { + if (br.isValid() || fr.isValid()) { + item->setData(col, Qt::BackgroundRole, br); + item->setData(col, Qt::ForegroundRole, fr); + item->setData(col, Qt::BackgroundRole + 500, QVariant()); + item->setData(col, Qt::ForegroundRole + 500, QVariant()); + } + } +} + +template <class T> +static void highlightWidgetItem(T *item, bool on) +{ + QVariant br = item->data(Qt::BackgroundRole + 500); + QVariant fr = item->data(Qt::ForegroundRole + 500); + if (on) { + if (!br.isValid() && !fr.isValid()) { + item->setData(Qt::BackgroundRole + 500, item->data(Qt::BackgroundRole)); + item->setData(Qt::ForegroundRole + 500, item->data(Qt::ForegroundRole)); + QPalette pal = qApp->palette(); + item->setData(Qt::BackgroundRole, pal.color(QPalette::Dark)); + item->setData(Qt::ForegroundRole, pal.color(QPalette::Light)); + } + } else { + if (br.isValid() || fr.isValid()) { + item->setData(Qt::BackgroundRole, br); + item->setData(Qt::ForegroundRole, fr); + item->setData(Qt::BackgroundRole + 500, QVariant()); + item->setData(Qt::ForegroundRole + 500, QVariant()); + } + } +} + +#define AUTOFILL_BACKUP_PROP "_q_linguist_autoFillBackup" +#define PALETTE_BACKUP_PROP "_q_linguist_paletteBackup" +#define FONT_BACKUP_PROP "_q_linguist_fontBackup" + +static void highlightWidget(QWidget *w, bool on); + +static void highlightAction(QAction *a, bool on) +{ + QVariant bak = a->property(FONT_BACKUP_PROP); + if (on) { + if (!bak.isValid()) { + QFont fnt = qApp->font(); + a->setProperty(FONT_BACKUP_PROP, QVariant::fromValue(a->font().resolve(fnt))); + fnt.setBold(true); + fnt.setItalic(true); + a->setFont(fnt); + } + } else { + if (bak.isValid()) { + a->setFont(qvariant_cast<QFont>(bak)); + a->setProperty(FONT_BACKUP_PROP, QVariant()); + } + } + foreach (QWidget *w, a->associatedWidgets()) + highlightWidget(w, on); +} + +static void highlightWidget(QWidget *w, bool on) +{ + QVariant bak = w->property(PALETTE_BACKUP_PROP); + if (on) { + if (!bak.isValid()) { + QPalette pal = qApp->palette(); + foreach (QObject *co, w->children()) + if (QWidget *cw = qobject_cast<QWidget *>(co)) + cw->setPalette(cw->palette().resolve(pal)); + w->setProperty(PALETTE_BACKUP_PROP, QVariant::fromValue(w->palette().resolve(pal))); + w->setProperty(AUTOFILL_BACKUP_PROP, QVariant::fromValue(w->autoFillBackground())); + QColor col1 = pal.color(QPalette::Dark); + QColor col2 = pal.color(QPalette::Light); + pal.setColor(QPalette::Base, col1); + pal.setColor(QPalette::Window, col1); + pal.setColor(QPalette::Button, col1); + pal.setColor(QPalette::Text, col2); + pal.setColor(QPalette::WindowText, col2); + pal.setColor(QPalette::ButtonText, col2); + pal.setColor(QPalette::BrightText, col2); + w->setPalette(pal); + w->setAutoFillBackground(true); + } + } else { + if (bak.isValid()) { + w->setPalette(qvariant_cast<QPalette>(bak)); + w->setAutoFillBackground(qvariant_cast<bool>(w->property(AUTOFILL_BACKUP_PROP))); + w->setProperty(PALETTE_BACKUP_PROP, QVariant()); + w->setProperty(AUTOFILL_BACKUP_PROP, QVariant()); + } + } + if (QMenu *m = qobject_cast<QMenu *>(w)) + if (m->menuAction()) + highlightAction(m->menuAction(), on); +} + +static void highlightTarget(const TranslatableEntry &target, bool on) +{ + switch (target.type) { + case TranslatableProperty: + if (QAction *a = qobject_cast<QAction *>(target.target.object)) { + highlightAction(a, on); + break; + } + // fallthrough +#ifndef QT_NO_TABWIDGET + case TranslatableTabPageText: +# ifndef QT_NO_TOOLTIP + case TranslatableTabPageToolTip: +# endif +# ifndef QT_NO_WHATSTHIS + case TranslatableTabPageWhatsThis: +# endif +#endif // QT_NO_TABWIDGET +#ifndef QT_NO_TOOLBOX + case TranslatableToolItemText: +# ifndef QT_NO_TOOLTIP + case TranslatableToolItemToolTip: +# endif +#endif // QT_NO_TOOLBOX +#ifndef QT_NO_COMBOBOX + case TranslatableComboBoxItem: +#endif + if (QWidget *w = qobject_cast<QWidget *>(target.target.object)) + highlightWidget(w, on); + break; +#ifndef QT_NO_LISTWIDGET + case TranslatableListWidgetItem: + highlightWidgetItem(target.target.listWidgetItem, on); + break; +#endif +#ifndef QT_NO_TABLEWIDGET + case TranslatableTableWidgetItem: + highlightWidgetItem(target.target.tableWidgetItem, on); + break; +#endif +#ifndef QT_NO_TREEWIDGET + case TranslatableTreeWidgetItem: + highlightTreeWidgetItem(target.target.treeWidgetItem, target.prop.treeIndex.column, on); + break; +#endif + } +} + +static void highlightTargets(const QList<TranslatableEntry> &targets, bool on) +{ + foreach (const TranslatableEntry &target, targets) + highlightTarget(target, on); +} + +FormPreviewView::FormPreviewView(QWidget *parent, MultiDataModel *dataModel) + : QMainWindow(parent), m_form(0), m_dataModel(dataModel) +{ + m_mdiSubWindow = new QMdiSubWindow; + m_mdiSubWindow->setWindowFlags(m_mdiSubWindow->windowFlags() & ~Qt::WindowSystemMenuHint); + m_mdiArea = new QMdiArea(this); + m_mdiArea->addSubWindow(m_mdiSubWindow); + setCentralWidget(m_mdiArea); + m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); +} + +void FormPreviewView::setSourceContext(int model, MessageItem *messageItem) +{ + if (model < 0 || !messageItem) { + m_lastModel = -1; + return; + } + + QDir dir = QFileInfo(m_dataModel->srcFileName(model)).dir(); + QString fileName = QDir::cleanPath(dir.absoluteFilePath(messageItem->fileName())); + if (m_lastFormName != fileName) { + delete m_form; + m_form = 0; + m_lastFormName.clear(); + m_highlights.clear(); + destroyTargets(&m_targets); + + static QUiLoader *uiLoader; + if (!uiLoader) { + uiLoader = new QUiLoader(this); + uiLoader->setLanguageChangeEnabled(true); + uiLoader->setTranslationEnabled(false); + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "CANNOT OPEN FORM" << fileName; + m_mdiSubWindow->hide(); + return; + } + m_form = uiLoader->load(&file, m_mdiSubWindow); + if (!m_form) { + qDebug() << "CANNOT LOAD FORM" << fileName; + m_mdiSubWindow->hide(); + return; + } + file.close(); + buildTargets(m_form, &m_targets); + + setToolTip(fileName); + + m_form->setWindowFlags(Qt::Widget); + m_form->setWindowModality(Qt::NonModal); + m_form->setFocusPolicy(Qt::NoFocus); + m_form->show(); // needed, otherwide the Qt::NoFocus is not propagated. + m_mdiSubWindow->setWidget(m_form); + m_mdiSubWindow->show(); + m_mdiArea->cascadeSubWindows(); + m_lastFormName = fileName; + m_lastClassName = messageItem->context(); + m_lastModel = -1; + } else { + highlightTargets(m_highlights, false); + } + QUiTranslatableStringValue tsv; + tsv.setValue(messageItem->text().toUtf8()); + tsv.setComment(messageItem->comment().toUtf8()); + m_highlights = m_targets.value(tsv); + if (m_lastModel != model) { + for (TargetsHash::Iterator it = m_targets.begin(), end = m_targets.end(); it != end; ++it) + retranslateTargets(*it, it.key(), m_dataModel->model(model), m_lastClassName); + m_lastModel = model; + } else { + retranslateTargets(m_highlights, tsv, m_dataModel->model(model), m_lastClassName); + } + highlightTargets(m_highlights, true); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/formpreviewview.h b/src/linguist/linguist/formpreviewview.h new file mode 100644 index 000000000..e30160670 --- /dev/null +++ b/src/linguist/linguist/formpreviewview.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef FORMPREVIEWVIEW_H +#define FORMPREVIEWVIEW_H + +#include <quiloader_p.h> + +#include <QtCore/QHash> +#include <QtCore/QList> + +#include <QtGui/QMainWindow> + +QT_BEGIN_NAMESPACE + +class MultiDataModel; +class FormFrame; +class MessageItem; + +class QComboBox; +class QListWidgetItem; +class QGridLayout; +class QMdiArea; +class QMdiSubWindow; +class QToolBox; +class QTableWidgetItem; +class QTreeWidgetItem; + +enum TranslatableEntryType { + TranslatableProperty, + TranslatableToolItemText, + TranslatableToolItemToolTip, + TranslatableTabPageText, + TranslatableTabPageToolTip, + TranslatableTabPageWhatsThis, + TranslatableListWidgetItem, + TranslatableTableWidgetItem, + TranslatableTreeWidgetItem, + TranslatableComboBoxItem +}; + +struct TranslatableEntry { + TranslatableEntryType type; + union { + QObject *object; + QComboBox *comboBox; + QTabWidget *tabWidget; + QToolBox *toolBox; + QListWidgetItem *listWidgetItem; + QTableWidgetItem *tableWidgetItem; + QTreeWidgetItem *treeWidgetItem; + } target; + union { + char *name; + int index; + struct { + short index; // Known to be below 1000 + short column; + } treeIndex; + } prop; +}; + +typedef QHash<QUiTranslatableStringValue, QList<TranslatableEntry> > TargetsHash; + +class FormPreviewView : public QMainWindow +{ + Q_OBJECT +public: + FormPreviewView(QWidget *parent, MultiDataModel *dataModel); + + void setSourceContext(int model, MessageItem *messageItem); + +private: + bool m_isActive; + QString m_currentFileName; + QMdiArea *m_mdiArea; + QMdiSubWindow *m_mdiSubWindow; + QWidget *m_form; + TargetsHash m_targets; + QList<TranslatableEntry> m_highlights; + MultiDataModel *m_dataModel; + + QString m_lastFormName; + QString m_lastClassName; + int m_lastModel; +}; + +QT_END_NAMESPACE + +#endif // FORMPREVIEWVIEW_H diff --git a/src/linguist/linguist/globals.cpp b/src/linguist/linguist/globals.cpp new file mode 100644 index 000000000..f2a8cd248 --- /dev/null +++ b/src/linguist/linguist/globals.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "globals.h" + +const QString &settingsPrefix() +{ + static QString prefix = QString(QLatin1String("%1.%2/")) + .arg((QT_VERSION >> 16) & 0xff) + .arg((QT_VERSION >> 8) & 0xff); + return prefix; +} + +QString settingPath(const char *path) +{ + return settingsPrefix() + QLatin1String(path); +} diff --git a/src/linguist/linguist/globals.h b/src/linguist/linguist/globals.h new file mode 100644 index 000000000..7d82f5b59 --- /dev/null +++ b/src/linguist/linguist/globals.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef GLOBALS_H +#define GLOBALS_H + +#include <QString> + +const QString &settingsPrefix(); +QString settingPath(const char *path); + +#endif // GLOBALS_H diff --git a/src/linguist/linguist/images/appicon.png b/src/linguist/linguist/images/appicon.png Binary files differnew file mode 100644 index 000000000..d388cbd0b --- /dev/null +++ b/src/linguist/linguist/images/appicon.png diff --git a/src/linguist/linguist/images/down.png b/src/linguist/linguist/images/down.png Binary files differnew file mode 100644 index 000000000..29d1d4439 --- /dev/null +++ b/src/linguist/linguist/images/down.png diff --git a/src/linguist/linguist/images/editdelete.png b/src/linguist/linguist/images/editdelete.png Binary files differnew file mode 100644 index 000000000..df2a147d2 --- /dev/null +++ b/src/linguist/linguist/images/editdelete.png diff --git a/src/linguist/linguist/images/icons/linguist-128-32.png b/src/linguist/linguist/images/icons/linguist-128-32.png Binary files differnew file mode 100644 index 000000000..d108208fa --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-128-32.png diff --git a/src/linguist/linguist/images/icons/linguist-128-8.png b/src/linguist/linguist/images/icons/linguist-128-8.png Binary files differnew file mode 100644 index 000000000..6678dd2ae --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-128-8.png diff --git a/src/linguist/linguist/images/icons/linguist-16-32.png b/src/linguist/linguist/images/icons/linguist-16-32.png Binary files differnew file mode 100644 index 000000000..8dfc97406 --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-16-32.png diff --git a/src/linguist/linguist/images/icons/linguist-16-8.png b/src/linguist/linguist/images/icons/linguist-16-8.png Binary files differnew file mode 100644 index 000000000..4f4e83955 --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-16-8.png diff --git a/src/linguist/linguist/images/icons/linguist-32-32.png b/src/linguist/linguist/images/icons/linguist-32-32.png Binary files differnew file mode 100644 index 000000000..d388cbd0b --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-32-32.png diff --git a/src/linguist/linguist/images/icons/linguist-32-8.png b/src/linguist/linguist/images/icons/linguist-32-8.png Binary files differnew file mode 100644 index 000000000..3db4bc5c0 --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-32-8.png diff --git a/src/linguist/linguist/images/icons/linguist-48-32.png b/src/linguist/linguist/images/icons/linguist-48-32.png Binary files differnew file mode 100644 index 000000000..ceb738759 --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-48-32.png diff --git a/src/linguist/linguist/images/icons/linguist-48-8.png b/src/linguist/linguist/images/icons/linguist-48-8.png Binary files differnew file mode 100644 index 000000000..9a13c201d --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-48-8.png diff --git a/src/linguist/linguist/images/icons/linguist-64-32.png b/src/linguist/linguist/images/icons/linguist-64-32.png Binary files differnew file mode 100644 index 000000000..0cce805e3 --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-64-32.png diff --git a/src/linguist/linguist/images/icons/linguist-64-8.png b/src/linguist/linguist/images/icons/linguist-64-8.png Binary files differnew file mode 100644 index 000000000..05c833dc9 --- /dev/null +++ b/src/linguist/linguist/images/icons/linguist-64-8.png diff --git a/src/linguist/linguist/images/mac/accelerator.png b/src/linguist/linguist/images/mac/accelerator.png Binary files differnew file mode 100644 index 000000000..9a684c104 --- /dev/null +++ b/src/linguist/linguist/images/mac/accelerator.png diff --git a/src/linguist/linguist/images/mac/book.png b/src/linguist/linguist/images/mac/book.png Binary files differnew file mode 100644 index 000000000..7a3204c87 --- /dev/null +++ b/src/linguist/linguist/images/mac/book.png diff --git a/src/linguist/linguist/images/mac/doneandnext.png b/src/linguist/linguist/images/mac/doneandnext.png Binary files differnew file mode 100644 index 000000000..512fea884 --- /dev/null +++ b/src/linguist/linguist/images/mac/doneandnext.png diff --git a/src/linguist/linguist/images/mac/editcopy.png b/src/linguist/linguist/images/mac/editcopy.png Binary files differnew file mode 100644 index 000000000..f55136446 --- /dev/null +++ b/src/linguist/linguist/images/mac/editcopy.png diff --git a/src/linguist/linguist/images/mac/editcut.png b/src/linguist/linguist/images/mac/editcut.png Binary files differnew file mode 100644 index 000000000..a784fd570 --- /dev/null +++ b/src/linguist/linguist/images/mac/editcut.png diff --git a/src/linguist/linguist/images/mac/editpaste.png b/src/linguist/linguist/images/mac/editpaste.png Binary files differnew file mode 100644 index 000000000..64c0b2d6a --- /dev/null +++ b/src/linguist/linguist/images/mac/editpaste.png diff --git a/src/linguist/linguist/images/mac/filenew.png b/src/linguist/linguist/images/mac/filenew.png Binary files differnew file mode 100644 index 000000000..d3882c7b3 --- /dev/null +++ b/src/linguist/linguist/images/mac/filenew.png diff --git a/src/linguist/linguist/images/mac/fileopen.png b/src/linguist/linguist/images/mac/fileopen.png Binary files differnew file mode 100644 index 000000000..fc06c5ec6 --- /dev/null +++ b/src/linguist/linguist/images/mac/fileopen.png diff --git a/src/linguist/linguist/images/mac/fileprint.png b/src/linguist/linguist/images/mac/fileprint.png Binary files differnew file mode 100644 index 000000000..808c97ea3 --- /dev/null +++ b/src/linguist/linguist/images/mac/fileprint.png diff --git a/src/linguist/linguist/images/mac/filesave.png b/src/linguist/linguist/images/mac/filesave.png Binary files differnew file mode 100644 index 000000000..b41ecf531 --- /dev/null +++ b/src/linguist/linguist/images/mac/filesave.png diff --git a/src/linguist/linguist/images/mac/next.png b/src/linguist/linguist/images/mac/next.png Binary files differnew file mode 100644 index 000000000..ba0792c36 --- /dev/null +++ b/src/linguist/linguist/images/mac/next.png diff --git a/src/linguist/linguist/images/mac/nextunfinished.png b/src/linguist/linguist/images/mac/nextunfinished.png Binary files differnew file mode 100644 index 000000000..bffbf0bb1 --- /dev/null +++ b/src/linguist/linguist/images/mac/nextunfinished.png diff --git a/src/linguist/linguist/images/mac/phrase.png b/src/linguist/linguist/images/mac/phrase.png Binary files differnew file mode 100644 index 000000000..651234109 --- /dev/null +++ b/src/linguist/linguist/images/mac/phrase.png diff --git a/src/linguist/linguist/images/mac/prev.png b/src/linguist/linguist/images/mac/prev.png Binary files differnew file mode 100644 index 000000000..612fb34dc --- /dev/null +++ b/src/linguist/linguist/images/mac/prev.png diff --git a/src/linguist/linguist/images/mac/prevunfinished.png b/src/linguist/linguist/images/mac/prevunfinished.png Binary files differnew file mode 100644 index 000000000..4d937b2a2 --- /dev/null +++ b/src/linguist/linguist/images/mac/prevunfinished.png diff --git a/src/linguist/linguist/images/mac/print.png b/src/linguist/linguist/images/mac/print.png Binary files differnew file mode 100644 index 000000000..10ca56c82 --- /dev/null +++ b/src/linguist/linguist/images/mac/print.png diff --git a/src/linguist/linguist/images/mac/punctuation.png b/src/linguist/linguist/images/mac/punctuation.png Binary files differnew file mode 100644 index 000000000..4719fc68f --- /dev/null +++ b/src/linguist/linguist/images/mac/punctuation.png diff --git a/src/linguist/linguist/images/mac/redo.png b/src/linguist/linguist/images/mac/redo.png Binary files differnew file mode 100644 index 000000000..8875bf246 --- /dev/null +++ b/src/linguist/linguist/images/mac/redo.png diff --git a/src/linguist/linguist/images/mac/searchfind.png b/src/linguist/linguist/images/mac/searchfind.png Binary files differnew file mode 100644 index 000000000..3561745f0 --- /dev/null +++ b/src/linguist/linguist/images/mac/searchfind.png diff --git a/src/linguist/linguist/images/mac/undo.png b/src/linguist/linguist/images/mac/undo.png Binary files differnew file mode 100644 index 000000000..a3bd5e0bf --- /dev/null +++ b/src/linguist/linguist/images/mac/undo.png diff --git a/src/linguist/linguist/images/mac/validateplacemarkers.png b/src/linguist/linguist/images/mac/validateplacemarkers.png Binary files differnew file mode 100644 index 000000000..18ccc4cbc --- /dev/null +++ b/src/linguist/linguist/images/mac/validateplacemarkers.png diff --git a/src/linguist/linguist/images/mac/whatsthis.png b/src/linguist/linguist/images/mac/whatsthis.png Binary files differnew file mode 100644 index 000000000..5b7078fff --- /dev/null +++ b/src/linguist/linguist/images/mac/whatsthis.png diff --git a/src/linguist/linguist/images/minus.png b/src/linguist/linguist/images/minus.png Binary files differnew file mode 100644 index 000000000..745b44572 --- /dev/null +++ b/src/linguist/linguist/images/minus.png diff --git a/src/linguist/linguist/images/plus.png b/src/linguist/linguist/images/plus.png Binary files differnew file mode 100644 index 000000000..ef43788e6 --- /dev/null +++ b/src/linguist/linguist/images/plus.png diff --git a/src/linguist/linguist/images/s_check_danger.png b/src/linguist/linguist/images/s_check_danger.png Binary files differnew file mode 100644 index 000000000..e10157768 --- /dev/null +++ b/src/linguist/linguist/images/s_check_danger.png diff --git a/src/linguist/linguist/images/s_check_empty.png b/src/linguist/linguist/images/s_check_empty.png Binary files differnew file mode 100644 index 000000000..759a41b6c --- /dev/null +++ b/src/linguist/linguist/images/s_check_empty.png diff --git a/src/linguist/linguist/images/s_check_obsolete.png b/src/linguist/linguist/images/s_check_obsolete.png Binary files differnew file mode 100644 index 000000000..b852b639f --- /dev/null +++ b/src/linguist/linguist/images/s_check_obsolete.png diff --git a/src/linguist/linguist/images/s_check_off.png b/src/linguist/linguist/images/s_check_off.png Binary files differnew file mode 100644 index 000000000..640b68972 --- /dev/null +++ b/src/linguist/linguist/images/s_check_off.png diff --git a/src/linguist/linguist/images/s_check_on.png b/src/linguist/linguist/images/s_check_on.png Binary files differnew file mode 100644 index 000000000..afcaf634d --- /dev/null +++ b/src/linguist/linguist/images/s_check_on.png diff --git a/src/linguist/linguist/images/s_check_warning.png b/src/linguist/linguist/images/s_check_warning.png Binary files differnew file mode 100644 index 000000000..f689c3303 --- /dev/null +++ b/src/linguist/linguist/images/s_check_warning.png diff --git a/src/linguist/linguist/images/splash.png b/src/linguist/linguist/images/splash.png Binary files differnew file mode 100644 index 000000000..0e99a1cf0 --- /dev/null +++ b/src/linguist/linguist/images/splash.png diff --git a/src/linguist/linguist/images/up.png b/src/linguist/linguist/images/up.png Binary files differnew file mode 100644 index 000000000..e43731221 --- /dev/null +++ b/src/linguist/linguist/images/up.png diff --git a/src/linguist/linguist/images/win/accelerator.png b/src/linguist/linguist/images/win/accelerator.png Binary files differnew file mode 100644 index 000000000..c423c12cf --- /dev/null +++ b/src/linguist/linguist/images/win/accelerator.png diff --git a/src/linguist/linguist/images/win/book.png b/src/linguist/linguist/images/win/book.png Binary files differnew file mode 100644 index 000000000..09ec4d33f --- /dev/null +++ b/src/linguist/linguist/images/win/book.png diff --git a/src/linguist/linguist/images/win/doneandnext.png b/src/linguist/linguist/images/win/doneandnext.png Binary files differnew file mode 100644 index 000000000..9d1d58d6a --- /dev/null +++ b/src/linguist/linguist/images/win/doneandnext.png diff --git a/src/linguist/linguist/images/win/editcopy.png b/src/linguist/linguist/images/win/editcopy.png Binary files differnew file mode 100644 index 000000000..1121b47d8 --- /dev/null +++ b/src/linguist/linguist/images/win/editcopy.png diff --git a/src/linguist/linguist/images/win/editcut.png b/src/linguist/linguist/images/win/editcut.png Binary files differnew file mode 100644 index 000000000..4b6c82c7a --- /dev/null +++ b/src/linguist/linguist/images/win/editcut.png diff --git a/src/linguist/linguist/images/win/editpaste.png b/src/linguist/linguist/images/win/editpaste.png Binary files differnew file mode 100644 index 000000000..ffab15aaf --- /dev/null +++ b/src/linguist/linguist/images/win/editpaste.png diff --git a/src/linguist/linguist/images/win/filenew.png b/src/linguist/linguist/images/win/filenew.png Binary files differnew file mode 100644 index 000000000..af5d12214 --- /dev/null +++ b/src/linguist/linguist/images/win/filenew.png diff --git a/src/linguist/linguist/images/win/fileopen.png b/src/linguist/linguist/images/win/fileopen.png Binary files differnew file mode 100644 index 000000000..fc6f17e97 --- /dev/null +++ b/src/linguist/linguist/images/win/fileopen.png diff --git a/src/linguist/linguist/images/win/filesave.png b/src/linguist/linguist/images/win/filesave.png Binary files differnew file mode 100644 index 000000000..8feec99be --- /dev/null +++ b/src/linguist/linguist/images/win/filesave.png diff --git a/src/linguist/linguist/images/win/next.png b/src/linguist/linguist/images/win/next.png Binary files differnew file mode 100644 index 000000000..8df4127a0 --- /dev/null +++ b/src/linguist/linguist/images/win/next.png diff --git a/src/linguist/linguist/images/win/nextunfinished.png b/src/linguist/linguist/images/win/nextunfinished.png Binary files differnew file mode 100644 index 000000000..636b9213b --- /dev/null +++ b/src/linguist/linguist/images/win/nextunfinished.png diff --git a/src/linguist/linguist/images/win/phrase.png b/src/linguist/linguist/images/win/phrase.png Binary files differnew file mode 100644 index 000000000..8ff952c51 --- /dev/null +++ b/src/linguist/linguist/images/win/phrase.png diff --git a/src/linguist/linguist/images/win/prev.png b/src/linguist/linguist/images/win/prev.png Binary files differnew file mode 100644 index 000000000..0780bc23d --- /dev/null +++ b/src/linguist/linguist/images/win/prev.png diff --git a/src/linguist/linguist/images/win/prevunfinished.png b/src/linguist/linguist/images/win/prevunfinished.png Binary files differnew file mode 100644 index 000000000..139d11b03 --- /dev/null +++ b/src/linguist/linguist/images/win/prevunfinished.png diff --git a/src/linguist/linguist/images/win/print.png b/src/linguist/linguist/images/win/print.png Binary files differnew file mode 100644 index 000000000..ba7c02dc1 --- /dev/null +++ b/src/linguist/linguist/images/win/print.png diff --git a/src/linguist/linguist/images/win/punctuation.png b/src/linguist/linguist/images/win/punctuation.png Binary files differnew file mode 100644 index 000000000..e2372a2ef --- /dev/null +++ b/src/linguist/linguist/images/win/punctuation.png diff --git a/src/linguist/linguist/images/win/redo.png b/src/linguist/linguist/images/win/redo.png Binary files differnew file mode 100644 index 000000000..686ad141c --- /dev/null +++ b/src/linguist/linguist/images/win/redo.png diff --git a/src/linguist/linguist/images/win/searchfind.png b/src/linguist/linguist/images/win/searchfind.png Binary files differnew file mode 100644 index 000000000..6ea35e930 --- /dev/null +++ b/src/linguist/linguist/images/win/searchfind.png diff --git a/src/linguist/linguist/images/win/undo.png b/src/linguist/linguist/images/win/undo.png Binary files differnew file mode 100644 index 000000000..c3b8c5136 --- /dev/null +++ b/src/linguist/linguist/images/win/undo.png diff --git a/src/linguist/linguist/images/win/validateplacemarkers.png b/src/linguist/linguist/images/win/validateplacemarkers.png Binary files differnew file mode 100644 index 000000000..cc127fde9 --- /dev/null +++ b/src/linguist/linguist/images/win/validateplacemarkers.png diff --git a/src/linguist/linguist/images/win/whatsthis.png b/src/linguist/linguist/images/win/whatsthis.png Binary files differnew file mode 100644 index 000000000..623cad687 --- /dev/null +++ b/src/linguist/linguist/images/win/whatsthis.png diff --git a/src/linguist/linguist/linguist.icns b/src/linguist/linguist/linguist.icns Binary files differnew file mode 100644 index 000000000..5918e001c --- /dev/null +++ b/src/linguist/linguist/linguist.icns diff --git a/src/linguist/linguist/linguist.ico b/src/linguist/linguist/linguist.ico Binary files differnew file mode 100644 index 000000000..5bbdc485b --- /dev/null +++ b/src/linguist/linguist/linguist.ico diff --git a/src/linguist/linguist/linguist.pro b/src/linguist/linguist/linguist.pro new file mode 100644 index 000000000..ce8d5854f --- /dev/null +++ b/src/linguist/linguist/linguist.pro @@ -0,0 +1,96 @@ +TEMPLATE = app +LANGUAGE = C++ +DESTDIR = ../../../bin + +QT += xml + +CONFIG += qt \ + warn_on \ + uitools + +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII +build_all:!build_pass { + CONFIG -= build_all + CONFIG += release +} + +include(../shared/formats.pri) + +DEFINES += QFORMINTERNAL_NAMESPACE +INCLUDEPATH += ../../designer/src/uitools +INCLUDEPATH += ../../designer/src/lib/uilib + +SOURCES += \ + batchtranslationdialog.cpp \ + errorsview.cpp \ + finddialog.cpp \ + formpreviewview.cpp \ + globals.cpp \ + main.cpp \ + mainwindow.cpp \ + messageeditor.cpp \ + messageeditorwidgets.cpp \ + messagehighlighter.cpp \ + messagemodel.cpp \ + phrasebookbox.cpp \ + phrase.cpp \ + phrasemodel.cpp \ + phraseview.cpp \ + printout.cpp \ + recentfiles.cpp \ + sourcecodeview.cpp \ + statistics.cpp \ + translatedialog.cpp \ + translationsettingsdialog.cpp \ + ../shared/simtexth.cpp + +HEADERS += \ + batchtranslationdialog.h \ + errorsview.h \ + finddialog.h \ + formpreviewview.h \ + globals.h \ + mainwindow.h \ + messageeditor.h \ + messageeditorwidgets.h \ + messagehighlighter.h \ + messagemodel.h \ + phrasebookbox.h \ + phrase.h \ + phrasemodel.h \ + phraseview.h \ + printout.h \ + recentfiles.h \ + sourcecodeview.h \ + statistics.h \ + translatedialog.h \ + translationsettingsdialog.h \ + ../shared/simtexth.h + +contains(QT_PRODUCT, OpenSource.*):DEFINES *= QT_OPENSOURCE +DEFINES += QT_KEYWORDS +TARGET = linguist +win32:RC_FILE = linguist.rc +mac { + static:CONFIG -= global_init_link_order + ICON = linguist.icns + TARGET = Linguist + QMAKE_INFO_PLIST=Info_mac.plist +} +PROJECTNAME = Qt \ + Linguist +target.path = $$[QT_INSTALL_BINS] +INSTALLS += target +phrasebooks.path = $$[QT_INSTALL_DATA]/phrasebooks + +# ## will this work on windows? +phrasebooks.files = $$QT_SOURCE_TREE/tools/linguist/phrasebooks/* +INSTALLS += phrasebooks +FORMS += statistics.ui \ + phrasebookbox.ui \ + batchtranslation.ui \ + translatedialog.ui \ + mainwindow.ui \ + translationsettings.ui \ + finddialog.ui +RESOURCES += linguist.qrc diff --git a/src/linguist/linguist/linguist.qrc b/src/linguist/linguist/linguist.qrc new file mode 100644 index 000000000..b70b9cd52 --- /dev/null +++ b/src/linguist/linguist/linguist.qrc @@ -0,0 +1,57 @@ +<RCC> + <qresource prefix="/"> + <file>images/appicon.png</file> + <file>images/mac/accelerator.png</file> + <file>images/mac/book.png</file> + <file>images/mac/doneandnext.png</file> + <file>images/mac/editcopy.png</file> + <file>images/mac/editcut.png</file> + <file>images/mac/editpaste.png</file> + <file>images/mac/fileopen.png</file> + <file>images/mac/filesave.png</file> + <file>images/mac/next.png</file> + <file>images/mac/nextunfinished.png</file> + <file>images/mac/phrase.png</file> + <file>images/mac/prev.png</file> + <file>images/mac/prevunfinished.png</file> + <file>images/mac/print.png</file> + <file>images/mac/punctuation.png</file> + <file>images/mac/redo.png</file> + <file>images/mac/searchfind.png</file> + <file>images/mac/undo.png</file> + <file>images/mac/validateplacemarkers.png</file> + <file>images/mac/whatsthis.png</file> + <file>images/s_check_danger.png</file> + <file>images/s_check_empty.png</file> + <file>images/s_check_obsolete.png</file> + <file>images/s_check_off.png</file> + <file>images/s_check_on.png</file> + <file>images/s_check_warning.png</file> + <file>images/splash.png</file> + <file>images/up.png</file> + <file>images/down.png</file> + <file>images/editdelete.png</file> + <file>images/minus.png</file> + <file>images/plus.png</file> + <file>images/win/accelerator.png</file> + <file>images/win/book.png</file> + <file>images/win/doneandnext.png</file> + <file>images/win/editcopy.png</file> + <file>images/win/editcut.png</file> + <file>images/win/editpaste.png</file> + <file>images/win/fileopen.png</file> + <file>images/win/filesave.png</file> + <file>images/win/next.png</file> + <file>images/win/nextunfinished.png</file> + <file>images/win/phrase.png</file> + <file>images/win/prev.png</file> + <file>images/win/prevunfinished.png</file> + <file>images/win/print.png</file> + <file>images/win/punctuation.png</file> + <file>images/win/redo.png</file> + <file>images/win/searchfind.png</file> + <file>images/win/undo.png</file> + <file>images/win/validateplacemarkers.png</file> + <file>images/win/whatsthis.png</file> + </qresource> +</RCC> diff --git a/src/linguist/linguist/linguist.rc b/src/linguist/linguist/linguist.rc new file mode 100644 index 000000000..375f02a27 --- /dev/null +++ b/src/linguist/linguist/linguist.rc @@ -0,0 +1,32 @@ +#include "winver.h" + +IDI_ICON1 ICON DISCARDABLE "linguist.ico" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGS 0x0L + FILEFLAGSMASK 0x3fL + FILEOS 0x00040004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "CompanyName", "Nokia Corporation and/or its subsidiary(-ies)" + VALUE "FileDescription", "Qt Linguist" + VALUE "FileVersion", "1.0.0.0" + VALUE "LegalCopyright", "Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)." + VALUE "InternalName", "linguist" + VALUE "OriginalFilename", "linguist.exe" + VALUE "ProductName", "Qt Linguist" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END diff --git a/src/linguist/linguist/main.cpp b/src/linguist/linguist/main.cpp new file mode 100644 index 000000000..c1388e198 --- /dev/null +++ b/src/linguist/linguist/main.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "globals.h" + +#include <QtCore/QFile> +#include <QtCore/QLibraryInfo> +#include <QtCore/QLocale> +#include <QtCore/QSettings> +#include <QtCore/QTextCodec> +#include <QtCore/QTranslator> + +#include <QtGui/QApplication> +#include <QtGui/QDesktopWidget> +#include <QtGui/QPixmap> +#include <QtGui/QSplashScreen> + +QT_USE_NAMESPACE + +int main(int argc, char **argv) +{ + Q_INIT_RESOURCE(linguist); + + QApplication app(argc, argv); + QApplication::setOverrideCursor(Qt::WaitCursor); + + QStringList files; + QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath); + QStringList args = app.arguments(); + + for (int i = 1; i < args.count(); ++i) { + QString argument = args.at(i); + if (argument == QLatin1String("-resourcedir")) { + if (i + 1 < args.count()) { + resourceDir = QFile::decodeName(args.at(++i).toLocal8Bit()); + } else { + // issue a warning + } + } else if (!files.contains(argument)) { + files.append(argument); + } + } + + QTranslator translator; + QTranslator qtTranslator; + QString sysLocale = QLocale::system().name(); + if (translator.load(QLatin1String("linguist_") + sysLocale, resourceDir)) { + app.installTranslator(&translator); + if (qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir)) + app.installTranslator(&qtTranslator); + else + app.removeTranslator(&translator); + } + + app.setOrganizationName(QLatin1String("Trolltech")); + app.setApplicationName(QLatin1String("Linguist")); + + QSettings config; + + QWidget tmp; + tmp.restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray()); + + QSplashScreen *splash = 0; + int screenId = QApplication::desktop()->screenNumber(tmp.geometry().center()); + splash = new QSplashScreen(QApplication::desktop()->screen(screenId), + QPixmap(QLatin1String(":/images/splash.png"))); + if (QApplication::desktop()->isVirtualDesktop()) { + QRect srect(0, 0, splash->width(), splash->height()); + splash->move(QApplication::desktop()->availableGeometry(screenId).center() - srect.center()); + } + splash->setAttribute(Qt::WA_DeleteOnClose); + splash->show(); + + MainWindow mw; + mw.show(); + splash->finish(&mw); + QApplication::restoreOverrideCursor(); + + mw.openFiles(files, true); + + return app.exec(); +} 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 diff --git a/src/linguist/linguist/mainwindow.h b/src/linguist/linguist/mainwindow.h new file mode 100644 index 000000000..fe9daf264 --- /dev/null +++ b/src/linguist/linguist/mainwindow.h @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "phrase.h" +#include "ui_mainwindow.h" +#include "recentfiles.h" +#include "messagemodel.h" + +#include <QtCore/QHash> +#include <QtCore/QLocale> + +#include <QtGui/QMainWindow> + +QT_BEGIN_NAMESPACE + +class QPixmap; +class QAction; +class QDialog; +class QLabel; +class QMenu; +class QPrinter; +class QProcess; +class QIcon; +class QSortFilterProxyModel; +class QStackedWidget; +class QTableView; +class QTreeView; + +class BatchTranslationDialog; +class ErrorsView; +class FindDialog; +class FocusWatcher; +class FormPreviewView; +class MessageEditor; +class PhraseView; +class SourceCodeView; +class Statistics; +class TranslateDialog; +class TranslationSettingsDialog; + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + enum {PhraseCloseMenu, PhraseEditMenu, PhrasePrintMenu}; + + MainWindow(); + ~MainWindow(); + + bool openFiles(const QStringList &names, bool readWrite = true); + static RecentFiles &recentFiles(); + static const QString &resourcePrefix(); + static QString friendlyString(const QString &str); + +protected: + void readConfig(); + void writeConfig(); + void closeEvent(QCloseEvent *); + bool eventFilter(QObject *object, QEvent *event); + +private slots: + void doneAndNext(); + void prev(); + void next(); + void recentFileActivated(QAction *action); + void setupRecentFilesMenu(); + void open(); + void openAux(); + void saveAll(); + void save(); + void saveAs(); + void releaseAll(); + void release(); + void releaseAs(); + void print(); + void closeFile(); + bool closeAll(); + void findAgain(); + void showTranslateDialog(); + void showBatchTranslateDialog(); + void showTranslationSettings(); + void updateTranslateHit(bool &hit); + void translate(int mode); + void newPhraseBook(); + void openPhraseBook(); + void closePhraseBook(QAction *action); + void editPhraseBook(QAction *action); + void printPhraseBook(QAction *action); + void addToPhraseBook(); + void manual(); + void resetSorting(); + void about(); + void aboutQt(); + + void updateViewMenu(); + void fileAboutToShow(); + void editAboutToShow(); + + void showContextDock(); + void showMessagesDock(); + void showPhrasesDock(); + void showSourceCodeDock(); + void showErrorDock(); + + void setupPhrase(); + bool maybeSaveAll(); + bool maybeSave(int model); + void updateProgress(); + void maybeUpdateStatistics(const MultiDataIndex &); + void translationChanged(const MultiDataIndex &); + void updateCaption(); + void updateLatestModel(const QModelIndex &index); + void selectedContextChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex); + void selectedMessageChanged(const QModelIndex &sortedIndex, const QModelIndex &oldIndex); + + // To synchronize from the message editor to the model ... + void updateTranslation(const QStringList &translations); + void updateTranslatorComment(const QString &comment); + + void updateActiveModel(int); + void refreshItemViews(); + void toggleFinished(const QModelIndex &index); + void prevUnfinished(); + void nextUnfinished(); + void findNext(const QString &text, DataModel::FindLocation where, bool matchCase, bool ignoreAccelerators); + void revalidate(); + void toggleStatistics(); + void onWhatsThis(); + void updatePhraseDicts(); + void updatePhraseDict(int model); + +private: + QModelIndex nextContext(const QModelIndex &index) const; + QModelIndex prevContext(const QModelIndex &index) const; + QModelIndex nextMessage(const QModelIndex ¤tIndex, bool checkUnfinished = false) const; + QModelIndex prevMessage(const QModelIndex ¤tIndex, bool checkUnfinished = false) const; + bool next(bool checkUnfinished); + bool prev(bool checkUnfinished); + + void updateStatistics(); + void initViewHeaders(); + void modelCountChanged(); + void setupMenuBar(); + void setupToolBars(); + void setCurrentMessage(const QModelIndex &index); + void setCurrentMessage(const QModelIndex &index, int model); + QModelIndex setMessageViewRoot(const QModelIndex &index); + QModelIndex currentContextIndex() const; + QModelIndex currentMessageIndex() const; + PhraseBook *openPhraseBook(const QString &name); + bool isPhraseBookOpen(const QString &name); + bool savePhraseBook(QString *name, PhraseBook &pb); + bool maybeSavePhraseBook(PhraseBook *phraseBook); + bool closePhraseBooks(); + QStringList pickTranslationFiles(); + void showTranslationSettings(int model); + void updateLatestModel(int model); + void updatePhraseBookActions(); + void updatePhraseDictInternal(int model); + void releaseInternal(int model); + void saveInternal(int model); + + QPrinter *printer(); + + // FIXME: move to DataModel + void updateDanger(const MultiDataIndex &index, bool verbose); + + bool searchItem(const QString &searchWhat); + + QProcess *m_assistantProcess; + QTreeView *m_contextView; + QTreeView *m_messageView; + MultiDataModel *m_dataModel; + MessageModel *m_messageModel; + QSortFilterProxyModel *m_sortedContextsModel; + QSortFilterProxyModel *m_sortedMessagesModel; + MessageEditor *m_messageEditor; + PhraseView *m_phraseView; + QStackedWidget *m_sourceAndFormView; + SourceCodeView *m_sourceCodeView; + FormPreviewView *m_formPreviewView; + ErrorsView *m_errorsView; + QLabel *m_progressLabel; + QLabel *m_modifiedLabel; + FocusWatcher *m_focusWatcher; + QString m_phraseBookDir; + // model : keyword -> list of appropriate phrases in the phrasebooks + QList<QHash<QString, QList<Phrase *> > > m_phraseDict; + QList<PhraseBook *> m_phraseBooks; + QMap<QAction *, PhraseBook *> m_phraseBookMenu[3]; + QPrinter *m_printer; + + FindDialog *m_findDialog; + QString m_findText; + Qt::CaseSensitivity m_findMatchCase; + bool m_findIgnoreAccelerators; + DataModel::FindLocation m_findWhere; + DataModel::FindLocation m_foundWhere; + + TranslateDialog *m_translateDialog; + QString m_latestFindText; + int m_latestCaseSensitivity; + int m_remainingCount; + int m_hitCount; + + BatchTranslationDialog *m_batchTranslateDialog; + TranslationSettingsDialog *m_translationSettingsDialog; + + bool m_settingCurrentMessage; + int m_fileActiveModel; + int m_editActiveModel; + MultiDataIndex m_currentIndex; + + QDockWidget *m_contextDock; + QDockWidget *m_messagesDock; + QDockWidget *m_phrasesDock; + QDockWidget *m_sourceAndFormDock; + QDockWidget *m_errorsDock; + + Ui::MainWindow m_ui; // menus and actions + Statistics *m_statistics; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/linguist/linguist/mainwindow.ui b/src/linguist/linguist/mainwindow.ui new file mode 100644 index 000000000..3453740ec --- /dev/null +++ b/src/linguist/linguist/mainwindow.ui @@ -0,0 +1,892 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <comment>********************************************************************* +** +** 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$ +** +*********************************************************************</comment> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>673</width> + <height>461</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"/> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>673</width> + <height>28</height> + </rect> + </property> + <widget class="QMenu" name="menuPhrases"> + <property name="title"> + <string>&Phrases</string> + </property> + <widget class="QMenu" name="menuClosePhraseBook"> + <property name="title"> + <string>&Close Phrase Book</string> + </property> + </widget> + <widget class="QMenu" name="menuEditPhraseBook"> + <property name="title"> + <string>&Edit Phrase Book</string> + </property> + </widget> + <widget class="QMenu" name="menuPrintPhraseBook"> + <property name="title"> + <string>&Print Phrase Book</string> + </property> + </widget> + <addaction name="actionNewPhraseBook"/> + <addaction name="actionOpenPhraseBook"/> + <addaction name="menuClosePhraseBook"/> + <addaction name="separator"/> + <addaction name="menuEditPhraseBook"/> + <addaction name="menuPrintPhraseBook"/> + <addaction name="actionAddToPhraseBook"/> + </widget> + <widget class="QMenu" name="menuValidation"> + <property name="title"> + <string>V&alidation</string> + </property> + <addaction name="actionAccelerators"/> + <addaction name="actionEndingPunctuation"/> + <addaction name="actionPhraseMatches"/> + <addaction name="actionPlaceMarkerMatches"/> + </widget> + <widget class="QMenu" name="menuView"> + <property name="title"> + <string>&View</string> + </property> + <widget class="QMenu" name="menuViewViews"> + <property name="title"> + <string>Vie&ws</string> + </property> + </widget> + <widget class="QMenu" name="menuToolbars"> + <property name="title"> + <string>&Toolbars</string> + </property> + </widget> + <addaction name="actionResetSorting"/> + <addaction name="actionDisplayGuesses"/> + <addaction name="actionStatistics"/> + <addaction name="actionLengthVariants"/> + <addaction name="separator"/> + <addaction name="menuToolbars"/> + <addaction name="menuViewViews"/> + </widget> + <widget class="QMenu" name="menuHelp"> + <property name="title"> + <string>&Help</string> + </property> + <addaction name="actionManual"/> + <addaction name="actionAbout"/> + <addaction name="actionAboutQt"/> + <addaction name="actionWhatsThis"/> + </widget> + <widget class="QMenu" name="menuTranslation"> + <property name="title"> + <string>&Translation</string> + </property> + <addaction name="actionPrevUnfinished"/> + <addaction name="actionNextUnfinished"/> + <addaction name="actionPrev"/> + <addaction name="actionNext"/> + <addaction name="actionDoneAndNext"/> + <addaction name="actionBeginFromSource"/> + </widget> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>&File</string> + </property> + <widget class="QMenu" name="menuRecentlyOpenedFiles"> + <property name="title"> + <string>Recently Opened &Files</string> + </property> + </widget> + <addaction name="actionOpen"/> + <addaction name="actionOpenAux"/> + <addaction name="menuRecentlyOpenedFiles"/> + <addaction name="separator"/> + <addaction name="actionSaveAll"/> + <addaction name="actionSave"/> + <addaction name="actionSaveAs"/> + <addaction name="actionReleaseAll"/> + <addaction name="actionRelease"/> + <addaction name="actionReleaseAs"/> + <addaction name="separator"/> + <addaction name="actionPrint"/> + <addaction name="separator"/> + <addaction name="actionCloseAll"/> + <addaction name="actionClose"/> + <addaction name="separator"/> + <addaction name="actionExit"/> + </widget> + <widget class="QMenu" name="menuEdit"> + <property name="title"> + <string>&Edit</string> + </property> + <addaction name="actionUndo"/> + <addaction name="actionRedo"/> + <addaction name="separator"/> + <addaction name="actionCut"/> + <addaction name="actionCopy"/> + <addaction name="actionPaste"/> + <addaction name="actionSelectAll"/> + <addaction name="separator"/> + <addaction name="actionFind"/> + <addaction name="actionFindNext"/> + <addaction name="actionSearchAndTranslate"/> + <addaction name="actionBatchTranslation"/> + <addaction name="actionTranslationFileSettings"/> + </widget> + <addaction name="menuFile"/> + <addaction name="menuEdit"/> + <addaction name="menuTranslation"/> + <addaction name="menuValidation"/> + <addaction name="menuPhrases"/> + <addaction name="menuView"/> + <addaction name="menuHelp"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <action name="actionOpen"> + <property name="text"> + <string>&Open...</string> + </property> + <property name="whatsThis"> + <string>Open a Qt translation source file (TS file) for editing</string> + </property> + <property name="shortcut"> + <string>Ctrl+O</string> + </property> + </action> + <action name="actionExit"> + <property name="text"> + <string>E&xit</string> + </property> + <property name="statusTip"> + <string/> + </property> + <property name="whatsThis"> + <string>Close this window and exit.</string> + </property> + <property name="shortcut"> + <string>Ctrl+Q</string> + </property> + <property name="menuRole"> + <enum>QAction::QuitRole</enum> + </property> + </action> + <action name="actionSave"> + <property name="text"> + <string>Save</string> + </property> + <property name="whatsThis"> + <string>Save changes made to this Qt translation source file</string> + </property> + </action> + <action name="actionSaveAs"> + <property name="text"> + <string>Save &As...</string> + </property> + <property name="iconText"> + <string>Save As...</string> + </property> + <property name="whatsThis"> + <string>Save changes made to this Qt translation source file into a new file.</string> + </property> + </action> + <action name="actionRelease"> + <property name="text"> + <string>Release</string> + </property> + <property name="whatsThis"> + <string>Create a Qt message file suitable for released applications from the current message file.</string> + </property> + </action> + <action name="actionPrint"> + <property name="text"> + <string>&Print...</string> + </property> + <property name="whatsThis"> + <string>Print a list of all the translation units in the current translation source file.</string> + </property> + <property name="shortcut"> + <string>Ctrl+P</string> + </property> + </action> + <action name="actionUndo"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Undo</string> + </property> + <property name="whatsThis"> + <string>Undo the last editing operation performed on the current translation.</string> + </property> + <property name="shortcut"> + <string>Ctrl+Z</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionRedo"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Redo</string> + </property> + <property name="whatsThis"> + <string>Redo an undone editing operation performed on the translation.</string> + </property> + <property name="shortcut"> + <string>Ctrl+Y</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionCut"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Cu&t</string> + </property> + <property name="whatsThis"> + <string>Copy the selected translation text to the clipboard and deletes it.</string> + </property> + <property name="shortcut"> + <string>Ctrl+X</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionCopy"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Copy</string> + </property> + <property name="whatsThis"> + <string>Copy the selected translation text to the clipboard.</string> + </property> + <property name="shortcut"> + <string>Ctrl+C</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionPaste"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Paste</string> + </property> + <property name="whatsThis"> + <string>Paste the clipboard text into the translation.</string> + </property> + <property name="shortcut"> + <string>Ctrl+V</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionSelectAll"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Select &All</string> + </property> + <property name="whatsThis"> + <string>Select the whole translation text.</string> + </property> + <property name="shortcut"> + <string>Ctrl+A</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionFind"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Find...</string> + </property> + <property name="whatsThis"> + <string>Search for some text in the translation source file.</string> + </property> + <property name="shortcut"> + <string>Ctrl+F</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionFindNext"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Find &Next</string> + </property> + <property name="whatsThis"> + <string>Continue the search where it was left.</string> + </property> + <property name="shortcut"> + <string>F3</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionPrevUnfinished"> + <property name="text"> + <string>&Prev Unfinished</string> + </property> + <property name="toolTip"> + <string>Previous unfinished item</string> + </property> + <property name="whatsThis"> + <string>Move to the previous unfinished item.</string> + </property> + <property name="shortcut"> + <string>Ctrl+K</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionNextUnfinished"> + <property name="text"> + <string>&Next Unfinished</string> + </property> + <property name="toolTip"> + <string>Next unfinished item</string> + </property> + <property name="whatsThis"> + <string>Move to the next unfinished item.</string> + </property> + <property name="shortcut"> + <string>Ctrl+J</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionPrev"> + <property name="text"> + <string>P&rev</string> + </property> + <property name="toolTip"> + <string>Move to previous item</string> + </property> + <property name="whatsThis"> + <string>Move to the previous item.</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+K</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionNext"> + <property name="text"> + <string>Ne&xt</string> + </property> + <property name="toolTip"> + <string>Next item</string> + </property> + <property name="whatsThis"> + <string>Move to the next item.</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+J</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionDoneAndNext"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Done and Next</string> + </property> + <property name="toolTip"> + <string>Mark item as done and move to the next unfinished item</string> + </property> + <property name="whatsThis"> + <string>Mark this item as done and move to the next unfinished item.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionBeginFromSource"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Copy from source text</string> + </property> + <property name="iconText"> + <string>Copy from source text</string> + </property> + <property name="toolTip"> + <string>Copies the source text into the translation field</string> + </property> + <property name="whatsThis"> + <string>Copies the source text into the translation field.</string> + </property> + <property name="shortcut"> + <string>Ctrl+B</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionAccelerators"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>&Accelerators</string> + </property> + <property name="toolTip"> + <string>Toggle the validity check of accelerators</string> + </property> + <property name="whatsThis"> + <string>Toggle the validity check of accelerators, i.e. whether the number of ampersands in the source and translation text is the same. If the check fails, a message is shown in the warnings window.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionEndingPunctuation"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>&Ending Punctuation</string> + </property> + <property name="toolTip"> + <string>Toggle the validity check of ending punctuation</string> + </property> + <property name="whatsThis"> + <string>Toggle the validity check of ending punctuation. If the check fails, a message is shown in the warnings window.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionPhraseMatches"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>&Phrase matches</string> + </property> + <property name="toolTip"> + <string>Toggle checking that phrase suggestions are used</string> + </property> + <property name="whatsThis"> + <string>Toggle checking that phrase suggestions are used. If the check fails, a message is shown in the warnings window.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionPlaceMarkerMatches"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Place &Marker Matches</string> + </property> + <property name="toolTip"> + <string>Toggle the validity check of place markers</string> + </property> + <property name="whatsThis"> + <string>Toggle the validity check of place markers, i.e. whether %1, %2, ... are used consistently in the source text and translation text. If the check fails, a message is shown in the warnings window.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionNewPhraseBook"> + <property name="text"> + <string>&New Phrase Book...</string> + </property> + <property name="whatsThis"> + <string>Create a new phrase book.</string> + </property> + <property name="shortcut"> + <string>Ctrl+N</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionOpenPhraseBook"> + <property name="text"> + <string>&Open Phrase Book...</string> + </property> + <property name="whatsThis"> + <string>Open a phrase book to assist translation.</string> + </property> + <property name="shortcut"> + <string>Ctrl+H</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionResetSorting"> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Reset Sorting</string> + </property> + <property name="whatsThis"> + <string>Sort the items back in the same order as in the message file.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionDisplayGuesses"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>&Display guesses</string> + </property> + <property name="whatsThis"> + <string>Set whether or not to display translation guesses.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionStatistics"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>&Statistics</string> + </property> + <property name="whatsThis"> + <string>Display translation statistics.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionManual"> + <property name="text"> + <string>&Manual</string> + </property> + <property name="whatsThis"> + <string/> + </property> + <property name="shortcut"> + <string>F1</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionAbout"> + <property name="text"> + <string>About Qt Linguist</string> + </property> + <property name="menuRole"> + <enum>QAction::AboutRole</enum> + </property> + </action> + <action name="actionAboutQt"> + <property name="text"> + <string>About Qt</string> + </property> + <property name="whatsThis"> + <string>Display information about the Qt toolkit by Nokia.</string> + </property> + <property name="menuRole"> + <enum>QAction::AboutQtRole</enum> + </property> + </action> + <action name="actionWhatsThis"> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="text"> + <string>&What's This?</string> + </property> + <property name="iconText"> + <string>What's This?</string> + </property> + <property name="toolTip"> + <string>What's This?</string> + </property> + <property name="whatsThis"> + <string>Enter What's This? mode.</string> + </property> + <property name="shortcut"> + <string>Shift+F1</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionSearchAndTranslate"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Search And Translate...</string> + </property> + <property name="whatsThis"> + <string>Replace the translation on all entries that matches the search source text.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionBatchTranslation"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Batch Translation...</string> + </property> + <property name="whatsThis"> + <string>Batch translate all entries using the information in the phrase books.</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionReleaseAs"> + <property name="text"> + <string>Release As...</string> + </property> + <property name="iconText"> + <string>Release As...</string> + </property> + <property name="whatsThis"> + <string>Create a Qt message file suitable for released applications from the current message file. The filename will automatically be determined from the name of the TS file.</string> + </property> + </action> + <action name="actionFile"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>File</string> + </property> + </action> + <action name="actionEdit"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>Edit</string> + </property> + </action> + <action name="actionTranslation"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>Translation</string> + </property> + </action> + <action name="actionValidation"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>Validation</string> + </property> + </action> + <action name="actionHelp"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>Help</string> + </property> + </action> + <action name="actionPreviewForm"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Open/Refresh Form &Preview</string> + </property> + <property name="iconText"> + <string>Form Preview Tool</string> + </property> + <property name="toolTip"> + <string>Form Preview Tool</string> + </property> + <property name="shortcut"> + <string>F5</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionTranslationFileSettings"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Translation File &Settings...</string> + </property> + <property name="menuRole"> + <enum>QAction::NoRole</enum> + </property> + </action> + <action name="actionAddToPhraseBook"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Add to Phrase Book</string> + </property> + <property name="shortcut"> + <string>Ctrl+T</string> + </property> + </action> + <action name="actionOpenAux"> + <property name="text"> + <string>Open Read-O&nly...</string> + </property> + </action> + <action name="actionSaveAll"> + <property name="text"> + <string>&Save All</string> + </property> + <property name="shortcut"> + <string>Ctrl+S</string> + </property> + </action> + <action name="actionReleaseAll"> + <property name="text"> + <string>&Release All</string> + </property> + </action> + <action name="actionClose"> + <property name="text"> + <string>Close</string> + </property> + </action> + <action name="actionCloseAll"> + <property name="text"> + <string>&Close All</string> + </property> + <property name="shortcut"> + <string>Ctrl+W</string> + </property> + </action> + <action name="actionLengthVariants"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Length Variants</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/linguist/linguist/messageeditor.cpp b/src/linguist/linguist/messageeditor.cpp new file mode 100644 index 000000000..3a705c1a7 --- /dev/null +++ b/src/linguist/linguist/messageeditor.cpp @@ -0,0 +1,880 @@ +/**************************************************************************** +** +** 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 MessageEditor + + This is the right panel of the main window. +*/ + +#include "messageeditor.h" +#include "messageeditorwidgets.h" +#include "simtexth.h" +#include "phrasemodel.h" + +#include <QApplication> +#include <QBoxLayout> +#include <QClipboard> +#include <QDebug> +#include <QDockWidget> +#include <QHeaderView> +#include <QKeyEvent> +#include <QMainWindow> +#include <QPainter> +#include <QTreeView> +#include <QVBoxLayout> + +QT_BEGIN_NAMESPACE + +#ifdef NEVER_TRUE +// Allow translators to provide localized names for QLocale::languageToString +// At least the own language should be translated ... This is a "hack" until +// functionality is provided within Qt (see task 196275). +static const char * language_strings[] = +{ + QT_TRANSLATE_NOOP("MessageEditor", "Russian"), + QT_TRANSLATE_NOOP("MessageEditor", "German"), + QT_TRANSLATE_NOOP("MessageEditor", "Japanese"), + QT_TRANSLATE_NOOP("MessageEditor", "French"), + QT_TRANSLATE_NOOP("MessageEditor", "Polish"), + QT_TRANSLATE_NOOP("MessageEditor", "Chinese") +}; +#endif + +/* + MessageEditor class impl. + + Handles layout of dock windows and the editor page. +*/ +MessageEditor::MessageEditor(MultiDataModel *dataModel, QMainWindow *parent) + : QScrollArea(parent->centralWidget()), + m_dataModel(dataModel), + m_currentModel(-1), + m_currentNumerus(-1), + m_lengthVariants(false), + m_undoAvail(false), + m_redoAvail(false), + m_cutAvail(false), + m_copyAvail(false), + m_selectionHolder(0), + m_focusWidget(0) +{ + setObjectName(QLatin1String("scroll area")); + + QPalette p; + p.setBrush(QPalette::Window, p.brush(QPalette::Active, QPalette::Base)); + setPalette(p); + + setupEditorPage(); + + // Signals + connect(qApp->clipboard(), SIGNAL(dataChanged()), + SLOT(clipboardChanged())); + connect(m_dataModel, SIGNAL(modelAppended()), + SLOT(messageModelAppended())); + connect(m_dataModel, SIGNAL(modelDeleted(int)), + SLOT(messageModelDeleted(int))); + connect(m_dataModel, SIGNAL(allModelsDeleted()), + SLOT(allModelsDeleted())); + connect(m_dataModel, SIGNAL(languageChanged(int)), + SLOT(setTargetLanguage(int))); + + m_tabOrderTimer.setSingleShot(true); + connect(&m_tabOrderTimer, SIGNAL(timeout()), SLOT(reallyFixTabOrder())); + + clipboardChanged(); + + setWhatsThis(tr("This whole panel allows you to view and edit " + "the translation of some source text.")); + showNothing(); +} + +void MessageEditor::setupEditorPage() +{ + QFrame *editorPage = new QFrame; + editorPage->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + m_source = new FormWidget(tr("Source text"), false); + m_source->setHideWhenEmpty(true); + m_source->setWhatsThis(tr("This area shows the source text.")); + connect(m_source, SIGNAL(selectionChanged(QTextEdit*)), + SLOT(selectionChanged(QTextEdit*))); + + m_pluralSource = new FormWidget(tr("Source text (Plural)"), false); + m_pluralSource->setHideWhenEmpty(true); + m_pluralSource->setWhatsThis(tr("This area shows the plural form of the source text.")); + connect(m_pluralSource, SIGNAL(selectionChanged(QTextEdit*)), + SLOT(selectionChanged(QTextEdit*))); + + m_commentText = new FormWidget(tr("Developer comments"), false); + m_commentText->setHideWhenEmpty(true); + m_commentText->setObjectName(QLatin1String("comment/context view")); + m_commentText->setWhatsThis(tr("This area shows a comment that" + " may guide you, and the context in which the text" + " occurs.") ); + + QBoxLayout *subLayout = new QVBoxLayout; + + subLayout->setMargin(5); + subLayout->addWidget(m_source); + subLayout->addWidget(m_pluralSource); + subLayout->addWidget(m_commentText); + + m_layout = new QVBoxLayout; + m_layout->setSpacing(2); + m_layout->setMargin(2); + m_layout->addLayout(subLayout); + m_layout->addStretch(1); + editorPage->setLayout(m_layout); + + setWidget(editorPage); + setWidgetResizable(true); +} + +QPalette MessageEditor::paletteForModel(int model) const +{ + QBrush brush = m_dataModel->brushForModel(model); + QPalette pal; + + if (m_dataModel->isModelWritable(model)) { + pal.setBrush(QPalette::Window, brush); + } else { + QPixmap pm(brush.texture().size()); + pm.fill(); + QPainter p(&pm); + p.fillRect(brush.texture().rect(), brush); + pal.setBrush(QPalette::Window, pm); + } + return pal; +} + +void MessageEditor::messageModelAppended() +{ + int model = m_editors.size(); + m_editors.append(MessageEditorData()); + MessageEditorData &ed = m_editors.last(); + ed.pluralEditMode = false; + ed.fontSize = font().pointSize(); + ed.container = new QWidget; + if (model > 0) { + ed.container->setPalette(paletteForModel(model)); + ed.container->setAutoFillBackground(true); + if (model == 1) { + m_editors[0].container->setPalette(paletteForModel(0)); + m_editors[0].container->setAutoFillBackground(true); + } + } + bool writable = m_dataModel->isModelWritable(model); + ed.transCommentText = new FormWidget(QString(), true); + ed.transCommentText->setEditingEnabled(writable); + ed.transCommentText->setHideWhenEmpty(!writable); + ed.transCommentText->setWhatsThis(tr("Here you can enter comments for your own use." + " They have no effect on the translated applications.") ); + ed.transCommentText->getEditor()->installEventFilter(this); + connect(ed.transCommentText, SIGNAL(selectionChanged(QTextEdit*)), + SLOT(selectionChanged(QTextEdit*))); + connect(ed.transCommentText, SIGNAL(textChanged(QTextEdit*)), + SLOT(emitTranslatorCommentChanged(QTextEdit*))); + connect(ed.transCommentText, SIGNAL(textChanged(QTextEdit*)), SLOT(resetHoverSelection())); + connect(ed.transCommentText, SIGNAL(cursorPositionChanged()), SLOT(resetHoverSelection())); + fixTabOrder(); + QBoxLayout *box = new QVBoxLayout(ed.container); + box->setMargin(5); + box->addWidget(ed.transCommentText); + box->addSpacing(ed.transCommentText->getEditor()->fontMetrics().height() / 2); + m_layout->addWidget(ed.container); + setTargetLanguage(model); +} + +void MessageEditor::allModelsDeleted() +{ + foreach (const MessageEditorData &med, m_editors) + med.container->deleteLater(); + m_editors.clear(); + m_currentModel = -1; + // Do not emit activeModelChanged() - the main window will refresh anyway + m_currentNumerus = -1; + showNothing(); +} + +void MessageEditor::messageModelDeleted(int model) +{ + m_editors[model].container->deleteLater(); + m_editors.removeAt(model); + if (model <= m_currentModel) { + if (model < m_currentModel || m_currentModel == m_editors.size()) + --m_currentModel; + // Do not emit activeModelChanged() - the main window will refresh anyway + if (m_currentModel >= 0) { + if (m_currentNumerus >= m_editors[m_currentModel].transTexts.size()) + m_currentNumerus = m_editors[m_currentModel].transTexts.size() - 1; + activeEditor()->setFocus(); + } else { + m_currentNumerus = -1; + } + } + if (m_editors.size() == 1) { + m_editors[0].container->setAutoFillBackground(false); + } else { + for (int i = model; i < m_editors.size(); ++i) + m_editors[i].container->setPalette(paletteForModel(i)); + } +} + +void MessageEditor::addPluralForm(int model, const QString &label, bool writable) +{ + FormMultiWidget *transEditor = new FormMultiWidget(label); + connect(transEditor, SIGNAL(editorCreated(QTextEdit*)), SLOT(editorCreated(QTextEdit*))); + transEditor->setEditingEnabled(writable); + transEditor->setHideWhenEmpty(!writable); + if (!m_editors[model].transTexts.isEmpty()) + transEditor->setVisible(false); + transEditor->setMultiEnabled(m_lengthVariants); + static_cast<QBoxLayout *>(m_editors[model].container->layout())->insertWidget( + m_editors[model].transTexts.count(), transEditor); + + connect(transEditor, SIGNAL(selectionChanged(QTextEdit*)), + SLOT(selectionChanged(QTextEdit*))); + connect(transEditor, SIGNAL(textChanged(QTextEdit*)), + SLOT(emitTranslationChanged(QTextEdit*))); + connect(transEditor, SIGNAL(textChanged(QTextEdit*)), SLOT(resetHoverSelection())); + connect(transEditor, SIGNAL(cursorPositionChanged()), SLOT(resetHoverSelection())); + + m_editors[model].transTexts << transEditor; +} + +void MessageEditor::editorCreated(QTextEdit *te) +{ + FormMultiWidget *snd = static_cast<FormMultiWidget *>(sender()); + for (int model = 0; ; ++model) { + MessageEditorData med = m_editors.at(model); + if (med.transTexts.contains(snd)) { + QFont font; + font.setPointSize(static_cast<int>(med.fontSize)); + te->setFont(font); + + te->installEventFilter(this); + + fixTabOrder(); + return; + } + } +} + +void MessageEditor::fixTabOrder() +{ + m_tabOrderTimer.start(0); +} + +void MessageEditor::reallyFixTabOrder() +{ + QWidget *prev = this; + foreach (const MessageEditorData &med, m_editors) { + foreach (FormMultiWidget *fmw, med.transTexts) + foreach (QTextEdit *te, fmw->getEditors()) { + setTabOrder(prev, te); + prev = te; + } + QTextEdit *te = med.transCommentText->getEditor(); + setTabOrder(prev, te); + prev = te; + } +} + +/*! internal + Returns all translations for an item. + The number of translations is dependent on if we have a plural form or not. + If we don't have a plural form, then this should only contain one item. + Otherwise it will contain the number of numerus forms for the particular language. +*/ +QStringList MessageEditor::translations(int model) const +{ + QStringList translations; + for (int i = 0; i < m_editors[model].transTexts.count() && + m_editors[model].transTexts.at(i)->isVisible(); ++i) + translations << m_editors[model].transTexts[i]->getTranslation(); + return translations; +} + +static void clearSelection(QTextEdit *t) +{ + bool oldBlockState = t->blockSignals(true); + QTextCursor c = t->textCursor(); + c.clearSelection(); + t->setTextCursor(c); + t->blockSignals(oldBlockState); +} + +void MessageEditor::selectionChanged(QTextEdit *te) +{ + if (te != m_selectionHolder) { + if (m_selectionHolder) + clearSelection(m_selectionHolder); + m_selectionHolder = (te->textCursor().hasSelection() ? te : 0); + updateCanCutCopy(); + } +} + +void MessageEditor::resetHoverSelection() +{ + if (m_selectionHolder && + (m_selectionHolder == m_source->getEditor() + || m_selectionHolder == m_pluralSource->getEditor())) + resetSelection(); +} + +void MessageEditor::resetSelection() +{ + if (m_selectionHolder) { + clearSelection(m_selectionHolder); + m_selectionHolder = 0; + updateCanCutCopy(); + } +} + +void MessageEditor::activeModelAndNumerus(int *model, int *numerus) const +{ + for (int j = 0; j < m_editors.count(); ++j) { + for (int i = 0; i < m_editors[j].transTexts.count(); ++i) + foreach (QTextEdit *te, m_editors[j].transTexts[i]->getEditors()) + if (m_focusWidget == te) { + *model = j; + *numerus = i; + return; + } + if (m_focusWidget == m_editors[j].transCommentText->getEditor()) { + *model = j; + *numerus = -1; + return; + } + } + *model = -1; + *numerus = -1; +} + +QTextEdit *MessageEditor::activeTranslation() const +{ + if (m_currentNumerus < 0) + return 0; + const QList<FormatTextEdit *> &editors = + m_editors[m_currentModel].transTexts[m_currentNumerus]->getEditors(); + foreach (QTextEdit *te, editors) + if (te->hasFocus()) + return te; + return editors.first(); +} + +QTextEdit *MessageEditor::activeOr1stTranslation() const +{ + if (m_currentNumerus < 0) { + for (int i = 0; i < m_editors.size(); ++i) + if (m_editors[i].container->isVisible() + && !m_editors[i].transTexts.first()->getEditors().first()->isReadOnly()) + return m_editors[i].transTexts.first()->getEditors().first(); + return 0; + } + return activeTranslation(); +} + +QTextEdit *MessageEditor::activeTransComment() const +{ + if (m_currentModel < 0 || m_currentNumerus >= 0) + return 0; + return m_editors[m_currentModel].transCommentText->getEditor(); +} + +QTextEdit *MessageEditor::activeEditor() const +{ + if (QTextEdit *te = activeTransComment()) + return te; + return activeTranslation(); +} + +QTextEdit *MessageEditor::activeOr1stEditor() const +{ + if (QTextEdit *te = activeTransComment()) + return te; + return activeOr1stTranslation(); +} + +void MessageEditor::setTargetLanguage(int model) +{ + const QStringList &numerusForms = m_dataModel->model(model)->numerusForms(); + const QString &langLocalized = m_dataModel->model(model)->localizedLanguage(); + for (int i = 0; i < numerusForms.count(); ++i) { + const QString &label = tr("%1 translation (%2)").arg(langLocalized, numerusForms[i]); + if (!i) + m_editors[model].firstForm = label; + if (i >= m_editors[model].transTexts.count()) + addPluralForm(model, label, m_dataModel->isModelWritable(model)); + else + m_editors[model].transTexts[i]->setLabel(label); + m_editors[model].transTexts[i]->setVisible(!i || m_editors[model].pluralEditMode); + m_editors[model].transTexts[i]->setWhatsThis( + tr("This is where you can enter or modify" + " the translation of the above source text.") ); + } + for (int j = m_editors[model].transTexts.count() - numerusForms.count(); j > 0; --j) + delete m_editors[model].transTexts.takeLast(); + m_editors[model].invariantForm = tr("%1 translation").arg(langLocalized); + m_editors[model].transCommentText->setLabel(tr("%1 translator comments").arg(langLocalized)); +} + +MessageEditorData *MessageEditor::modelForWidget(const QObject *o) +{ + for (int j = 0; j < m_editors.count(); ++j) { + for (int i = 0; i < m_editors[j].transTexts.count(); ++i) + foreach (QTextEdit *te, m_editors[j].transTexts[i]->getEditors()) + if (te == o) + return &m_editors[j]; + if (m_editors[j].transCommentText->getEditor() == o) + return &m_editors[j]; + } + return 0; +} + +static bool applyFont(MessageEditorData *med) +{ + QFont font; + font.setPointSize(static_cast<int>(med->fontSize)); + for (int i = 0; i < med->transTexts.count(); ++i) + foreach (QTextEdit *te, med->transTexts[i]->getEditors()) + te->setFont(font); + med->transCommentText->getEditor()->setFont(font); + return true; +} + +static bool incFont(MessageEditorData *med) +{ + if (!med || med->fontSize >= 32) + return true; + med->fontSize *= 1.2; + return applyFont(med); +} + +static bool decFont(MessageEditorData *med) +{ + if (!med || med->fontSize <= 8) + return true; + med->fontSize /= 1.2; + return applyFont(med); +} + +bool MessageEditor::eventFilter(QObject *o, QEvent *e) +{ + // handle copying from the source + if (e->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + + if (ke->modifiers() & Qt::ControlModifier) { + if (ke->key() == Qt::Key_C) { + if (m_source->getEditor()->textCursor().hasSelection()) { + m_source->getEditor()->copy(); + return true; + } + if (m_pluralSource->getEditor()->textCursor().hasSelection()) { + m_pluralSource->getEditor()->copy(); + return true; + } + } else if (ke->key() == Qt::Key_A) { + return true; + } + } + } else if (e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + if (ke->modifiers() & Qt::ControlModifier) { + if (ke->key() == Qt::Key_Plus || ke->key() == Qt::Key_Equal) + return incFont(modelForWidget(o)); + if (ke->key() == Qt::Key_Minus) + return decFont(modelForWidget(o)); + } else { + // Ctrl-Tab is still passed through to the textedit and causes a tab to be inserted. + if (ke->key() == Qt::Key_Tab) { + focusNextChild(); + return true; + } + } + } else if (e->type() == QEvent::Wheel) { + QWheelEvent *we = static_cast<QWheelEvent *>(e); + if (we->modifiers() & Qt::ControlModifier) { + if (we->delta() > 0) + return incFont(modelForWidget(o)); + return decFont(modelForWidget(o)); + } + } else if (e->type() == QEvent::FocusIn) { + QWidget *widget = static_cast<QWidget *>(o); + if (widget != m_focusWidget) + trackFocus(widget); + } + + return QScrollArea::eventFilter(o, e); +} + +void MessageEditor::grabFocus(QWidget *widget) +{ + if (widget != m_focusWidget) { + widget->setFocus(); + trackFocus(widget); + } +} + +void MessageEditor::trackFocus(QWidget *widget) +{ + m_focusWidget = widget; + + int model, numerus; + activeModelAndNumerus(&model, &numerus); + if (model != m_currentModel || numerus != m_currentNumerus) { + resetSelection(); + m_currentModel = model; + m_currentNumerus = numerus; + emit activeModelChanged(activeModel()); + updateBeginFromSource(); + updateUndoRedo(); + updateCanPaste(); + } +} + +void MessageEditor::showNothing() +{ + m_source->clearTranslation(); + m_pluralSource->clearTranslation(); + m_commentText->clearTranslation(); + for (int j = 0; j < m_editors.count(); ++j) { + setEditingEnabled(j, false); + foreach (FormMultiWidget *widget, m_editors[j].transTexts) + widget->clearTranslation(); + m_editors[j].transCommentText->clearTranslation(); + } + emit pasteAvailable(false); + updateBeginFromSource(); + updateUndoRedo(); +} + +void MessageEditor::showMessage(const MultiDataIndex &index) +{ + m_currentIndex = index; + + bool hadMsg = false; + for (int j = 0; j < m_editors.size(); ++j) { + + MessageEditorData &ed = m_editors[j]; + + MessageItem *item = m_dataModel->messageItem(index, j); + if (!item) { + ed.container->hide(); + continue; + } + ed.container->show(); + + if (!hadMsg) { + + // Source text form + m_source->setTranslation(item->text()); + m_pluralSource->setTranslation(item->pluralText()); + // Use location from first non-obsolete message + if (!item->fileName().isEmpty()) { + QString toolTip = tr("'%1'\nLine: %2").arg(item->fileName(), QString::number(item->lineNumber())); + m_source->setToolTip(toolTip); + } else { + m_source->setToolTip(QLatin1String("")); + } + + // Comment field + QString commentText = item->comment().simplified(); + + if (!item->extraComment().isEmpty()) { + if (!commentText.isEmpty()) + commentText += QLatin1String("\n"); + commentText += item->extraComment().simplified(); + } + + m_commentText->setTranslation(commentText); + + hadMsg = true; + } + + setEditingEnabled(j, m_dataModel->isModelWritable(j) + && item->message().type() != TranslatorMessage::Obsolete); + + // Translation label + ed.pluralEditMode = item->translations().count() > 1; + ed.transTexts.first()->setLabel(ed.pluralEditMode ? ed.firstForm : ed.invariantForm); + + // Translation forms + if (item->text().isEmpty() && !item->context().isEmpty()) { + for (int i = 0; i < ed.transTexts.size(); ++i) + ed.transTexts.at(i)->setVisible(false); + } else { + QStringList normalizedTranslations = + m_dataModel->model(j)->normalizedTranslations(*item); + for (int i = 0; i < ed.transTexts.size(); ++i) { + bool shouldShow = (i < normalizedTranslations.count()); + if (shouldShow) + setTranslation(j, normalizedTranslations.at(i), i); + else + setTranslation(j, QString(), i); + ed.transTexts.at(i)->setVisible(i == 0 || shouldShow); + } + } + + ed.transCommentText->setTranslation(item->translatorComment().trimmed(), false); + } + + updateUndoRedo(); +} + +void MessageEditor::setTranslation(int model, const QString &translation, int numerus) +{ + MessageEditorData &ed = m_editors[model]; + if (numerus >= ed.transTexts.count()) + numerus = 0; + FormMultiWidget *transForm = ed.transTexts[numerus]; + transForm->setTranslation(translation, false); + + updateBeginFromSource(); +} + +void MessageEditor::setTranslation(int latestModel, const QString &translation) +{ + int numerus; + if (m_currentNumerus < 0) { + numerus = 0; + } else { + latestModel = m_currentModel; + numerus = m_currentNumerus; + } + FormMultiWidget *transForm = m_editors[latestModel].transTexts[numerus]; + transForm->getEditors().first()->setFocus(); + transForm->setTranslation(translation, true); + + updateBeginFromSource(); +} + +void MessageEditor::setEditingEnabled(int model, bool enabled) +{ + MessageEditorData &ed = m_editors[model]; + foreach (FormMultiWidget *widget, ed.transTexts) + widget->setEditingEnabled(enabled); + ed.transCommentText->setEditingEnabled(enabled); + + updateCanPaste(); +} + +void MessageEditor::setLengthVariants(bool on) +{ + m_lengthVariants = on; + foreach (const MessageEditorData &ed, m_editors) + foreach (FormMultiWidget *widget, ed.transTexts) + widget->setMultiEnabled(on); +} + +void MessageEditor::undo() +{ + activeEditor()->document()->undo(); +} + +void MessageEditor::redo() +{ + activeEditor()->document()->redo(); +} + +void MessageEditor::updateUndoRedo() +{ + bool newUndoAvail = false; + bool newRedoAvail = false; + if (QTextEdit *te = activeEditor()) { + QTextDocument *doc = te->document(); + newUndoAvail = doc->isUndoAvailable(); + newRedoAvail = doc->isRedoAvailable(); + } + + if (newUndoAvail != m_undoAvail) { + m_undoAvail = newUndoAvail; + emit undoAvailable(newUndoAvail); + } + + if (newRedoAvail != m_redoAvail) { + m_redoAvail = newRedoAvail; + emit redoAvailable(newRedoAvail); + } +} + +void MessageEditor::cut() +{ + m_selectionHolder->cut(); +} + +void MessageEditor::copy() +{ + m_selectionHolder->copy(); +} + +void MessageEditor::updateCanCutCopy() +{ + bool newCopyState = false; + bool newCutState = false; + + if (m_selectionHolder) { + newCopyState = true; + newCutState = !m_selectionHolder->isReadOnly(); + } + + if (newCopyState != m_copyAvail) { + m_copyAvail = newCopyState; + emit copyAvailable(m_copyAvail); + } + + if (newCutState != m_cutAvail) { + m_cutAvail = newCutState; + emit cutAvailable(m_cutAvail); + } +} + +void MessageEditor::paste() +{ + activeEditor()->paste(); +} + +void MessageEditor::updateCanPaste() +{ + QTextEdit *te; + emit pasteAvailable(!m_clipboardEmpty + && (te = activeEditor()) && !te->isReadOnly()); +} + +void MessageEditor::clipboardChanged() +{ + // this is expensive, so move it out of the common path in updateCanPaste + m_clipboardEmpty = qApp->clipboard()->text().isNull(); + updateCanPaste(); +} + +void MessageEditor::selectAll() +{ + // make sure we don't select the selection of a translator textedit, + // if we really want the source text editor to be selected. + QTextEdit *te; + if ((te = m_source->getEditor())->underMouse() + || (te = m_pluralSource->getEditor())->underMouse() + || ((te = activeEditor()) && te->hasFocus())) + te->selectAll(); +} + +void MessageEditor::emitTranslationChanged(QTextEdit *widget) +{ + grabFocus(widget); // DND proofness + updateBeginFromSource(); + updateUndoRedo(); + emit translationChanged(translations(m_currentModel)); +} + +void MessageEditor::emitTranslatorCommentChanged(QTextEdit *widget) +{ + grabFocus(widget); // DND proofness + updateUndoRedo(); + emit translatorCommentChanged(m_editors[m_currentModel].transCommentText->getTranslation()); +} + +void MessageEditor::updateBeginFromSource() +{ + bool overwrite = false; + if (QTextEdit *activeEditor = activeTranslation()) + overwrite = !activeEditor->isReadOnly() + && activeEditor->toPlainText().trimmed().isEmpty(); + emit beginFromSourceAvailable(overwrite); +} + +void MessageEditor::beginFromSource() +{ + MessageItem *item = m_dataModel->messageItem(m_currentIndex, m_currentModel); + setTranslation(m_currentModel, + m_currentNumerus > 0 && !item->pluralText().isEmpty() ? + item->pluralText() : item->text()); +} + +void MessageEditor::setEditorFocus() +{ + if (!widget()->hasFocus()) + if (QTextEdit *activeEditor = activeOr1stEditor()) + activeEditor->setFocus(); +} + +void MessageEditor::setEditorFocus(int model) +{ + if (m_currentModel != model) { + if (model < 0) { + resetSelection(); + m_currentNumerus = -1; + m_currentModel = -1; + m_focusWidget = 0; + emit activeModelChanged(activeModel()); + updateBeginFromSource(); + updateUndoRedo(); + updateCanPaste(); + } else { + m_editors[model].transTexts.first()->getEditors().first()->setFocus(); + } + } +} + +bool MessageEditor::focusNextUnfinished(int start) +{ + for (int j = start; j < m_editors.count(); ++j) + if (m_dataModel->isModelWritable(j)) + if (MessageItem *item = m_dataModel->messageItem(m_currentIndex, j)) + if (item->type() == TranslatorMessage::Unfinished) { + m_editors[j].transTexts.first()->getEditors().first()->setFocus(); + return true; + } + return false; +} + +void MessageEditor::setUnfinishedEditorFocus() +{ + focusNextUnfinished(0); +} + +bool MessageEditor::focusNextUnfinished() +{ + return focusNextUnfinished(m_currentModel + 1); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/messageeditor.h b/src/linguist/linguist/messageeditor.h new file mode 100644 index 000000000..875ef3303 --- /dev/null +++ b/src/linguist/linguist/messageeditor.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MESSAGEEDITOR_H +#define MESSAGEEDITOR_H + +#include "messagemodel.h" + +#include <QtCore/QLocale> +#include <QtCore/QTimer> + +#include <QtGui/QFrame> +#include <QtGui/QScrollArea> + +QT_BEGIN_NAMESPACE + +class QBoxLayout; +class QMainWindow; +class QTextEdit; + +class MessageEditor; +class FormatTextEdit; +class FormWidget; +class FormMultiWidget; + +struct MessageEditorData { + QWidget *container; + FormWidget *transCommentText; + QList<FormMultiWidget *> transTexts; + QString invariantForm; + QString firstForm; + qreal fontSize; + bool pluralEditMode; +}; + +class MessageEditor : public QScrollArea +{ + Q_OBJECT + +public: + MessageEditor(MultiDataModel *dataModel, QMainWindow *parent = 0); + + void showNothing(); + void showMessage(const MultiDataIndex &index); + void setNumerusForms(int model, const QStringList &numerusForms); + bool eventFilter(QObject *, QEvent *); + void setTranslation(int model, const QString &translation, int numerus); + int activeModel() const { return (m_editors.count() != 1) ? m_currentModel : 0; } + void setEditorFocus(int model); + void setUnfinishedEditorFocus(); + bool focusNextUnfinished(); + +signals: + void translationChanged(const QStringList &translations); + void translatorCommentChanged(const QString &comment); + void activeModelChanged(int model); + + void undoAvailable(bool avail); + void redoAvailable(bool avail); + void cutAvailable(bool avail); + void copyAvailable(bool avail); + void pasteAvailable(bool avail); + void beginFromSourceAvailable(bool enable); + +public slots: + void undo(); + void redo(); + void cut(); + void copy(); + void paste(); + void selectAll(); + void beginFromSource(); + void setEditorFocus(); + void setTranslation(int latestModel, const QString &translation); + void setLengthVariants(bool on); + +private slots: + void editorCreated(QTextEdit *); + void selectionChanged(QTextEdit *); + void resetHoverSelection(); + void emitTranslationChanged(QTextEdit *); + void emitTranslatorCommentChanged(QTextEdit *); + void updateCanPaste(); + void clipboardChanged(); + void messageModelAppended(); + void messageModelDeleted(int model); + void allModelsDeleted(); + void setTargetLanguage(int model); + void reallyFixTabOrder(); + +private: + void setupEditorPage(); + void setEditingEnabled(int model, bool enabled); + bool focusNextUnfinished(int start); + void resetSelection(); + void grabFocus(QWidget *widget); + void trackFocus(QWidget *widget); + void activeModelAndNumerus(int *model, int *numerus) const; + QTextEdit *activeTranslation() const; + QTextEdit *activeOr1stTranslation() const; + QTextEdit *activeTransComment() const; + QTextEdit *activeEditor() const; + QTextEdit *activeOr1stEditor() const; + MessageEditorData *modelForWidget(const QObject *o); + int activeTranslationNumerus() const; + QStringList translations(int model) const; + void updateBeginFromSource(); + void updateUndoRedo(); + void updateCanCutCopy(); + void addPluralForm(int model, const QString &label, bool writable); + void fixTabOrder(); + QPalette paletteForModel(int model) const; + + MultiDataModel *m_dataModel; + + MultiDataIndex m_currentIndex; + int m_currentModel; + int m_currentNumerus; + + bool m_lengthVariants; + + bool m_undoAvail; + bool m_redoAvail; + bool m_cutAvail; + bool m_copyAvail; + + bool m_clipboardEmpty; + + QTextEdit *m_selectionHolder; + QWidget *m_focusWidget; + QBoxLayout *m_layout; + FormWidget *m_source; + FormWidget *m_pluralSource; + FormWidget *m_commentText; + QList<MessageEditorData> m_editors; + + QTimer m_tabOrderTimer; +}; + +QT_END_NAMESPACE + +#endif // MESSAGEEDITOR_H diff --git a/src/linguist/linguist/messageeditorwidgets.cpp b/src/linguist/linguist/messageeditorwidgets.cpp new file mode 100644 index 000000000..f55c336a9 --- /dev/null +++ b/src/linguist/linguist/messageeditorwidgets.cpp @@ -0,0 +1,456 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "messageeditorwidgets.h" +#include "messagehighlighter.h" + +#include <translator.h> + +#include <QAbstractTextDocumentLayout> +#include <QAction> +#include <QApplication> +#include <QClipboard> +#include <QDebug> +#include <QLayout> +#include <QMenu> +#include <QMessageBox> +#include <QPainter> +#include <QScrollArea> +#include <QTextBlock> +#include <QTextDocumentFragment> +#include <QToolButton> +#include <QVBoxLayout> + +QT_BEGIN_NAMESPACE + +ExpandingTextEdit::ExpandingTextEdit(QWidget *parent) + : QTextEdit(parent) +{ + setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding)); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + QAbstractTextDocumentLayout *docLayout = document()->documentLayout(); + connect(docLayout, SIGNAL(documentSizeChanged(QSizeF)), SLOT(updateHeight(QSizeF))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(reallyEnsureCursorVisible())); + + m_minimumHeight = qRound(docLayout->documentSize().height()) + frameWidth() * 2; +} + +void ExpandingTextEdit::updateHeight(const QSizeF &documentSize) +{ + m_minimumHeight = qRound(documentSize.height()) + frameWidth() * 2; + updateGeometry(); +} + +QSize ExpandingTextEdit::sizeHint() const +{ + return QSize(100, m_minimumHeight); +} + +QSize ExpandingTextEdit::minimumSizeHint() const +{ + return QSize(100, m_minimumHeight); +} + +void ExpandingTextEdit::reallyEnsureCursorVisible() +{ + QObject *ancestor = parent(); + while (ancestor) { + QScrollArea *scrollArea = qobject_cast<QScrollArea*>(ancestor); + if (scrollArea && + (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff && + scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)) { + const QRect &r = cursorRect(); + const QPoint &c = mapTo(scrollArea->widget(), r.center()); + scrollArea->ensureVisible(c.x(), c.y()); + break; + } + ancestor = ancestor->parent(); + } +} + +FormatTextEdit::FormatTextEdit(QWidget *parent) + : ExpandingTextEdit(parent) +{ + setLineWrapMode(QTextEdit::WidgetWidth); + setAcceptRichText(false); + QTextOption option = document()->defaultTextOption(); + option.setFlags(option.flags() + | QTextOption::ShowLineAndParagraphSeparators + | QTextOption::ShowTabsAndSpaces); + document()->setDefaultTextOption(option); + + // Do not set different background if disabled + QPalette p = palette(); + p.setColor(QPalette::Disabled, QPalette::Base, p.color(QPalette::Active, QPalette::Base)); + setPalette(p); + + setEditable(true); + + m_highlighter = new MessageHighlighter(this); +} + +void FormatTextEdit::setEditable(bool editable) +{ + // save default frame style + static int framed = frameStyle(); + static Qt::FocusPolicy defaultFocus = focusPolicy(); + + if (editable) { + setFrameStyle(framed); + setFocusPolicy(defaultFocus); + } else { + setFrameStyle(QFrame::NoFrame | QFrame::Plain); + setFocusPolicy(Qt::NoFocus); + } + + setReadOnly(!editable); +} + +void FormatTextEdit::setPlainText(const QString &text, bool userAction) +{ + if (!userAction) { + // Prevent contentsChanged signal + bool oldBlockState = blockSignals(true); + document()->setUndoRedoEnabled(false); + ExpandingTextEdit::setPlainText(text); + // highlighter is out of sync because of blocked signals + m_highlighter->rehighlight(); + document()->setUndoRedoEnabled(true); + blockSignals(oldBlockState); + } else { + ExpandingTextEdit::setPlainText(text); + } +} + +FormWidget::FormWidget(const QString &label, bool isEditable, QWidget *parent) + : QWidget(parent), + m_hideWhenEmpty(false) +{ + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(0); + + m_label = new QLabel(this); + QFont fnt; + fnt.setBold(true); + m_label->setFont(fnt); + m_label->setText(label); + layout->addWidget(m_label); + + m_editor = new FormatTextEdit(this); + m_editor->setEditable(isEditable); + //m_textEdit->setWhatsThis(tr("This area shows text from an auxillary translation.")); + layout->addWidget(m_editor); + + setLayout(layout); + + connect(m_editor, SIGNAL(textChanged()), SLOT(slotTextChanged())); + connect(m_editor, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); + connect(m_editor, SIGNAL(cursorPositionChanged()), SIGNAL(cursorPositionChanged())); +} + +void FormWidget::slotTextChanged() +{ + emit textChanged(m_editor); +} + +void FormWidget::slotSelectionChanged() +{ + emit selectionChanged(m_editor); +} + +void FormWidget::setTranslation(const QString &text, bool userAction) +{ + m_editor->setPlainText(text, userAction); + if (m_hideWhenEmpty) + setHidden(text.isEmpty()); +} + +void FormWidget::setEditingEnabled(bool enable) +{ + // Use read-only state so that the text can still be copied + m_editor->setReadOnly(!enable); + m_label->setEnabled(enable); +} + + +class ButtonWrapper : public QWidget +{ + // no Q_OBJECT: no need to, and don't want the useless moc file + +public: + ButtonWrapper(QWidget *wrapee, QWidget *relator) : m_wrapee(wrapee) + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + QBoxLayout *box = new QVBoxLayout; + box->setMargin(0); + setLayout(box); + box->addWidget(wrapee, 0, Qt::AlignBottom); + if (relator) + relator->installEventFilter(this); + } + +protected: + virtual bool eventFilter(QObject *object, QEvent *event) + { + if (event->type() == QEvent::Resize) { + QWidget *relator = static_cast<QWidget *>(object); + setFixedHeight((relator->height() + layout()->spacing() + m_wrapee->height()) / 2); + } + return false; + } + +private: + QWidget *m_wrapee; +}; + +FormMultiWidget::FormMultiWidget(const QString &label, QWidget *parent) + : QWidget(parent), + m_hideWhenEmpty(false), + m_multiEnabled(false), + m_plusIcon(QIcon(QLatin1String(":/images/plus.png"))), // make static + m_minusIcon(QIcon(QLatin1String(":/images/minus.png"))) +{ + m_label = new QLabel(this); + QFont fnt; + fnt.setBold(true); + m_label->setFont(fnt); + m_label->setText(label); + + m_plusButtons.append( + new ButtonWrapper(makeButton(m_plusIcon, SLOT(plusButtonClicked())), 0)); +} + +QAbstractButton *FormMultiWidget::makeButton(const QIcon &icon, const char *slot) +{ + QAbstractButton *btn = new QToolButton(this); + btn->setIcon(icon); + btn->setFixedSize(icon.availableSizes().first() /* + something */); + btn->setFocusPolicy(Qt::NoFocus); + connect(btn, SIGNAL(clicked()), slot); + return btn; +} + +void FormMultiWidget::addEditor(int idx) +{ + FormatTextEdit *editor = new FormatTextEdit(this); + m_editors.insert(idx, editor); + + m_minusButtons.insert(idx, makeButton(m_minusIcon, SLOT(minusButtonClicked()))); + m_plusButtons.insert(idx + 1, + new ButtonWrapper(makeButton(m_plusIcon, SLOT(plusButtonClicked())), editor)); + + connect(editor, SIGNAL(textChanged()), SLOT(slotTextChanged())); + connect(editor, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); + connect(editor, SIGNAL(cursorPositionChanged()), SIGNAL(cursorPositionChanged())); + editor->installEventFilter(this); + + emit editorCreated(editor); +} + +bool FormMultiWidget::eventFilter(QObject *watched, QEvent *event) +{ + int i = 0; + while (m_editors.at(i) != watched) + if (++i >= m_editors.count()) // Happens when deleting an editor + return false; + if (event->type() == QEvent::FocusOut) { + m_minusButtons.at(i)->setToolTip(QString()); + m_plusButtons.at(i)->setToolTip(QString()); + m_plusButtons.at(i + 1)->setToolTip(QString()); + } else if (event->type() == QEvent::FocusIn) { + m_minusButtons.at(i)->setToolTip(/*: translate, but don't change */ tr("Alt+Delete")); + m_plusButtons.at(i)->setToolTip(/*: translate, but don't change */ tr("Shift+Alt+Insert")); + m_plusButtons.at(i + 1)->setToolTip(/*: translate, but don't change */ tr("Alt+Insert")); + } else if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (ke->modifiers() & Qt::AltModifier) { + if (ke->key() == Qt::Key_Delete) { + deleteEditor(i); + return true; + } else if (ke->key() == Qt::Key_Insert) { + if (!(ke->modifiers() & Qt::ShiftModifier)) + ++i; + insertEditor(i); + return true; + } + } + } + return false; +} + +void FormMultiWidget::updateLayout() +{ + delete layout(); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + setLayout(layout); + + bool variants = m_multiEnabled && m_label->isEnabled(); + + layout->addWidget(m_label, 0, 0, 1, variants ? 3 : 1); + + for (int i = 0; i < m_plusButtons.count(); ++i) { + if (variants) + layout->addWidget(m_plusButtons.at(i), 1 + i * 2, 0, 2, 1, Qt::AlignTop); + m_plusButtons.at(i)->setVisible(variants); + } + for (int j = 0; j < m_minusButtons.count(); ++j) { + if (variants) + layout->addWidget(m_minusButtons.at(j), 2 + j * 2, 2, 2, 1, Qt::AlignVCenter); + m_minusButtons.at(j)->setVisible(variants); + } + for (int k = 0; k < m_editors.count(); ++k) + layout->addWidget(m_editors.at(k), 2 + k * 2, variants ? 1 : 0, 2, 1, Qt::AlignVCenter); + + updateGeometry(); +} + +void FormMultiWidget::slotTextChanged() +{ + emit textChanged(static_cast<QTextEdit *>(sender())); +} + +void FormMultiWidget::slotSelectionChanged() +{ + emit selectionChanged(static_cast<QTextEdit *>(sender())); +} + +void FormMultiWidget::setTranslation(const QString &text, bool userAction) +{ + QStringList texts = text.split(QChar(Translator::BinaryVariantSeparator), QString::KeepEmptyParts); + + while (m_editors.count() > texts.count()) { + delete m_minusButtons.takeLast(); + delete m_plusButtons.takeLast(); + delete m_editors.takeLast(); + } + while (m_editors.count() < texts.count()) + addEditor(m_editors.count()); + updateLayout(); + + for (int i = 0; i < texts.count(); ++i) + // XXX this will emit n textChanged signals + m_editors.at(i)->setPlainText(texts.at(i), userAction); + + if (m_hideWhenEmpty) + setHidden(text.isEmpty()); +} + +QString FormMultiWidget::getTranslation() const +{ + QString ret; + for (int i = 0; i < m_editors.count(); ++i) { + if (i) + ret += QChar(Translator::BinaryVariantSeparator); + ret += m_editors.at(i)->toPlainText(); + } + return ret; +} + +void FormMultiWidget::setEditingEnabled(bool enable) +{ + // Use read-only state so that the text can still be copied + for (int i = 0; i < m_editors.count(); ++i) + m_editors.at(i)->setReadOnly(!enable); + m_label->setEnabled(enable); + if (m_multiEnabled) + updateLayout(); +} + +void FormMultiWidget::setMultiEnabled(bool enable) +{ + m_multiEnabled = enable; + if (m_label->isEnabled()) + updateLayout(); +} + +void FormMultiWidget::minusButtonClicked() +{ + int i = 0; + while (m_minusButtons.at(i) != sender()) + ++i; + deleteEditor(i); +} + +void FormMultiWidget::plusButtonClicked() +{ + QWidget *btn = static_cast<QAbstractButton *>(sender())->parentWidget(); + int i = 0; + while (m_plusButtons.at(i) != btn) + ++i; + insertEditor(i); +} + +void FormMultiWidget::deleteEditor(int idx) +{ + if (m_editors.count() == 1) { + // Don't just clear(), so the undo history is not lost + QTextCursor c = m_editors.first()->textCursor(); + c.select(QTextCursor::Document); + c.removeSelectedText(); + } else { + if (!m_editors.at(idx)->toPlainText().isEmpty()) { + if (QMessageBox::question(topLevelWidget(), tr("Confirmation - Qt Linguist"), + tr("Delete non-empty length variant?"), + QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes) + != QMessageBox::Yes) + return; + } + delete m_editors.takeAt(idx); + delete m_minusButtons.takeAt(idx); + delete m_plusButtons.takeAt(idx + 1); + updateLayout(); + emit textChanged(m_editors.at((m_editors.count() == idx) ? idx - 1 : idx)); + } +} + +void FormMultiWidget::insertEditor(int idx) +{ + addEditor(idx); + updateLayout(); + emit textChanged(m_editors.at(idx)); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/messageeditorwidgets.h b/src/linguist/linguist/messageeditorwidgets.h new file mode 100644 index 000000000..482d9b9db --- /dev/null +++ b/src/linguist/linguist/messageeditorwidgets.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MESSAGEEDITORWIDGETS_H +#define MESSAGEEDITORWIDGETS_H + +#include <QIcon> +#include <QImage> +#include <QLabel> +#include <QMap> +#include <QTextEdit> +#include <QUrl> +#include <QWidget> + +QT_BEGIN_NAMESPACE + +class QAbstractButton; +class QAction; +class QContextMenuEvent; +class QKeyEvent; +class QMenu; +class QSizeF; +class QString; +class QVariant; + +class MessageHighlighter; + +/* + Automatically adapt height to document contents + */ +class ExpandingTextEdit : public QTextEdit +{ + Q_OBJECT + +public: + ExpandingTextEdit(QWidget *parent = 0); + QSize sizeHint() const; + QSize minimumSizeHint() const; + +private slots: + void updateHeight(const QSizeF &documentSize); + void reallyEnsureCursorVisible(); + +private: + int m_minimumHeight; +}; + +/* + Format markup & control characters +*/ +class FormatTextEdit : public ExpandingTextEdit +{ + Q_OBJECT +public: + FormatTextEdit(QWidget *parent = 0); + void setEditable(bool editable); + +public slots: + void setPlainText(const QString & text, bool userAction); + +private: + MessageHighlighter *m_highlighter; +}; + +/* + Displays text field & associated label +*/ +class FormWidget : public QWidget +{ + Q_OBJECT +public: + FormWidget(const QString &label, bool isEditable, QWidget *parent = 0); + void setLabel(const QString &label) { m_label->setText(label); } + void setTranslation(const QString &text, bool userAction = false); + void clearTranslation() { setTranslation(QString(), false); } + QString getTranslation() { return m_editor->toPlainText(); } + void setEditingEnabled(bool enable); + void setHideWhenEmpty(bool optional) { m_hideWhenEmpty = optional; } + FormatTextEdit *getEditor() { return m_editor; } + +signals: + void textChanged(QTextEdit *); + void selectionChanged(QTextEdit *); + void cursorPositionChanged(); + +private slots: + void slotSelectionChanged(); + void slotTextChanged(); + +private: + QLabel *m_label; + FormatTextEdit *m_editor; + bool m_hideWhenEmpty; +}; + +/* + Displays text fields & associated label +*/ +class FormMultiWidget : public QWidget +{ + Q_OBJECT +public: + FormMultiWidget(const QString &label, QWidget *parent = 0); + void setLabel(const QString &label) { m_label->setText(label); } + void setTranslation(const QString &text, bool userAction = false); + void clearTranslation() { setTranslation(QString(), false); } + QString getTranslation() const; + void setEditingEnabled(bool enable); + void setMultiEnabled(bool enable); + void setHideWhenEmpty(bool optional) { m_hideWhenEmpty = optional; } + const QList<FormatTextEdit *> &getEditors() const { return m_editors; } + +signals: + void editorCreated(QTextEdit *); + void textChanged(QTextEdit *); + void selectionChanged(QTextEdit *); + void cursorPositionChanged(); + +protected: + bool eventFilter(QObject *watched, QEvent *event); + +private slots: + void slotTextChanged(); + void slotSelectionChanged(); + void minusButtonClicked(); + void plusButtonClicked(); + +private: + void addEditor(int idx); + void updateLayout(); + QAbstractButton *makeButton(const QIcon &icon, const char *slot); + void insertEditor(int idx); + void deleteEditor(int idx); + + QLabel *m_label; + QList<FormatTextEdit *> m_editors; + QList<QWidget *> m_plusButtons; + QList<QAbstractButton *> m_minusButtons; + bool m_hideWhenEmpty; + bool m_multiEnabled; + QIcon m_plusIcon, m_minusIcon; +}; + +QT_END_NAMESPACE + +#endif // MESSAGEEDITORWIDGETS_H diff --git a/src/linguist/linguist/messagehighlighter.cpp b/src/linguist/linguist/messagehighlighter.cpp new file mode 100644 index 000000000..bef2d6221 --- /dev/null +++ b/src/linguist/linguist/messagehighlighter.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "messagehighlighter.h" + +#include <QtCore/QTextStream> + +QT_BEGIN_NAMESPACE + +MessageHighlighter::MessageHighlighter(QTextEdit *textEdit) + : QSyntaxHighlighter(textEdit) +{ + QTextCharFormat entityFormat; + entityFormat.setForeground(Qt::red); + m_formats[Entity] = entityFormat; + + QTextCharFormat tagFormat; + tagFormat.setForeground(Qt::darkMagenta); + m_formats[Tag] = tagFormat; + + QTextCharFormat commentFormat; + commentFormat.setForeground(Qt::gray); + commentFormat.setFontItalic(true); + m_formats[Comment] = commentFormat; + + QTextCharFormat attributeFormat; + attributeFormat.setForeground(Qt::black); + attributeFormat.setFontItalic(true); + m_formats[Attribute] = attributeFormat; + + QTextCharFormat valueFormat; + valueFormat.setForeground(Qt::blue); + m_formats[Value] = valueFormat; + + QTextCharFormat acceleratorFormat; + acceleratorFormat.setFontUnderline(true); + m_formats[Accelerator] = acceleratorFormat; + + QTextCharFormat variableFormat; + variableFormat.setForeground(Qt::blue); + m_formats[Variable] = variableFormat; + + rehighlight(); +} + +void MessageHighlighter::highlightBlock(const QString &text) +{ + static const QLatin1Char tab = QLatin1Char('\t'); + static const QLatin1Char space = QLatin1Char(' '); + static const QLatin1Char amp = QLatin1Char('&'); + static const QLatin1Char endTag = QLatin1Char('>'); + static const QLatin1Char quot = QLatin1Char('"'); + static const QLatin1Char apos = QLatin1Char('\''); + static const QLatin1Char semicolon = QLatin1Char(';'); + static const QLatin1Char equals = QLatin1Char('='); + static const QLatin1Char percent = QLatin1Char('%'); + static const QLatin1String startComment = QLatin1String("<!--"); + static const QLatin1String endComment = QLatin1String("-->"); + static const QLatin1String endElement = QLatin1String("/>"); + + int state = previousBlockState(); + int len = text.length(); + int start = 0; + int pos = 0; + + while (pos < len) { + switch (state) { + case NormalState: + default: + while (pos < len) { + QChar ch = text.at(pos); + if (ch == QLatin1Char('<')) { + if (text.mid(pos, 4) == startComment) { + state = InComment; + } else { + state = InTag; + start = pos; + while (pos < len && text.at(pos) != space + && text.at(pos) != endTag + && text.at(pos) != tab + && text.mid(pos, 2) != endElement) + ++pos; + if (text.mid(pos, 2) == endElement) + ++pos; + setFormat(start, pos - start, + m_formats[Tag]); + break; + } + break; + } else if (ch == amp && pos + 1 < len) { + // Default is Accelerator + if (text.at(pos + 1).isLetterOrNumber()) + setFormat(pos + 1, 1, m_formats[Accelerator]); + + // When a semicolon follows assume an Entity + start = pos; + ch = text.at(++pos); + while (pos + 1 < len && ch != semicolon && ch.isLetterOrNumber()) + ch = text.at(++pos); + if (ch == semicolon) + setFormat(start, pos - start + 1, m_formats[Entity]); + } else if (ch == percent) { + start = pos; + // %[1-9]* + for (++pos; pos < len && text.at(pos).isDigit(); ++pos) {} + // %n + if (pos < len && pos == start + 1 && text.at(pos) == QLatin1Char('n')) + ++pos; + setFormat(start, pos - start, m_formats[Variable]); + } else { + // No tag, comment or entity started, continue... + ++pos; + } + } + break; + case InComment: + start = pos; + while (pos < len) { + if (text.mid(pos, 3) == endComment) { + pos += 3; + state = NormalState; + break; + } else { + ++pos; + } + } + setFormat(start, pos - start, m_formats[Comment]); + break; + case InTag: + QChar quote = QChar::Null; + while (pos < len) { + QChar ch = text.at(pos); + if (quote.isNull()) { + start = pos; + if (ch == apos || ch == quot) { + quote = ch; + } else if (ch == endTag) { + ++pos; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (text.mid(pos, 2) == endElement) { + pos += 2; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (ch != space && text.at(pos) != tab) { + // Tag not ending, not a quote and no whitespace, so + // we must be dealing with an attribute. + ++pos; + while (pos < len && text.at(pos) != space + && text.at(pos) != tab + && text.at(pos) != equals) + ++pos; + setFormat(start, pos - start, m_formats[Attribute]); + start = pos; + } + } else if (ch == quote) { + quote = QChar::Null; + + // Anything quoted is a value + setFormat(start, pos - start, m_formats[Value]); + } + ++pos; + } + break; + } + } + setCurrentBlockState(state); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/messagehighlighter.h b/src/linguist/linguist/messagehighlighter.h new file mode 100644 index 000000000..455140f61 --- /dev/null +++ b/src/linguist/linguist/messagehighlighter.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MESSAGEHIGHLIGHTER_H +#define MESSAGEHIGHLIGHTER_H + +#include <QtGui/QSyntaxHighlighter> + +QT_BEGIN_NAMESPACE + +/* Message highlighter based on HtmlSyntaxHighlighter from designer */ +class MessageHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + MessageHighlighter(QTextEdit *textEdit); + +protected: + void highlightBlock(const QString &text); + +private: + enum Construct { + Entity, + Tag, + Comment, + Attribute, + Value, + Accelerator, // "Open &File" + Variable, // "Opening %1" + LastConstruct = Variable + }; + + enum State { + NormalState = -1, + InComment, + InTag + }; + + QTextCharFormat m_formats[LastConstruct + 1]; +}; + +QT_END_NAMESPACE + +#endif // MESSAGEHIGHLIGHTER_H diff --git a/src/linguist/linguist/messagemodel.cpp b/src/linguist/linguist/messagemodel.cpp new file mode 100644 index 000000000..2d126577c --- /dev/null +++ b/src/linguist/linguist/messagemodel.cpp @@ -0,0 +1,1426 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "messagemodel.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QTextCodec> + +#include <QtGui/QMessageBox> +#include <QtGui/QPainter> +#include <QtGui/QPixmap> +#include <QtGui/QTextDocument> + +#include <private/qtranslator_p.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +/****************************************************************************** + * + * MessageItem + * + *****************************************************************************/ + +MessageItem::MessageItem(const TranslatorMessage &message) + : m_message(message), + m_danger(false) +{ + if (m_message.translation().isEmpty()) + m_message.setTranslation(QString()); +} + + +bool MessageItem::compare(const QString &findText, bool matchSubstring, + Qt::CaseSensitivity cs) const +{ + return matchSubstring + ? text().indexOf(findText, 0, cs) >= 0 + : text().compare(findText, cs) == 0; +} + +/****************************************************************************** + * + * ContextItem + * + *****************************************************************************/ + +ContextItem::ContextItem(const QString &context) + : m_context(context), + m_finishedCount(0), + m_finishedDangerCount(0), + m_unfinishedDangerCount(0), + m_nonobsoleteCount(0) +{} + +void ContextItem::appendToComment(const QString &str) +{ + if (!m_comment.isEmpty()) + m_comment += QLatin1String("\n\n"); + m_comment += str; +} + +MessageItem *ContextItem::messageItem(int i) const +{ + if (i >= 0 && i < msgItemList.count()) + return const_cast<MessageItem *>(&msgItemList[i]); + Q_ASSERT(i >= 0 && i < msgItemList.count()); + return 0; +} + +MessageItem *ContextItem::findMessage(const QString &sourcetext, const QString &comment) const +{ + for (int i = 0; i < messageCount(); ++i) { + MessageItem *mi = messageItem(i); + if (mi->text() == sourcetext && mi->comment() == comment) + return mi; + } + return 0; +} + +/****************************************************************************** + * + * DataModel + * + *****************************************************************************/ + +DataModel::DataModel(QObject *parent) + : QObject(parent), + m_modified(false), + m_numMessages(0), + m_srcWords(0), + m_srcChars(0), + m_srcCharsSpc(0), + m_language(QLocale::Language(-1)), + m_sourceLanguage(QLocale::Language(-1)), + m_country(QLocale::Country(-1)), + m_sourceCountry(QLocale::Country(-1)) +{} + +QStringList DataModel::normalizedTranslations(const MessageItem &m) const +{ + return Translator::normalizedTranslations(m.message(), m_numerusForms.count()); +} + +ContextItem *DataModel::contextItem(int context) const +{ + if (context >= 0 && context < m_contextList.count()) + return const_cast<ContextItem *>(&m_contextList[context]); + Q_ASSERT(context >= 0 && context < m_contextList.count()); + return 0; +} + +MessageItem *DataModel::messageItem(const DataIndex &index) const +{ + if (ContextItem *c = contextItem(index.context())) + return c->messageItem(index.message()); + return 0; +} + +ContextItem *DataModel::findContext(const QString &context) const +{ + for (int c = 0; c < m_contextList.count(); ++c) { + ContextItem *ctx = contextItem(c); + if (ctx->context() == context) + return ctx; + } + return 0; +} + +MessageItem *DataModel::findMessage(const QString &context, + const QString &sourcetext, const QString &comment) const +{ + if (ContextItem *ctx = findContext(context)) + return ctx->findMessage(sourcetext, comment); + return 0; +} + +static int calcMergeScore(const DataModel *one, const DataModel *two) +{ + int inBoth = 0; + for (int i = 0; i < two->contextCount(); ++i) { + ContextItem *oc = two->contextItem(i); + if (ContextItem *c = one->findContext(oc->context())) { + for (int j = 0; j < oc->messageCount(); ++j) { + MessageItem *m = oc->messageItem(j); + if (c->findMessage(m->text(), m->comment()) >= 0) + ++inBoth; + } + } + } + return inBoth * 100 / two->messageCount(); +} + +bool DataModel::isWellMergeable(const DataModel *other) const +{ + if (!other->messageCount() || !messageCount()) + return true; + + return calcMergeScore(this, other) + calcMergeScore(other, this) > 90; +} + +bool DataModel::load(const QString &fileName, bool *langGuessed, QWidget *parent) +{ + Translator tor; + ConversionData cd; + bool ok = tor.load(fileName, cd, QLatin1String("auto")); + if (!ok) { + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), cd.error()); + return false; + } + + if (!tor.messageCount()) { + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), + tr("The translation file '%1' will not be loaded because it is empty.") + .arg(Qt::escape(fileName))); + return false; + } + + Translator::Duplicates dupes = tor.resolveDuplicates(); + if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) { + QString err = tr("<qt>Duplicate messages found in '%1':").arg(Qt::escape(fileName)); + int numdups = 0; + foreach (int i, dupes.byId) { + if (++numdups >= 5) { + err += tr("<p>[more duplicates omitted]"); + goto doWarn; + } + err += tr("<p>* ID: %1").arg(Qt::escape(tor.message(i).id())); + } + foreach (int j, dupes.byContents) { + const TranslatorMessage &msg = tor.message(j); + if (++numdups >= 5) { + err += tr("<p>[more duplicates omitted]"); + break; + } + err += tr("<p>* Context: %1<br>* Source: %2") + .arg(Qt::escape(msg.context()), Qt::escape(msg.sourceText())); + if (!msg.comment().isEmpty()) + err += tr("<br>* Comment: %3").arg(Qt::escape(msg.comment())); + } + doWarn: + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), err); + } + + m_srcFileName = fileName; + m_codecName = tor.codecName(); + m_relativeLocations = (tor.locationsType() == Translator::RelativeLocations); + m_extra = tor.extras(); + m_contextList.clear(); + m_numMessages = 0; + + QHash<QString, int> contexts; + + m_srcWords = 0; + m_srcChars = 0; + m_srcCharsSpc = 0; + + foreach (const TranslatorMessage &msg, tor.messages()) { + if (!contexts.contains(msg.context())) { + contexts.insert(msg.context(), m_contextList.size()); + m_contextList.append(ContextItem(msg.context())); + } + + ContextItem *c = contextItem(contexts.value(msg.context())); + if (msg.sourceText() == QLatin1String(ContextComment)) { + c->appendToComment(msg.comment()); + } else { + MessageItem tmp(msg); + if (msg.type() == TranslatorMessage::Finished) + c->incrementFinishedCount(); + if (msg.type() != TranslatorMessage::Obsolete) { + doCharCounting(tmp.text(), m_srcWords, m_srcChars, m_srcCharsSpc); + doCharCounting(tmp.pluralText(), m_srcWords, m_srcChars, m_srcCharsSpc); + c->incrementNonobsoleteCount(); + } + c->appendMessage(tmp); + ++m_numMessages; + } + } + + // Try to detect the correct language in the following order + // 1. Look for the language attribute in the ts + // if that fails + // 2. Guestimate the language from the filename + // (expecting the qt_{en,de}.ts convention) + // if that fails + // 3. Retrieve the locale from the system. + *langGuessed = false; + QString lang = tor.languageCode(); + if (lang.isEmpty()) { + lang = QFileInfo(fileName).baseName(); + int pos = lang.indexOf(QLatin1Char('_')); + if (pos != -1 && pos + 3 == lang.length()) + lang = fileName.mid(pos + 1); + else + lang.clear(); + *langGuessed = true; + } + QLocale::Language l; + QLocale::Country c; + Translator::languageAndCountry(lang, &l, &c); + if (l == QLocale::C) { + QLocale sys; + l = sys.language(); + c = sys.country(); + *langGuessed = true; + } + if (!setLanguageAndCountry(l, c)) + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), + tr("Linguist does not know the plural rules for '%1'.\n" + "Will assume a single universal form.") + .arg(m_localizedLanguage)); + // Try to detect the correct source language in the following order + // 1. Look for the language attribute in the ts + // if that fails + // 2. Assume English + lang = tor.sourceLanguageCode(); + if (lang.isEmpty()) { + l = QLocale::C; + c = QLocale::AnyCountry; + } else { + Translator::languageAndCountry(lang, &l, &c); + } + setSourceLanguageAndCountry(l, c); + + setModified(false); + + return true; +} + +bool DataModel::save(const QString &fileName, QWidget *parent) +{ + Translator tor; + for (DataModelIterator it(this); it.isValid(); ++it) + tor.append(it.current()->message()); + + tor.setLanguageCode(Translator::makeLanguageCode(m_language, m_country)); + tor.setSourceLanguageCode(Translator::makeLanguageCode(m_sourceLanguage, m_sourceCountry)); + tor.setCodecName(m_codecName); + tor.setLocationsType(m_relativeLocations ? Translator::RelativeLocations + : Translator::AbsoluteLocations); + tor.setExtras(m_extra); + ConversionData cd; + bool ok = tor.save(fileName, cd, QLatin1String("auto")); + if (ok) + setModified(false); + else + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), cd.error()); + return ok; +} + +bool DataModel::saveAs(const QString &newFileName, QWidget *parent) +{ + if (!save(newFileName, parent)) + return false; + m_srcFileName = newFileName; + return true; +} + +bool DataModel::release(const QString &fileName, bool verbose, bool ignoreUnfinished, + TranslatorSaveMode mode, QWidget *parent) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), + tr("Cannot create '%2': %1").arg(file.errorString()).arg(fileName)); + return false; + } + Translator tor; + QLocale locale(m_language, m_country); + tor.setLanguageCode(locale.name()); + for (DataModelIterator it(this); it.isValid(); ++it) + tor.append(it.current()->message()); + ConversionData cd; + cd.m_verbose = verbose; + cd.m_ignoreUnfinished = ignoreUnfinished; + cd.m_saveMode = mode; + bool ok = tor.release(&file, cd); + if (!ok) + QMessageBox::warning(parent, QObject::tr("Qt Linguist"), cd.error()); + return ok; +} + +void DataModel::doCharCounting(const QString &text, int &trW, int &trC, int &trCS) +{ + trCS += text.length(); + bool inWord = false; + for (int i = 0; i < text.length(); ++i) { + if (text[i].isLetterOrNumber() || text[i] == QLatin1Char('_')) { + if (!inWord) { + ++trW; + inWord = true; + } + } else { + inWord = false; + } + if (!text[i].isSpace()) + trC++; + } +} + +bool DataModel::setLanguageAndCountry(QLocale::Language lang, QLocale::Country country) +{ + if (m_language == lang && m_country == country) + return true; + m_language = lang; + m_country = country; + + if (lang == QLocale::C || uint(lang) > uint(QLocale::LastLanguage)) // XXX does this make any sense? + lang = QLocale::English; + QByteArray rules; + bool ok = getNumerusInfo(lang, country, &rules, &m_numerusForms, 0); + m_localizedLanguage = QCoreApplication::translate("MessageEditor", QLocale::languageToString(lang).toAscii()); + m_countRefNeeds.clear(); + for (int i = 0; i < rules.size(); ++i) { + m_countRefNeeds.append(!(rules.at(i) == Q_EQ && (i == (rules.size() - 2) || rules.at(i + 2) == (char)Q_NEWRULE))); + while (++i < rules.size() && rules.at(i) != (char)Q_NEWRULE) {} + } + m_countRefNeeds.append(true); + if (!ok) { + m_numerusForms.clear(); + m_numerusForms << tr("Universal Form"); + } + emit languageChanged(); + setModified(true); + return ok; +} + +void DataModel::setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country) +{ + if (m_sourceLanguage == lang && m_sourceCountry == country) + return; + m_sourceLanguage = lang; + m_sourceCountry = country; + setModified(true); +} + +void DataModel::updateStatistics() +{ + int trW = 0; + int trC = 0; + int trCS = 0; + + for (DataModelIterator it(this); it.isValid(); ++it) { + const MessageItem *mi = it.current(); + if (mi->isFinished()) + foreach (const QString &trnsl, mi->translations()) + doCharCounting(trnsl, trW, trC, trCS); + } + + emit statsChanged(m_srcWords, m_srcChars, m_srcCharsSpc, trW, trC, trCS); +} + +void DataModel::setModified(bool isModified) +{ + if (m_modified == isModified) + return; + m_modified = isModified; + emit modifiedChanged(); +} + +QString DataModel::prettifyPlainFileName(const QString &fn) +{ + static QString workdir = QDir::currentPath() + QLatin1Char('/'); + + return QDir::toNativeSeparators(fn.startsWith(workdir) ? fn.mid(workdir.length()) : fn); +} + +QString DataModel::prettifyFileName(const QString &fn) +{ + if (fn.startsWith(QLatin1Char('='))) + return QLatin1Char('=') + prettifyPlainFileName(fn.mid(1)); + else + return prettifyPlainFileName(fn); +} + +/****************************************************************************** + * + * DataModelIterator + * + *****************************************************************************/ + +DataModelIterator::DataModelIterator(DataModel *model, int context, int message) + : DataIndex(context, message), m_model(model) +{ +} + +bool DataModelIterator::isValid() const +{ + return m_context < m_model->m_contextList.count(); +} + +void DataModelIterator::operator++() +{ + ++m_message; + if (m_message >= m_model->m_contextList.at(m_context).messageCount()) { + ++m_context; + m_message = 0; + } +} + +MessageItem *DataModelIterator::current() const +{ + return m_model->messageItem(*this); +} + + +/****************************************************************************** + * + * MultiMessageItem + * + *****************************************************************************/ + +MultiMessageItem::MultiMessageItem(const MessageItem *m) + : m_text(m->text()), + m_pluralText(m->pluralText()), + m_comment(m->comment()), + m_nonnullCount(0), + m_nonobsoleteCount(0), + m_editableCount(0), + m_unfinishedCount(0) +{ +} + +/****************************************************************************** + * + * MultiContextItem + * + *****************************************************************************/ + +MultiContextItem::MultiContextItem(int oldCount, ContextItem *ctx, bool writable) + : m_context(ctx->context()), + m_comment(ctx->comment()), + m_finishedCount(0), + m_editableCount(0), + m_nonobsoleteCount(0) +{ + QList<MessageItem *> mList; + QList<MessageItem *> eList; + for (int j = 0; j < ctx->messageCount(); ++j) { + MessageItem *m = ctx->messageItem(j); + mList.append(m); + eList.append(0); + m_multiMessageList.append(MultiMessageItem(m)); + } + for (int i = 0; i < oldCount; ++i) { + m_messageLists.append(eList); + m_writableMessageLists.append(0); + m_contextList.append(0); + } + m_messageLists.append(mList); + m_writableMessageLists.append(writable ? &m_messageLists.last() : 0); + m_contextList.append(ctx); +} + +void MultiContextItem::appendEmptyModel() +{ + QList<MessageItem *> eList; + for (int j = 0; j < messageCount(); ++j) + eList.append(0); + m_messageLists.append(eList); + m_writableMessageLists.append(0); + m_contextList.append(0); +} + +void MultiContextItem::assignLastModel(ContextItem *ctx, bool writable) +{ + if (writable) + m_writableMessageLists.last() = &m_messageLists.last(); + m_contextList.last() = ctx; +} + +// XXX this is not needed, yet +void MultiContextItem::moveModel(int oldPos, int newPos) +{ + m_contextList.insert(newPos, m_contextList[oldPos]); + m_messageLists.insert(newPos, m_messageLists[oldPos]); + m_writableMessageLists.insert(newPos, m_writableMessageLists[oldPos]); + removeModel(oldPos < newPos ? oldPos : oldPos + 1); +} + +void MultiContextItem::removeModel(int pos) +{ + m_contextList.removeAt(pos); + m_messageLists.removeAt(pos); + m_writableMessageLists.removeAt(pos); +} + +void MultiContextItem::putMessageItem(int pos, MessageItem *m) +{ + m_messageLists.last()[pos] = m; +} + +void MultiContextItem::appendMessageItems(const QList<MessageItem *> &m) +{ + QList<MessageItem *> nullItems = m; // Basically, just a reservation + for (int i = 0; i < nullItems.count(); ++i) + nullItems[i] = 0; + for (int i = 0; i < m_messageLists.count() - 1; ++i) + m_messageLists[i] += nullItems; + m_messageLists.last() += m; + foreach (MessageItem *mi, m) + m_multiMessageList.append(MultiMessageItem(mi)); +} + +void MultiContextItem::removeMultiMessageItem(int pos) +{ + for (int i = 0; i < m_messageLists.count(); ++i) + m_messageLists[i].removeAt(pos); + m_multiMessageList.removeAt(pos); +} + +int MultiContextItem::firstNonobsoleteMessageIndex(int msgIdx) const +{ + for (int i = 0; i < m_messageLists.size(); ++i) + if (m_messageLists[i][msgIdx] && !m_messageLists[i][msgIdx]->isObsolete()) + return i; + return -1; +} + +int MultiContextItem::findMessage(const QString &sourcetext, const QString &comment) const +{ + for (int i = 0, cnt = messageCount(); i < cnt; ++i) { + MultiMessageItem *m = multiMessageItem(i); + if (m->text() == sourcetext && m->comment() == comment) + return i; + } + return -1; +} + +/****************************************************************************** + * + * MultiDataModel + * + *****************************************************************************/ + +static const uchar paletteRGBs[7][3] = { + { 236, 244, 255 }, // blue + { 236, 255, 255 }, // cyan + { 236, 255, 232 }, // green + { 255, 255, 230 }, // yellow + { 255, 242, 222 }, // orange + { 255, 236, 236 }, // red + { 252, 236, 255 } // purple +}; + +MultiDataModel::MultiDataModel(QObject *parent) : + QObject(parent), + m_numFinished(0), + m_numEditable(0), + m_numMessages(0), + m_modified(false) +{ + for (int i = 0; i < 7; ++i) + m_colors[i] = QColor(paletteRGBs[i][0], paletteRGBs[i][1], paletteRGBs[i][2]); + + m_bitmap = QBitmap(8, 8); + m_bitmap.clear(); + QPainter p(&m_bitmap); + for (int j = 0; j < 8; ++j) + for (int k = 0; k < 8; ++k) + if ((j + k) & 4) + p.drawPoint(j, k); +} + +MultiDataModel::~MultiDataModel() +{ + qDeleteAll(m_dataModels); +} + +QBrush MultiDataModel::brushForModel(int model) const +{ + QBrush brush(m_colors[model % 7]); + if (!isModelWritable(model)) + brush.setTexture(m_bitmap); + return brush; +} + +bool MultiDataModel::isWellMergeable(const DataModel *dm) const +{ + if (!dm->messageCount() || !messageCount()) + return true; + + int inBothNew = 0; + for (int i = 0; i < dm->contextCount(); ++i) { + ContextItem *c = dm->contextItem(i); + if (MultiContextItem *mc = findContext(c->context())) { + for (int j = 0; j < c->messageCount(); ++j) { + MessageItem *m = c->messageItem(j); + if (mc->findMessage(m->text(), m->comment()) >= 0) + ++inBothNew; + } + } + } + int newRatio = inBothNew * 100 / dm->messageCount(); + + int inBothOld = 0; + for (int k = 0; k < contextCount(); ++k) { + MultiContextItem *mc = multiContextItem(k); + if (ContextItem *c = dm->findContext(mc->context())) { + for (int j = 0; j < mc->messageCount(); ++j) { + MultiMessageItem *m = mc->multiMessageItem(j); + if (c->findMessage(m->text(), m->comment())) + ++inBothOld; + } + } + } + int oldRatio = inBothOld * 100 / messageCount(); + + return newRatio + oldRatio > 90; +} + +void MultiDataModel::append(DataModel *dm, bool readWrite) +{ + int insCol = modelCount() + 1; + m_msgModel->beginInsertColumns(QModelIndex(), insCol, insCol); + m_dataModels.append(dm); + for (int j = 0; j < contextCount(); ++j) { + m_msgModel->beginInsertColumns(m_msgModel->createIndex(j, 0, 0), insCol, insCol); + m_multiContextList[j].appendEmptyModel(); + m_msgModel->endInsertColumns(); + } + m_msgModel->endInsertColumns(); + int appendedContexts = 0; + for (int i = 0; i < dm->contextCount(); ++i) { + ContextItem *c = dm->contextItem(i); + int mcx = findContextIndex(c->context()); + if (mcx >= 0) { + MultiContextItem *mc = multiContextItem(mcx); + mc->assignLastModel(c, readWrite); + QList<MessageItem *> appendItems; + for (int j = 0; j < c->messageCount(); ++j) { + MessageItem *m = c->messageItem(j); + int msgIdx = mc->findMessage(m->text(), m->comment()); + if (msgIdx >= 0) + mc->putMessageItem(msgIdx, m); + else + appendItems << m; + } + if (!appendItems.isEmpty()) { + int msgCnt = mc->messageCount(); + m_msgModel->beginInsertRows(m_msgModel->createIndex(mcx, 0, 0), + msgCnt, msgCnt + appendItems.size() - 1); + mc->appendMessageItems(appendItems); + m_msgModel->endInsertRows(); + m_numMessages += appendItems.size(); + } + } else { + m_multiContextList << MultiContextItem(modelCount() - 1, c, readWrite); + m_numMessages += c->messageCount(); + ++appendedContexts; + } + } + if (appendedContexts) { + // Do that en block to avoid itemview inefficiency. It doesn't hurt that we + // announce the availability of the data "long" after it was actually added. + m_msgModel->beginInsertRows(QModelIndex(), + contextCount() - appendedContexts, contextCount() - 1); + m_msgModel->endInsertRows(); + } + dm->setWritable(readWrite); + updateCountsOnAdd(modelCount() - 1, readWrite); + connect(dm, SIGNAL(modifiedChanged()), SLOT(onModifiedChanged())); + connect(dm, SIGNAL(languageChanged()), SLOT(onLanguageChanged())); + connect(dm, SIGNAL(statsChanged(int,int,int,int,int,int)), SIGNAL(statsChanged(int,int,int,int,int,int))); + emit modelAppended(); +} + +void MultiDataModel::close(int model) +{ + if (m_dataModels.count() == 1) { + closeAll(); + } else { + updateCountsOnRemove(model, isModelWritable(model)); + int delCol = model + 1; + m_msgModel->beginRemoveColumns(QModelIndex(), delCol, delCol); + for (int i = m_multiContextList.size(); --i >= 0;) { + m_msgModel->beginRemoveColumns(m_msgModel->createIndex(i, 0, 0), delCol, delCol); + m_multiContextList[i].removeModel(model); + m_msgModel->endRemoveColumns(); + } + delete m_dataModels.takeAt(model); + m_msgModel->endRemoveColumns(); + emit modelDeleted(model); + for (int i = m_multiContextList.size(); --i >= 0;) { + MultiContextItem &mc = m_multiContextList[i]; + QModelIndex contextIdx = m_msgModel->createIndex(i, 0, 0); + for (int j = mc.messageCount(); --j >= 0;) + if (mc.multiMessageItem(j)->isEmpty()) { + m_msgModel->beginRemoveRows(contextIdx, j, j); + mc.removeMultiMessageItem(j); + m_msgModel->endRemoveRows(); + --m_numMessages; + } + if (!mc.messageCount()) { + m_msgModel->beginRemoveRows(QModelIndex(), i, i); + m_multiContextList.removeAt(i); + m_msgModel->endRemoveRows(); + } + } + onModifiedChanged(); + } +} + +void MultiDataModel::closeAll() +{ + m_numFinished = 0; + m_numEditable = 0; + m_numMessages = 0; + qDeleteAll(m_dataModels); + m_dataModels.clear(); + m_multiContextList.clear(); + m_msgModel->reset(); + emit allModelsDeleted(); + onModifiedChanged(); +} + +// XXX this is not needed, yet +void MultiDataModel::moveModel(int oldPos, int newPos) +{ + int delPos = oldPos < newPos ? oldPos : oldPos + 1; + m_dataModels.insert(newPos, m_dataModels[oldPos]); + m_dataModels.removeAt(delPos); + for (int i = 0; i < m_multiContextList.size(); ++i) + m_multiContextList[i].moveModel(oldPos, newPos); +} + +QStringList MultiDataModel::prettifyFileNames(const QStringList &names) +{ + QStringList out; + + foreach (const QString &name, names) + out << DataModel::prettifyFileName(name); + return out; +} + +QString MultiDataModel::condenseFileNames(const QStringList &names) +{ + if (names.isEmpty()) + return QString(); + + if (names.count() < 2) + return names.first(); + + QString prefix = names.first(); + if (prefix.startsWith(QLatin1Char('='))) + prefix.remove(0, 1); + QString suffix = prefix; + for (int i = 1; i < names.count(); ++i) { + QString fn = names[i]; + if (fn.startsWith(QLatin1Char('='))) + fn.remove(0, 1); + for (int j = 0; j < prefix.length(); ++j) + if (fn[j] != prefix[j]) { + if (j < prefix.length()) { + while (j > 0 && prefix[j - 1].isLetterOrNumber()) + --j; + prefix.truncate(j); + } + break; + } + int fnl = fn.length() - 1; + int sxl = suffix.length() - 1; + for (int k = 0; k <= sxl; ++k) + if (fn[fnl - k] != suffix[sxl - k]) { + if (k < sxl) { + while (k > 0 && suffix[sxl - k + 1].isLetterOrNumber()) + --k; + if (prefix.length() + k > fnl) + --k; + suffix.remove(0, sxl - k + 1); + } + break; + } + } + QString ret = prefix + QLatin1Char('{'); + int pxl = prefix.length(); + int sxl = suffix.length(); + for (int j = 0; j < names.count(); ++j) { + if (j) + ret += QLatin1Char(','); + int off = pxl; + QString fn = names[j]; + if (fn.startsWith(QLatin1Char('='))) { + ret += QLatin1Char('='); + ++off; + } + ret += fn.mid(off, fn.length() - sxl - off); + } + ret += QLatin1Char('}') + suffix; + return ret; +} + +QStringList MultiDataModel::srcFileNames(bool pretty) const +{ + QStringList names; + foreach (DataModel *dm, m_dataModels) + names << (dm->isWritable() ? QString() : QString::fromLatin1("=")) + dm->srcFileName(pretty); + return names; +} + +QString MultiDataModel::condensedSrcFileNames(bool pretty) const +{ + return condenseFileNames(srcFileNames(pretty)); +} + +bool MultiDataModel::isModified() const +{ + foreach (const DataModel *mdl, m_dataModels) + if (mdl->isModified()) + return true; + return false; +} + +void MultiDataModel::onModifiedChanged() +{ + bool modified = isModified(); + if (modified != m_modified) { + emit modifiedChanged(modified); + m_modified = modified; + } +} + +void MultiDataModel::onLanguageChanged() +{ + int i = 0; + while (sender() != m_dataModels[i]) + ++i; + emit languageChanged(i); +} + +int MultiDataModel::isFileLoaded(const QString &name) const +{ + for (int i = 0; i < m_dataModels.size(); ++i) + if (m_dataModels[i]->srcFileName() == name) + return i; + return -1; +} + +int MultiDataModel::findContextIndex(const QString &context) const +{ + for (int i = 0; i < m_multiContextList.size(); ++i) { + const MultiContextItem &mc = m_multiContextList[i]; + if (mc.context() == context) + return i; + } + return -1; +} + +MultiContextItem *MultiDataModel::findContext(const QString &context) const +{ + for (int i = 0; i < m_multiContextList.size(); ++i) { + const MultiContextItem &mc = m_multiContextList[i]; + if (mc.context() == context) + return const_cast<MultiContextItem *>(&mc); + } + return 0; +} + +MessageItem *MultiDataModel::messageItem(const MultiDataIndex &index, int model) const +{ + if (index.context() < contextCount() && model >= 0 && model < modelCount()) { + MultiContextItem *mc = multiContextItem(index.context()); + if (index.message() < mc->messageCount()) + return mc->messageItem(model, index.message()); + } + Q_ASSERT(model >= 0 && model < modelCount()); + Q_ASSERT(index.context() < contextCount()); + return 0; +} + +void MultiDataModel::setTranslation(const MultiDataIndex &index, const QString &translation) +{ + MessageItem *m = messageItem(index); + if (translation == m->translation()) + return; + m->setTranslation(translation); + setModified(index.model(), true); + emit translationChanged(index); +} + +void MultiDataModel::setFinished(const MultiDataIndex &index, bool finished) +{ + MultiContextItem *mc = multiContextItem(index.context()); + MultiMessageItem *mm = mc->multiMessageItem(index.message()); + ContextItem *c = contextItem(index); + MessageItem *m = messageItem(index); + TranslatorMessage::Type type = m->type(); + if (type == TranslatorMessage::Unfinished && finished) { + m->setType(TranslatorMessage::Finished); + mm->decrementUnfinishedCount(); + if (!mm->countUnfinished()) { + incrementFinishedCount(); + mc->incrementFinishedCount(); + emit multiContextDataChanged(index); + } + c->incrementFinishedCount(); + if (m->danger()) { + c->incrementFinishedDangerCount(); + c->decrementUnfinishedDangerCount(); + if (!c->unfinishedDangerCount() + || c->finishedCount() == c->nonobsoleteCount()) + emit contextDataChanged(index); + } else if (c->finishedCount() == c->nonobsoleteCount()) { + emit contextDataChanged(index); + } + emit messageDataChanged(index); + setModified(index.model(), true); + } else if (type == TranslatorMessage::Finished && !finished) { + m->setType(TranslatorMessage::Unfinished); + mm->incrementUnfinishedCount(); + if (mm->countUnfinished() == 1) { + decrementFinishedCount(); + mc->decrementFinishedCount(); + emit multiContextDataChanged(index); + } + c->decrementFinishedCount(); + if (m->danger()) { + c->decrementFinishedDangerCount(); + c->incrementUnfinishedDangerCount(); + if (c->unfinishedDangerCount() == 1 + || c->finishedCount() + 1 == c->nonobsoleteCount()) + emit contextDataChanged(index); + } else if (c->finishedCount() + 1 == c->nonobsoleteCount()) { + emit contextDataChanged(index); + } + emit messageDataChanged(index); + setModified(index.model(), true); + } +} + +void MultiDataModel::setDanger(const MultiDataIndex &index, bool danger) +{ + ContextItem *c = contextItem(index); + MessageItem *m = messageItem(index); + if (!m->danger() && danger) { + if (m->isFinished()) { + c->incrementFinishedDangerCount(); + if (c->finishedDangerCount() == 1) + emit contextDataChanged(index); + } else { + c->incrementUnfinishedDangerCount(); + if (c->unfinishedDangerCount() == 1) + emit contextDataChanged(index); + } + emit messageDataChanged(index); + m->setDanger(danger); + } else if (m->danger() && !danger) { + if (m->isFinished()) { + c->decrementFinishedDangerCount(); + if (!c->finishedDangerCount()) + emit contextDataChanged(index); + } else { + c->decrementUnfinishedDangerCount(); + if (!c->unfinishedDangerCount()) + emit contextDataChanged(index); + } + emit messageDataChanged(index); + m->setDanger(danger); + } +} + +void MultiDataModel::updateCountsOnAdd(int model, bool writable) +{ + for (int i = 0; i < m_multiContextList.size(); ++i) { + MultiContextItem &mc = m_multiContextList[i]; + for (int j = 0; j < mc.messageCount(); ++j) + if (MessageItem *m = mc.messageItem(model, j)) { + MultiMessageItem *mm = mc.multiMessageItem(j); + mm->incrementNonnullCount(); + if (!m->isObsolete()) { + if (writable) { + if (!mm->countEditable()) { + mc.incrementEditableCount(); + incrementEditableCount(); + if (m->isFinished()) { + mc.incrementFinishedCount(); + incrementFinishedCount(); + } else { + mm->incrementUnfinishedCount(); + } + } else if (!m->isFinished()) { + if (!mm->isUnfinished()) { + mc.decrementFinishedCount(); + decrementFinishedCount(); + } + mm->incrementUnfinishedCount(); + } + mm->incrementEditableCount(); + } + mc.incrementNonobsoleteCount(); + mm->incrementNonobsoleteCount(); + } + } + } +} + +void MultiDataModel::updateCountsOnRemove(int model, bool writable) +{ + for (int i = 0; i < m_multiContextList.size(); ++i) { + MultiContextItem &mc = m_multiContextList[i]; + for (int j = 0; j < mc.messageCount(); ++j) + if (MessageItem *m = mc.messageItem(model, j)) { + MultiMessageItem *mm = mc.multiMessageItem(j); + mm->decrementNonnullCount(); + if (!m->isObsolete()) { + mm->decrementNonobsoleteCount(); + mc.decrementNonobsoleteCount(); + if (writable) { + mm->decrementEditableCount(); + if (!mm->countEditable()) { + mc.decrementEditableCount(); + decrementEditableCount(); + if (m->isFinished()) { + mc.decrementFinishedCount(); + decrementFinishedCount(); + } else { + mm->decrementUnfinishedCount(); + } + } else if (!m->isFinished()) { + mm->decrementUnfinishedCount(); + if (!mm->isUnfinished()) { + mc.incrementFinishedCount(); + incrementFinishedCount(); + } + } + } + } + } + } +} + +/****************************************************************************** + * + * MultiDataModelIterator + * + *****************************************************************************/ + +MultiDataModelIterator::MultiDataModelIterator(MultiDataModel *dataModel, int model, int context, int message) + : MultiDataIndex(model, context, message), m_dataModel(dataModel) +{ +} + +void MultiDataModelIterator::operator++() +{ + Q_ASSERT(isValid()); + ++m_message; + if (m_message >= m_dataModel->m_multiContextList.at(m_context).messageCount()) { + ++m_context; + m_message = 0; + } +} + +bool MultiDataModelIterator::isValid() const +{ + return m_context < m_dataModel->m_multiContextList.count(); +} + +MessageItem *MultiDataModelIterator::current() const +{ + return m_dataModel->messageItem(*this); +} + + +/****************************************************************************** + * + * MessageModel + * + *****************************************************************************/ + +MessageModel::MessageModel(QObject *parent, MultiDataModel *data) + : QAbstractItemModel(parent), m_data(data) +{ + data->m_msgModel = this; + connect(m_data, SIGNAL(multiContextDataChanged(MultiDataIndex)), + SLOT(multiContextItemChanged(MultiDataIndex))); + connect(m_data, SIGNAL(contextDataChanged(MultiDataIndex)), + SLOT(contextItemChanged(MultiDataIndex))); + connect(m_data, SIGNAL(messageDataChanged(MultiDataIndex)), + SLOT(messageItemChanged(MultiDataIndex))); +} + +QModelIndex MessageModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid()) + return createIndex(row, column, 0); + if (!parent.internalId()) + return createIndex(row, column, parent.row() + 1); + return QModelIndex(); +} + +QModelIndex MessageModel::parent(const QModelIndex& index) const +{ + if (index.internalId()) + return createIndex(index.internalId() - 1, 0, 0); + return QModelIndex(); +} + +void MessageModel::multiContextItemChanged(const MultiDataIndex &index) +{ + QModelIndex idx = createIndex(index.context(), m_data->modelCount() + 2, 0); + emit dataChanged(idx, idx); +} + +void MessageModel::contextItemChanged(const MultiDataIndex &index) +{ + QModelIndex idx = createIndex(index.context(), index.model() + 1, 0); + emit dataChanged(idx, idx); +} + +void MessageModel::messageItemChanged(const MultiDataIndex &index) +{ + QModelIndex idx = createIndex(index.message(), index.model() + 1, index.context() + 1); + emit dataChanged(idx, idx); +} + +QModelIndex MessageModel::modelIndex(const MultiDataIndex &index) +{ + if (index.message() < 0) // Should be unused case + return createIndex(index.context(), index.model() + 1, 0); + return createIndex(index.message(), index.model() + 1, index.context() + 1); +} + +int MessageModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return m_data->contextCount(); // contexts + if (!parent.internalId()) // messages + return m_data->multiContextItem(parent.row())->messageCount(); + return 0; +} + +int MessageModel::columnCount(const QModelIndex &) const +{ + return m_data->modelCount() + 3; +} + +QVariant MessageModel::data(const QModelIndex &index, int role) const +{ + static QVariant pxOn = + QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_on.png"))); + static QVariant pxOff = + QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_off.png"))); + static QVariant pxObsolete = + QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_obsolete.png"))); + static QVariant pxDanger = + QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_danger.png"))); + static QVariant pxWarning = + QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_warning.png"))); + static QVariant pxEmpty = + QVariant::fromValue(QPixmap(QLatin1String(":/images/s_check_empty.png"))); + + int row = index.row(); + int column = index.column() - 1; + if (column < 0) + return QVariant(); + + int numLangs = m_data->modelCount(); + + if (role == Qt::ToolTipRole && column < numLangs) { + return tr("Completion status for %1").arg(m_data->model(column)->localizedLanguage()); + } else if (index.internalId()) { + // this is a message + int crow = index.internalId() - 1; + MultiContextItem *mci = m_data->multiContextItem(crow); + if (row >= mci->messageCount() || !index.isValid()) + return QVariant(); + + if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column == numLangs)) { + switch (column - numLangs) { + case 0: // Source text + { + MultiMessageItem *msgItem = mci->multiMessageItem(row); + if (msgItem->text().isEmpty()) { + if (mci->context().isEmpty()) + return tr("<file header>"); + else + return tr("<context comment>"); + } + return msgItem->text().simplified(); + } + default: // Status or dummy column => no text + return QVariant(); + } + } + else if (role == Qt::DecorationRole && column < numLangs) { + if (MessageItem *msgItem = mci->messageItem(column, row)) { + switch (msgItem->message().type()) { + case TranslatorMessage::Unfinished: + if (msgItem->translation().isEmpty()) + return pxEmpty; + if (msgItem->danger()) + return pxDanger; + return pxOff; + case TranslatorMessage::Finished: + if (msgItem->danger()) + return pxWarning; + return pxOn; + default: + return pxObsolete; + } + } + return QVariant(); + } + else if (role == SortRole) { + switch (column - numLangs) { + case 0: // Source text + return mci->multiMessageItem(row)->text().simplified().remove(QLatin1Char('&')); + case 1: // Dummy column + return QVariant(); + default: + if (MessageItem *msgItem = mci->messageItem(column, row)) { + int rslt = !msgItem->translation().isEmpty(); + if (!msgItem->danger()) + rslt |= 2; + if (msgItem->isObsolete()) + rslt |= 8; + else if (msgItem->isFinished()) + rslt |= 4; + return rslt; + } + return INT_MAX; + } + } + else if (role == Qt::ForegroundRole && column > 0 + && mci->multiMessageItem(row)->isObsolete()) { + return QBrush(Qt::darkGray); + } + else if (role == Qt::ForegroundRole && column == numLangs + && mci->multiMessageItem(row)->text().isEmpty()) { + return QBrush(QColor(0, 0xa0, 0xa0)); + } + else if (role == Qt::BackgroundRole) { + if (column < numLangs && numLangs != 1) + return m_data->brushForModel(column); + } + } else { + // this is a context + if (row >= m_data->contextCount() || !index.isValid()) + return QVariant(); + + MultiContextItem *mci = m_data->multiContextItem(row); + + if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column == numLangs)) { + switch (column - numLangs) { + case 0: // Context + { + if (mci->context().isEmpty()) + return tr("<unnamed context>"); + return mci->context().simplified(); + } + case 1: + { + QString s; + s.sprintf("%d/%d", mci->getNumFinished(), mci->getNumEditable()); + return s; + } + default: + return QVariant(); // Status => no text + } + } + else if (role == Qt::DecorationRole && column < numLangs) { + if (ContextItem *contextItem = mci->contextItem(column)) { + if (contextItem->isObsolete()) + return pxObsolete; + if (contextItem->isFinished()) + return contextItem->finishedDangerCount() > 0 ? pxWarning : pxOn; + return contextItem->unfinishedDangerCount() > 0 ? pxDanger : pxOff; + } + return QVariant(); + } + else if (role == SortRole) { + switch (column - numLangs) { + case 0: // Context (same as display role) + return mci->context().simplified(); + case 1: // Items + return mci->getNumEditable(); + default: // Percent + if (ContextItem *contextItem = mci->contextItem(column)) { + int totalItems = contextItem->nonobsoleteCount(); + int percent = totalItems ? (100 * contextItem->finishedCount()) / totalItems : 100; + int rslt = percent * (((1 << 28) - 1) / 100) + totalItems; + if (contextItem->isObsolete()) { + rslt |= (1 << 30); + } else if (contextItem->isFinished()) { + rslt |= (1 << 29); + if (!contextItem->finishedDangerCount()) + rslt |= (1 << 28); + } else { + if (!contextItem->unfinishedDangerCount()) + rslt |= (1 << 28); + } + return rslt; + } + return INT_MAX; + } + } + else if (role == Qt::ForegroundRole && column >= numLangs + && m_data->multiContextItem(row)->isObsolete()) { + return QBrush(Qt::darkGray); + } + else if (role == Qt::ForegroundRole && column == numLangs + && m_data->multiContextItem(row)->context().isEmpty()) { + return QBrush(QColor(0, 0xa0, 0xa0)); + } + else if (role == Qt::BackgroundRole) { + if (column < numLangs && numLangs != 1) { + QBrush brush = m_data->brushForModel(column); + if (row & 1) { + brush.setColor(brush.color().darker(108)); + } + return brush; + } + } + } + return QVariant(); +} + +MultiDataIndex MessageModel::dataIndex(const QModelIndex &index, int model) const +{ + Q_ASSERT(index.isValid()); + Q_ASSERT(index.internalId()); + return MultiDataIndex(model, index.internalId() - 1, index.row()); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/messagemodel.h b/src/linguist/linguist/messagemodel.h new file mode 100644 index 000000000..d8ca9d8e8 --- /dev/null +++ b/src/linguist/linguist/messagemodel.h @@ -0,0 +1,535 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MESSAGEMODEL_H +#define MESSAGEMODEL_H + +#include "translator.h" + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QLocale> +#include <QtGui/QColor> +#include <QtGui/QBitmap> +#include <QtXml/QXmlDefaultHandler> + + +QT_BEGIN_NAMESPACE + +class DataModel; +class MultiDataModel; + +class MessageItem +{ +public: + MessageItem(const TranslatorMessage &message); + + bool danger() const { return m_danger; } + void setDanger(bool danger) { m_danger = danger; } + + void setTranslation(const QString &translation) + { m_message.setTranslation(translation); } + + QString context() const { return m_message.context(); } + QString text() const { return m_message.sourceText(); } + QString pluralText() const { return m_message.extra(QLatin1String("po-msgid_plural")); } + QString comment() const { return m_message.comment(); } + QString fileName() const { return m_message.fileName(); } + QString extraComment() const { return m_message.extraComment(); } + QString translatorComment() const { return m_message.translatorComment(); } + void setTranslatorComment(const QString &cmt) { m_message.setTranslatorComment(cmt); } + int lineNumber() const { return m_message.lineNumber(); } + QString translation() const { return m_message.translation(); } + QStringList translations() const { return m_message.translations(); } + void setTranslations(const QStringList &translations) + { m_message.setTranslations(translations); } + + TranslatorMessage::Type type() const { return m_message.type(); } + void setType(TranslatorMessage::Type type) { m_message.setType(type); } + + bool isFinished() const { return type() == TranslatorMessage::Finished; } + bool isObsolete() const { return type() == TranslatorMessage::Obsolete; } + const TranslatorMessage &message() const { return m_message; } + + bool compare(const QString &findText, bool matchSubstring, + Qt::CaseSensitivity cs) const; + +private: + TranslatorMessage m_message; + bool m_danger; +}; + + +class ContextItem +{ +public: + ContextItem(const QString &context); + + int finishedDangerCount() const { return m_finishedDangerCount; } + int unfinishedDangerCount() const { return m_unfinishedDangerCount; } + + int finishedCount() const { return m_finishedCount; } + int unfinishedCount() const { return m_nonobsoleteCount - m_finishedCount; } + int nonobsoleteCount() const { return m_nonobsoleteCount; } + + QString context() const { return m_context; } + QString comment() const { return m_comment; } + QString fullContext() const { return m_comment.trimmed(); } + + // For item status in context list + bool isObsolete() const { return !nonobsoleteCount(); } + bool isFinished() const { return unfinishedCount() == 0; } + + MessageItem *messageItem(int i) const; + int messageCount() const { return msgItemList.count(); } + + MessageItem *findMessage(const QString &sourcetext, const QString &comment) const; + +private: + friend class DataModel; + friend class MultiDataModel; + void appendMessage(const MessageItem &msg) { msgItemList.append(msg); } + void appendToComment(const QString &x); + void incrementFinishedCount() { ++m_finishedCount; } + void decrementFinishedCount() { --m_finishedCount; } + void incrementFinishedDangerCount() { ++m_finishedDangerCount; } + void decrementFinishedDangerCount() { --m_finishedDangerCount; } + void incrementUnfinishedDangerCount() { ++m_unfinishedDangerCount; } + void decrementUnfinishedDangerCount() { --m_unfinishedDangerCount; } + void incrementNonobsoleteCount() { ++m_nonobsoleteCount; } + + QString m_comment; + QString m_context; + int m_finishedCount; + int m_finishedDangerCount; + int m_unfinishedDangerCount; + int m_nonobsoleteCount; + QList<MessageItem> msgItemList; +}; + + +class DataIndex +{ +public: + DataIndex() : m_context(-1), m_message(-1) {} + DataIndex(int context, int message) : m_context(context), m_message(message) {} + int context() const { return m_context; } + int message() const { return m_message; } + bool isValid() const { return m_context >= 0; } +protected: + int m_context; + int m_message; +}; + + +class DataModelIterator : public DataIndex +{ +public: + DataModelIterator(DataModel *model, int contextNo = 0, int messageNo = 0); + MessageItem *current() const; + bool isValid() const; + void operator++(); +private: + DataModelIterator() {} + DataModel *m_model; // not owned +}; + + +class DataModel : public QObject +{ + Q_OBJECT +public: + DataModel(QObject *parent = 0); + + enum FindLocation { NoLocation = 0, SourceText = 0x1, Translations = 0x2, Comments = 0x4 }; + + // Specializations + int contextCount() const { return m_contextList.count(); } + ContextItem *findContext(const QString &context) const; + MessageItem *findMessage(const QString &context, const QString &sourcetext, + const QString &comment) const; + + ContextItem *contextItem(int index) const; + MessageItem *messageItem(const DataIndex &index) const; + + int messageCount() const { return m_numMessages; } + bool isEmpty() const { return m_numMessages == 0; } + bool isModified() const { return m_modified; } + void setModified(bool dirty); + bool isWritable() const { return m_writable; } + void setWritable(bool writable) { m_writable = writable; } + + bool isWellMergeable(const DataModel *other) const; + bool load(const QString &fileName, bool *langGuessed, QWidget *parent); + bool save(QWidget *parent) { return save(m_srcFileName, parent); } + bool saveAs(const QString &newFileName, QWidget *parent); + bool release(const QString &fileName, bool verbose, + bool ignoreUnfinished, TranslatorSaveMode mode, QWidget *parent); + QString srcFileName(bool pretty = false) const + { return pretty ? prettifyPlainFileName(m_srcFileName) : m_srcFileName; } + + static QString prettifyPlainFileName(const QString &fn); + static QString prettifyFileName(const QString &fn); + + bool setLanguageAndCountry(QLocale::Language lang, QLocale::Country country); + QLocale::Language language() const { return m_language; } + QLocale::Country country() const { return m_country; } + void setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country); + QLocale::Language sourceLanguage() const { return m_sourceLanguage; } + QLocale::Country sourceCountry() const { return m_sourceCountry; } + + const QString &localizedLanguage() const { return m_localizedLanguage; } + const QStringList &numerusForms() const { return m_numerusForms; } + const QList<bool> &countRefNeeds() const { return m_countRefNeeds; } + + QStringList normalizedTranslations(const MessageItem &m) const; + void doCharCounting(const QString& text, int& trW, int& trC, int& trCS); + void updateStatistics(); + + int getSrcWords() const { return m_srcWords; } + int getSrcChars() const { return m_srcChars; } + int getSrcCharsSpc() const { return m_srcCharsSpc; } + +signals: + void statsChanged(int words, int characters, int cs, int words2, int characters2, int cs2); + void progressChanged(int finishedCount, int oldFinishedCount); + void languageChanged(); + void modifiedChanged(); + +private: + friend class DataModelIterator; + QList<ContextItem> m_contextList; + + bool save(const QString &fileName, QWidget *parent); + void updateLocale(); + + bool m_writable; + bool m_modified; + + int m_numMessages; + + // For statistics + int m_srcWords; + int m_srcChars; + int m_srcCharsSpc; + + QString m_srcFileName; + QLocale::Language m_language; + QLocale::Language m_sourceLanguage; + QLocale::Country m_country; + QLocale::Country m_sourceCountry; + QByteArray m_codecName; + bool m_relativeLocations; + Translator::ExtraData m_extra; + + QString m_localizedLanguage; + QStringList m_numerusForms; + QList<bool> m_countRefNeeds; +}; + + +struct MultiMessageItem +{ +public: + MultiMessageItem(const MessageItem *m); + QString text() const { return m_text; } + QString pluralText() const { return m_pluralText; } + QString comment() const { return m_comment; } + bool isEmpty() const { return !m_nonnullCount; } + // The next two include also read-only + bool isObsolete() const { return m_nonnullCount && !m_nonobsoleteCount; } + int countNonobsolete() const { return m_nonobsoleteCount; } + // The next three include only read-write + int countEditable() const { return m_editableCount; } + bool isUnfinished() const { return m_unfinishedCount != 0; } + int countUnfinished() const { return m_unfinishedCount; } + +private: + friend class MultiDataModel; + void incrementNonnullCount() { ++m_nonnullCount; } + void decrementNonnullCount() { --m_nonnullCount; } + void incrementNonobsoleteCount() { ++m_nonobsoleteCount; } + void decrementNonobsoleteCount() { --m_nonobsoleteCount; } + void incrementEditableCount() { ++m_editableCount; } + void decrementEditableCount() { --m_editableCount; } + void incrementUnfinishedCount() { ++m_unfinishedCount; } + void decrementUnfinishedCount() { --m_unfinishedCount; } + + QString m_text; + QString m_pluralText; + QString m_comment; + int m_nonnullCount; // all + int m_nonobsoleteCount; // all + int m_editableCount; // read-write + int m_unfinishedCount; // read-write +}; + +struct MultiContextItem +{ +public: + MultiContextItem(int oldCount, ContextItem *ctx, bool writable); + + ContextItem *contextItem(int model) const { return m_contextList[model]; } + + MultiMessageItem *multiMessageItem(int msgIdx) const + { return const_cast<MultiMessageItem *>(&m_multiMessageList[msgIdx]); } + MessageItem *messageItem(int model, int msgIdx) const { return m_messageLists[model][msgIdx]; } + int firstNonobsoleteMessageIndex(int msgIdx) const; + int findMessage(const QString &sourcetext, const QString &comment) const; + + QString context() const { return m_context; } + QString comment() const { return m_comment; } + int messageCount() const { return m_messageLists.isEmpty() ? 0 : m_messageLists[0].count(); } + // For item count in context list + int getNumFinished() const { return m_finishedCount; } + int getNumEditable() const { return m_editableCount; } + // For background in context list + bool isObsolete() const { return messageCount() && !m_nonobsoleteCount; } + +private: + friend class MultiDataModel; + void appendEmptyModel(); + void assignLastModel(ContextItem *ctx, bool writable); + void removeModel(int pos); + void moveModel(int oldPos, int newPos); // newPos is *before* removing at oldPos + void putMessageItem(int pos, MessageItem *m); + void appendMessageItems(const QList<MessageItem *> &m); + void removeMultiMessageItem(int pos); + void incrementFinishedCount() { ++m_finishedCount; } + void decrementFinishedCount() { --m_finishedCount; } + void incrementEditableCount() { ++m_editableCount; } + void decrementEditableCount() { --m_editableCount; } + void incrementNonobsoleteCount() { ++m_nonobsoleteCount; } + void decrementNonobsoleteCount() { --m_nonobsoleteCount; } + + QString m_context; + QString m_comment; + QList<MultiMessageItem> m_multiMessageList; + QList<ContextItem *> m_contextList; + // The next two could be in the MultiMessageItems, but are here for efficiency + QList<QList<MessageItem *> > m_messageLists; + QList<QList<MessageItem *> *> m_writableMessageLists; + int m_finishedCount; // read-write + int m_editableCount; // read-write + int m_nonobsoleteCount; // all (note: this counts messages, not multi-messages) +}; + + +class MultiDataIndex +{ +public: + MultiDataIndex() : m_model(-1), m_context(-1), m_message(-1) {} + MultiDataIndex(int model, int context, int message) + : m_model(model), m_context(context), m_message(message) {} + void setModel(int model) { m_model = model; } + int model() const { return m_model; } + int context() const { return m_context; } + int message() const { return m_message; } + bool isValid() const { return m_context >= 0; } + bool operator==(const MultiDataIndex &other) const + { return m_model == other.m_model && m_context == other.m_context && m_message == other.m_message; } + bool operator!=(const MultiDataIndex &other) const { return !(*this == other); } +protected: + int m_model; + int m_context; + int m_message; +}; + + +class MultiDataModelIterator : public MultiDataIndex +{ +public: + MultiDataModelIterator(MultiDataModel *model, int modelNo, int contextNo = 0, int messageNo = 0); + MessageItem *current() const; + bool isValid() const; + void operator++(); +private: + MultiDataModelIterator() {} + MultiDataModel *m_dataModel; // not owned +}; + + +class MessageModel; + +class MultiDataModel : public QObject +{ + Q_OBJECT + +public: + MultiDataModel(QObject *parent = 0); + ~MultiDataModel(); + + bool isWellMergeable(const DataModel *dm) const; + void append(DataModel *dm, bool readWrite); + bool save(int model, QWidget *parent) { return m_dataModels[model]->save(parent); } + bool saveAs(int model, const QString &newFileName, QWidget *parent) + { return m_dataModels[model]->saveAs(newFileName, parent); } + bool release(int model, const QString &fileName, bool verbose, bool ignoreUnfinished, TranslatorSaveMode mode, QWidget *parent) + { return m_dataModels[model]->release(fileName, verbose, ignoreUnfinished, mode, parent); } + void close(int model); + void closeAll(); + int isFileLoaded(const QString &name) const; + void moveModel(int oldPos, int newPos); // newPos is *before* removing at oldPos; note that this does not emit update signals + + // Entire multi-model + int modelCount() const { return m_dataModels.count(); } + int contextCount() const { return m_multiContextList.count(); } + int messageCount() const { return m_numMessages; } + // Next two needed for progress indicator in main window + int getNumFinished() const { return m_numFinished; } + int getNumEditable() const { return m_numEditable; } + bool isModified() const; + QStringList srcFileNames(bool pretty = false) const; + QString condensedSrcFileNames(bool pretty = false) const; + + // Per submodel + QString srcFileName(int model, bool pretty = false) const { return m_dataModels[model]->srcFileName(pretty); } + bool isModelWritable(int model) const { return m_dataModels[model]->isWritable(); } + bool isModified(int model) const { return m_dataModels[model]->isModified(); } + void setModified(int model, bool dirty) { m_dataModels[model]->setModified(dirty); } + QLocale::Language language(int model) const { return m_dataModels[model]->language(); } + QLocale::Language sourceLanguage(int model) const { return m_dataModels[model]->sourceLanguage(); } + + // Per message + void setTranslation(const MultiDataIndex &index, const QString &translation); + void setFinished(const MultiDataIndex &index, bool finished); + void setDanger(const MultiDataIndex &index, bool danger); + + // Retrieve items + DataModel *model(int i) { return m_dataModels[i]; } + MultiContextItem *multiContextItem(int ctxIdx) const + { return const_cast<MultiContextItem *>(&m_multiContextList[ctxIdx]); } + MultiMessageItem *multiMessageItem(const MultiDataIndex &index) const + { return multiContextItem(index.context())->multiMessageItem(index.message()); } + MessageItem *messageItem(const MultiDataIndex &index, int model) const; + MessageItem *messageItem(const MultiDataIndex &index) const { return messageItem(index, index.model()); } + + static QString condenseFileNames(const QStringList &names); + static QStringList prettifyFileNames(const QStringList &names); + + QBrush brushForModel(int model) const; + +signals: + void modelAppended(); + void modelDeleted(int model); + void allModelsDeleted(); + void languageChanged(int model); + void statsChanged(int words, int characters, int cs, int words2, int characters2, int cs2); + void modifiedChanged(bool); + void multiContextDataChanged(const MultiDataIndex &index); + void contextDataChanged(const MultiDataIndex &index); + void messageDataChanged(const MultiDataIndex &index); + void translationChanged(const MultiDataIndex &index); // Only the primary one + +private slots: + void onModifiedChanged(); + void onLanguageChanged(); + +private: + friend class MultiDataModelIterator; + friend class MessageModel; + + int findContextIndex(const QString &context) const; + MultiContextItem *findContext(const QString &context) const; + + ContextItem *contextItem(const MultiDataIndex &index) const + { return multiContextItem(index.context())->contextItem(index.model()); } + + void updateCountsOnAdd(int model, bool writable); + void updateCountsOnRemove(int model, bool writable); + void incrementFinishedCount() { ++m_numFinished; } + void decrementFinishedCount() { --m_numFinished; } + void incrementEditableCount() { ++m_numEditable; } + void decrementEditableCount() { --m_numEditable; } + + int m_numFinished; + int m_numEditable; + int m_numMessages; + + bool m_modified; + + QList<MultiContextItem> m_multiContextList; + QList<DataModel *> m_dataModels; + + MessageModel *m_msgModel; + + QColor m_colors[7]; + QBitmap m_bitmap; +}; + +class MessageModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum { SortRole = Qt::UserRole }; + + MessageModel(QObject *parent, MultiDataModel *data); + + // QAbstractItemModel + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + // Convenience + MultiDataIndex dataIndex(const QModelIndex &index, int model) const; + MultiDataIndex dataIndex(const QModelIndex &index) const + { return dataIndex(index, index.column() - 1 < m_data->modelCount() ? index.column() - 1 : -1); } + QModelIndex modelIndex(const MultiDataIndex &index); + +private slots: + void reset() { QAbstractItemModel::reset(); } + void multiContextItemChanged(const MultiDataIndex &index); + void contextItemChanged(const MultiDataIndex &index); + void messageItemChanged(const MultiDataIndex &index); + +private: + friend class MultiDataModel; + + MultiDataModel *m_data; // not owned +}; + +QT_END_NAMESPACE + +#endif // MESSAGEMODEL_H diff --git a/src/linguist/linguist/phrase.cpp b/src/linguist/linguist/phrase.cpp new file mode 100644 index 000000000..2f51c42bd --- /dev/null +++ b/src/linguist/linguist/phrase.cpp @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "phrase.h" +#include "translator.h" + +#include <QApplication> +#include <QFile> +#include <QFileInfo> +#include <QMessageBox> +#include <QRegExp> +#include <QTextCodec> +#include <QTextStream> +#include <QXmlAttributes> +#include <QXmlDefaultHandler> +#include <QXmlParseException> + +QT_BEGIN_NAMESPACE + +static QString protect(const QString & str) +{ + QString p = str; + p.replace(QLatin1Char('&'), QLatin1String("&")); + p.replace(QLatin1Char('\"'), QLatin1String(""")); + p.replace(QLatin1Char('>'), QLatin1String(">")); + p.replace(QLatin1Char('<'), QLatin1String("<")); + p.replace(QLatin1Char('\''), QLatin1String("'")); + return p; +} + +Phrase::Phrase() + : shrtc(-1), m_phraseBook(0) +{ +} + +Phrase::Phrase(const QString &source, const QString &target, + const QString &definition, int sc) + : shrtc(sc), s(source), t(target), d(definition), + m_phraseBook(0) +{ +} + +Phrase::Phrase(const QString &source, const QString &target, + const QString &definition, PhraseBook *phraseBook) + : shrtc(-1), s(source), t(target), d(definition), + m_phraseBook(phraseBook) +{ +} + +void Phrase::setSource(const QString &ns) +{ + if (s == ns) + return; + s = ns; + if (m_phraseBook) + m_phraseBook->phraseChanged(this); +} + +void Phrase::setTarget(const QString &nt) +{ + if (t == nt) + return; + t = nt; + if (m_phraseBook) + m_phraseBook->phraseChanged(this); +} + +void Phrase::setDefinition(const QString &nd) +{ + if (d == nd) + return; + d = nd; + if (m_phraseBook) + m_phraseBook->phraseChanged(this); +} + +bool operator==(const Phrase &p, const Phrase &q) +{ + return p.source() == q.source() && p.target() == q.target() && + p.definition() == q.definition() && p.phraseBook() == q.phraseBook(); +} + +class QphHandler : public QXmlDefaultHandler +{ +public: + QphHandler(PhraseBook *phraseBook) + : pb(phraseBook), ferrorCount(0) { } + + virtual bool startElement(const QString &namespaceURI, + const QString &localName, const QString &qName, + const QXmlAttributes &atts); + virtual bool endElement(const QString &namespaceURI, + const QString &localName, const QString &qName); + virtual bool characters(const QString &ch); + virtual bool fatalError(const QXmlParseException &exception); + + QString language() const { return m_language; } + QString sourceLanguage() const { return m_sourceLanguage; } + +private: + PhraseBook *pb; + QString source; + QString target; + QString definition; + QString m_language; + QString m_sourceLanguage; + + QString accum; + int ferrorCount; +}; + +bool QphHandler::startElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName, + const QXmlAttributes &atts) +{ + if (qName == QLatin1String("QPH")) { + m_language = atts.value(QLatin1String("language")); + m_sourceLanguage = atts.value(QLatin1String("sourcelanguage")); + } else if (qName == QLatin1String("phrase")) { + source.truncate(0); + target.truncate(0); + definition.truncate(0); + } + accum.truncate(0); + return true; +} + +bool QphHandler::endElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName) +{ + if (qName == QLatin1String("source")) + source = accum; + else if (qName == QLatin1String("target")) + target = accum; + else if (qName == QLatin1String("definition")) + definition = accum; + else if (qName == QLatin1String("phrase")) + pb->m_phrases.append(new Phrase(source, target, definition, pb)); + return true; +} + +bool QphHandler::characters(const QString &ch) +{ + accum += ch; + return true; +} + +bool QphHandler::fatalError(const QXmlParseException &exception) +{ + if (ferrorCount++ == 0) { + QString msg = PhraseBook::tr("Parse error at line %1, column %2 (%3).") + .arg(exception.lineNumber()).arg(exception.columnNumber()) + .arg(exception.message()); + QMessageBox::information(0, + QObject::tr("Qt Linguist"), msg); + } + return false; +} + +PhraseBook::PhraseBook() : + m_changed(false), + m_language(QLocale::C), + m_sourceLanguage(QLocale::C), + m_country(QLocale::AnyCountry), + m_sourceCountry(QLocale::AnyCountry) +{ +} + +PhraseBook::~PhraseBook() +{ + qDeleteAll(m_phrases); +} + +void PhraseBook::setLanguageAndCountry(QLocale::Language lang, QLocale::Country country) +{ + if (m_language == lang && m_country == country) + return; + m_language = lang; + m_country = country; + setModified(true); +} + +void PhraseBook::setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country) +{ + if (m_sourceLanguage == lang && m_sourceCountry == country) + return; + m_sourceLanguage = lang; + m_sourceCountry = country; + setModified(true); +} + +bool PhraseBook::load(const QString &fileName, bool *langGuessed) +{ + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) + return false; + + m_fileName = fileName; + + QXmlInputSource in(&f); + QXmlSimpleReader reader; + // don't click on these! + reader.setFeature(QLatin1String("http://xml.org/sax/features/namespaces"), false); + reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true); + reader.setFeature(QLatin1String("http://trolltech.com/xml/features/report-whitespace" + "-only-CharData"), false); + QphHandler *hand = new QphHandler(this); + reader.setContentHandler(hand); + reader.setErrorHandler(hand); + + bool ok = reader.parse(in); + reader.setContentHandler(0); + reader.setErrorHandler(0); + + Translator::languageAndCountry(hand->language(), &m_language, &m_country); + *langGuessed = false; + if (m_language == QLocale::C) { + QLocale sys; + m_language = sys.language(); + m_country = sys.country(); + *langGuessed = true; + } + + QString lang = hand->sourceLanguage(); + if (lang.isEmpty()) { + m_sourceLanguage = QLocale::C; + m_sourceCountry = QLocale::AnyCountry; + } else { + Translator::languageAndCountry(lang, &m_sourceLanguage, &m_sourceCountry); + } + + delete hand; + f.close(); + if (!ok) { + qDeleteAll(m_phrases); + m_phrases.clear(); + } else { + emit listChanged(); + } + + return ok; +} + +bool PhraseBook::save(const QString &fileName) +{ + QFile f(fileName); + if (!f.open(QIODevice::WriteOnly)) + return false; + + m_fileName = fileName; + + QTextStream t(&f); + t.setCodec( QTextCodec::codecForName("UTF-8") ); + + t << "<!DOCTYPE QPH>\n<QPH"; + if (sourceLanguage() != QLocale::C) + t << " sourcelanguage=\"" + << Translator::makeLanguageCode(sourceLanguage(), sourceCountry()) << '"'; + if (language() != QLocale::C) + t << " language=\"" << Translator::makeLanguageCode(language(), country()) << '"'; + t << ">\n"; + foreach (Phrase *p, m_phrases) { + t << "<phrase>\n"; + t << " <source>" << protect( p->source() ) << "</source>\n"; + t << " <target>" << protect( p->target() ) << "</target>\n"; + if (!p->definition().isEmpty()) + t << " <definition>" << protect( p->definition() ) + << "</definition>\n"; + t << "</phrase>\n"; + } + t << "</QPH>\n"; + f.close(); + setModified(false); + return true; +} + +void PhraseBook::append(Phrase *phrase) +{ + m_phrases.append(phrase); + phrase->setPhraseBook(this); + setModified(true); + emit listChanged(); +} + +void PhraseBook::remove(Phrase *phrase) +{ + m_phrases.removeOne(phrase); + phrase->setPhraseBook(0); + setModified(true); + emit listChanged(); +} + +void PhraseBook::setModified(bool modified) + { + if (m_changed != modified) { + emit modifiedChanged(modified); + m_changed = modified; + } +} + +void PhraseBook::phraseChanged(Phrase *p) +{ + Q_UNUSED(p); + + setModified(true); +} + +QString PhraseBook::friendlyPhraseBookName() const +{ + if (!m_fileName.isEmpty()) + return QFileInfo(m_fileName).fileName(); + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/phrase.h b/src/linguist/linguist/phrase.h new file mode 100644 index 000000000..a46165c46 --- /dev/null +++ b/src/linguist/linguist/phrase.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PHRASE_H +#define PHRASE_H + +#include <QObject> +#include <QString> +#include <QList> +#include <QtCore/QLocale> + +QT_BEGIN_NAMESPACE + +class PhraseBook; + +class Phrase +{ +public: + Phrase(); + Phrase(const QString &source, const QString &target, + const QString &definition, int sc = -1); + Phrase(const QString &source, const QString &target, + const QString &definition, PhraseBook *phraseBook); + + QString source() const { return s; } + void setSource(const QString &ns); + QString target() const {return t;} + void setTarget(const QString &nt); + QString definition() const {return d;} + void setDefinition (const QString &nd); + int shortcut() const { return shrtc; } + PhraseBook *phraseBook() const { return m_phraseBook; } + void setPhraseBook(PhraseBook *book) { m_phraseBook = book; } + +private: + int shrtc; + QString s; + QString t; + QString d; + PhraseBook *m_phraseBook; +}; + +bool operator==(const Phrase &p, const Phrase &q); +inline bool operator!=(const Phrase &p, const Phrase &q) { + return !(p == q); +} + +class QphHandler; + +class PhraseBook : public QObject +{ + Q_OBJECT + +public: + PhraseBook(); + ~PhraseBook(); + bool load(const QString &fileName, bool *langGuessed); + bool save(const QString &fileName); + QList<Phrase *> phrases() const { return m_phrases; } + void append(Phrase *phrase); + void remove(Phrase *phrase); + QString fileName() const { return m_fileName; } + QString friendlyPhraseBookName() const; + bool isModified() const { return m_changed; } + + void setLanguageAndCountry(QLocale::Language lang, QLocale::Country country); + QLocale::Language language() const { return m_language; } + QLocale::Country country() const { return m_country; } + void setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country); + QLocale::Language sourceLanguage() const { return m_sourceLanguage; } + QLocale::Country sourceCountry() const { return m_sourceCountry; } + +signals: + void modifiedChanged(bool changed); + void listChanged(); + +private: + // Prevent copying + PhraseBook(const PhraseBook &); + PhraseBook& operator=(const PhraseBook &); + + void setModified(bool modified); + void phraseChanged(Phrase *phrase); + + QList<Phrase *> m_phrases; + QString m_fileName; + bool m_changed; + + QLocale::Language m_language; + QLocale::Language m_sourceLanguage; + QLocale::Country m_country; + QLocale::Country m_sourceCountry; + + friend class QphHandler; + friend class Phrase; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/linguist/linguist/phrasebookbox.cpp b/src/linguist/linguist/phrasebookbox.cpp new file mode 100644 index 000000000..32d707040 --- /dev/null +++ b/src/linguist/linguist/phrasebookbox.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** 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 PhraseBookBox + + Go to Phrase > Edit Phrase Book... The dialog that pops up is a + PhraseBookBox. +*/ + +#include "phrasebookbox.h" +#include "translationsettingsdialog.h" + +#include <QtEvents> +#include <QLineEdit> +#include <QMessageBox> +#include <QHeaderView> +#include <QSortFilterProxyModel> + +QT_BEGIN_NAMESPACE + +PhraseBookBox::PhraseBookBox(PhraseBook *phraseBook, QWidget *parent) + : QDialog(parent), + m_phraseBook(phraseBook), + m_translationSettingsDialog(0) +{ + +// This definition needs to be within class context for lupdate to find it +#define NewPhrase tr("(New Entry)") + + setupUi(this); + setWindowTitle(tr("%1[*] - Qt Linguist").arg(m_phraseBook->friendlyPhraseBookName())); + setWindowModified(m_phraseBook->isModified()); + + phrMdl = new PhraseModel(this); + + m_sortedPhraseModel = new QSortFilterProxyModel(this); + m_sortedPhraseModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_sortedPhraseModel->setSortLocaleAware(true); + m_sortedPhraseModel->setDynamicSortFilter(true); + m_sortedPhraseModel->setSourceModel(phrMdl); + + phraseList->setModel(m_sortedPhraseModel); + phraseList->header()->setDefaultSectionSize(150); + phraseList->header()->setResizeMode(QHeaderView::Interactive); + + connect(sourceLed, SIGNAL(textChanged(QString)), + this, SLOT(sourceChanged(QString))); + connect(targetLed, SIGNAL(textChanged(QString)), + this, SLOT(targetChanged(QString))); + connect(definitionLed, SIGNAL(textChanged(QString)), + this, SLOT(definitionChanged(QString))); + connect(phraseList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(selectionChanged())); + connect(newBut, SIGNAL(clicked()), this, SLOT(newPhrase())); + connect(removeBut, SIGNAL(clicked()), this, SLOT(removePhrase())); + connect(settingsBut, SIGNAL(clicked()), this, SLOT(settings())); + connect(saveBut, SIGNAL(clicked()), this, SLOT(save())); + connect(m_phraseBook, SIGNAL(modifiedChanged(bool)), this, SLOT(setWindowModified(bool))); + + sourceLed->installEventFilter(this); + targetLed->installEventFilter(this); + definitionLed->installEventFilter(this); + + foreach (Phrase *p, phraseBook->phrases()) + phrMdl->addPhrase(p); + + phraseList->sortByColumn(0, Qt::AscendingOrder); + + enableDisable(); +} + +bool PhraseBookBox::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress && + (obj == sourceLed || obj == targetLed || obj == definitionLed)) + { + const QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + const int key = keyEvent->key(); + + switch (key) { + case Qt::Key_Down: + case Qt::Key_Up: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + return QApplication::sendEvent(phraseList, event); + } + } + return QDialog::eventFilter(obj, event); +} + +void PhraseBookBox::newPhrase() +{ + Phrase *p = new Phrase(); + p->setSource(NewPhrase); + m_phraseBook->append(p); + selectItem(phrMdl->addPhrase(p)); +} + +void PhraseBookBox::removePhrase() +{ + QModelIndex index = currentPhraseIndex(); + Phrase *phrase = phrMdl->phrase(index); + m_phraseBook->remove(phrase); + phrMdl->removePhrase(index); + delete phrase; +} + +void PhraseBookBox::settings() +{ + if (!m_translationSettingsDialog) + m_translationSettingsDialog = new TranslationSettingsDialog(this); + m_translationSettingsDialog->setPhraseBook(m_phraseBook); + m_translationSettingsDialog->exec(); +} + +void PhraseBookBox::save() +{ + const QString &fileName = m_phraseBook->fileName(); + if (!m_phraseBook->save(fileName)) + QMessageBox::warning(this, + tr("Qt Linguist"), + tr("Cannot save phrase book '%1'.").arg(fileName)); +} + +void PhraseBookBox::sourceChanged(const QString& source) +{ + QModelIndex index = currentPhraseIndex(); + if (index.isValid()) + phrMdl->setData(phrMdl->index(index.row(), 0), source); +} + +void PhraseBookBox::targetChanged(const QString& target) +{ + QModelIndex index = currentPhraseIndex(); + if (index.isValid()) + phrMdl->setData(phrMdl->index(index.row(), 1), target); +} + +void PhraseBookBox::definitionChanged(const QString& definition) +{ + QModelIndex index = currentPhraseIndex(); + if (index.isValid()) + phrMdl->setData(phrMdl->index(index.row(), 2), definition); +} + +void PhraseBookBox::selectionChanged() +{ + enableDisable(); +} + +void PhraseBookBox::selectItem(const QModelIndex &index) +{ + const QModelIndex &sortedIndex = m_sortedPhraseModel->mapFromSource(index); + phraseList->scrollTo(sortedIndex); + phraseList->setCurrentIndex(sortedIndex); +} + +void PhraseBookBox::enableDisable() +{ + QModelIndex index = currentPhraseIndex(); + + sourceLed->blockSignals(true); + targetLed->blockSignals(true); + definitionLed->blockSignals(true); + + bool indexValid = index.isValid(); + + if (indexValid) { + Phrase *p = phrMdl->phrase(index); + sourceLed->setText(p->source().simplified()); + targetLed->setText(p->target().simplified()); + definitionLed->setText(p->definition()); + } + else { + sourceLed->setText(QString()); + targetLed->setText(QString()); + definitionLed->setText(QString()); + } + + sourceLed->setEnabled(indexValid); + targetLed->setEnabled(indexValid); + definitionLed->setEnabled(indexValid); + removeBut->setEnabled(indexValid); + + sourceLed->blockSignals(false); + targetLed->blockSignals(false); + definitionLed->blockSignals(false); + + QWidget *f = QApplication::focusWidget(); + if (f != sourceLed && f != targetLed && f != definitionLed) { + QLineEdit *led = (sourceLed->text() == NewPhrase ? sourceLed : targetLed); + led->setFocus(); + led->selectAll(); + } else { + static_cast<QLineEdit*>(f)->selectAll(); + } +} + +QModelIndex PhraseBookBox::currentPhraseIndex() const +{ + return m_sortedPhraseModel->mapToSource(phraseList->currentIndex()); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/phrasebookbox.h b/src/linguist/linguist/phrasebookbox.h new file mode 100644 index 000000000..3cf2ce8a3 --- /dev/null +++ b/src/linguist/linguist/phrasebookbox.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PHRASEBOOKBOX_H +#define PHRASEBOOKBOX_H + +#include "ui_phrasebookbox.h" +#include "phrase.h" +#include "phrasemodel.h" +#include <QDialog> + +QT_BEGIN_NAMESPACE + +class TranslationSettingsDialog; + +class QSortFilterProxyModel; + +class PhraseBookBox : public QDialog, public Ui::PhraseBookBox +{ + Q_OBJECT +public: + PhraseBookBox(PhraseBook *phraseBook, QWidget *parent = 0); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private slots: + void newPhrase(); + void removePhrase(); + void settings(); + void save(); + void sourceChanged(const QString &source); + void targetChanged(const QString &target); + void definitionChanged(const QString &definition); + void selectionChanged(); + +private: + void selectItem(const QModelIndex &index); + void enableDisable(); + QModelIndex currentPhraseIndex() const; + + QString fn; + PhraseBook *m_phraseBook; + PhraseModel *phrMdl; + QSortFilterProxyModel *m_sortedPhraseModel; + TranslationSettingsDialog *m_translationSettingsDialog; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/linguist/linguist/phrasebookbox.ui b/src/linguist/linguist/phrasebookbox.ui new file mode 100644 index 000000000..5c29d5ea1 --- /dev/null +++ b/src/linguist/linguist/phrasebookbox.ui @@ -0,0 +1,236 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <comment>********************************************************************* +** +** 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$ +** +*********************************************************************</comment> + <class>PhraseBookBox</class> + <widget class="QDialog" name="PhraseBookBox"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>596</width> + <height>454</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Phrase Book</string> + </property> + <property name="whatsThis"> + <string>This window allows you to add, modify, or delete entries in a phrase book.</string> + </property> + <layout class="QHBoxLayout" name="hboxLayout"> + <item> + <layout class="QVBoxLayout" name="inputsLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="target"> + <property name="text"> + <string>&Translation:</string> + </property> + <property name="buddy"> + <cstring>targetLed</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="targetLed"> + <property name="whatsThis"> + <string>This is the phrase in the target language corresponding to the source phrase.</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="source"> + <property name="text"> + <string>S&ource phrase:</string> + </property> + <property name="buddy"> + <cstring>sourceLed</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="definitionLed"> + <property name="whatsThis"> + <string>This is a definition for the source phrase.</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="sourceLed"> + <property name="whatsThis"> + <string>This is the phrase in the source language.</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="definition"> + <property name="text"> + <string>&Definition:</string> + </property> + <property name="buddy"> + <cstring>definitionLed</cstring> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="phraseList"> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="buttonLayout"> + <item> + <widget class="QPushButton" name="newBut"> + <property name="whatsThis"> + <string>Click here to add the phrase to the phrase book.</string> + </property> + <property name="text"> + <string>&New Entry</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeBut"> + <property name="whatsThis"> + <string>Click here to remove the entry from the phrase book.</string> + </property> + <property name="text"> + <string>&Remove Entry</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="settingsBut"> + <property name="text"> + <string>Settin&gs...</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="saveBut"> + <property name="whatsThis"> + <string>Click here to save the changes made.</string> + </property> + <property name="text"> + <string>&Save</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="closeBut"> + <property name="whatsThis"> + <string>Click here to close this window.</string> + </property> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + <item> + <spacer name="spacer1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>51</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <tabstops> + <tabstop>sourceLed</tabstop> + <tabstop>targetLed</tabstop> + <tabstop>definitionLed</tabstop> + <tabstop>newBut</tabstop> + <tabstop>removeBut</tabstop> + <tabstop>saveBut</tabstop> + <tabstop>closeBut</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>closeBut</sender> + <signal>clicked()</signal> + <receiver>PhraseBookBox</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>545</x> + <y>166</y> + </hint> + <hint type="destinationlabel"> + <x>545</x> + <y>199</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/linguist/linguist/phrasemodel.cpp b/src/linguist/linguist/phrasemodel.cpp new file mode 100644 index 000000000..e28b64927 --- /dev/null +++ b/src/linguist/linguist/phrasemodel.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "phrasemodel.h" + +QT_BEGIN_NAMESPACE + +void PhraseModel::removePhrases() +{ + int r = plist.count(); + if (r > 0) { + plist.clear(); + reset(); + } +} + +Phrase *PhraseModel::phrase(const QModelIndex &index) const +{ + return plist.at(index.row()); +} + +void PhraseModel::setPhrase(const QModelIndex &indx, Phrase *ph) +{ + int r = indx.row(); + + plist[r] = ph; + + // update item in view + const QModelIndex &si = index(r, 0); + const QModelIndex &ei = index(r, 2); + emit dataChanged(si, ei); +} + +QModelIndex PhraseModel::addPhrase(Phrase *p) +{ + int r = plist.count(); + + plist.append(p); + + // update phrases as we add them + beginInsertRows(QModelIndex(), r, r); + QModelIndex i = index(r, 0); + endInsertRows(); + return i; +} + +void PhraseModel::removePhrase(const QModelIndex &index) +{ + int r = index.row(); + beginRemoveRows(QModelIndex(), r, r); + plist.removeAt(r); + endRemoveRows(); +} + +QModelIndex PhraseModel::index(Phrase * const phr) const +{ + int row; + if ((row = plist.indexOf(phr)) == -1) + return QModelIndex(); + + return index(row, 0); +} + +int PhraseModel::rowCount(const QModelIndex &) const +{ + return plist.count(); +} + +int PhraseModel::columnCount(const QModelIndex &) const +{ + return 3; +} + +QVariant PhraseModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal)) { + switch(section) { + case 0: + return tr("Source phrase"); + case 1: + return tr("Translation"); + case 2: + return tr("Definition"); + } + } + + return QVariant(); +} + +Qt::ItemFlags PhraseModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + // Edit is allowed for source & translation if item is from phrasebook + if (plist.at(index.row())->phraseBook() + && (index.column() != 2)) + flags |= Qt::ItemIsEditable; + return flags; +} + +bool PhraseModel::setData(const QModelIndex & index, const QVariant & value, int role) +{ + int row = index.row(); + int column = index.column(); + + if (!index.isValid() || row >= plist.count() || role != Qt::EditRole) + return false; + + Phrase *phrase = plist.at(row); + + switch (column) { + case 0: + phrase->setSource(value.toString()); + break; + case 1: + phrase->setTarget(value.toString()); + break; + case 2: + phrase->setDefinition(value.toString()); + break; + default: + return false; + } + + emit dataChanged(index, index); + return true; +} + +QVariant PhraseModel::data(const QModelIndex &index, int role) const +{ + int row = index.row(); + int column = index.column(); + + if (row >= plist.count() || !index.isValid()) + return QVariant(); + + Phrase *phrase = plist.at(row); + + if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column != 2)) { + switch (column) { + case 0: // source phrase + return phrase->source().simplified(); + case 1: // translation + return phrase->target().simplified(); + case 2: // definition + return phrase->definition(); + } + } + else if (role == Qt::EditRole && column != 2) { + switch (column) { + case 0: // source phrase + return phrase->source(); + case 1: // translation + return phrase->target(); + } + } + + return QVariant(); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/phrasemodel.h b/src/linguist/linguist/phrasemodel.h new file mode 100644 index 000000000..95efa00dd --- /dev/null +++ b/src/linguist/linguist/phrasemodel.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PHRASEMODEL_H +#define PHRASEMODEL_H + +#include "phrase.h" + +#include <QList> +#include <QAbstractItemModel> + +QT_BEGIN_NAMESPACE + +class PhraseModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + PhraseModel(QObject *parent = 0) + : QAbstractTableModel(parent) + {} + + void removePhrases(); + QList<Phrase *> phraseList() const {return plist;} + + QModelIndex addPhrase(Phrase *p); + void removePhrase(const QModelIndex &index); + + Phrase *phrase(const QModelIndex &index) const; + void setPhrase(const QModelIndex &indx, Phrase *ph); + QModelIndex index(Phrase * const phr) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + { return QAbstractTableModel::index(row, column, parent); } + + // from qabstracttablemodel + int rowCount(const QModelIndex &) const; + int columnCount(const QModelIndex &) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole); + + // HACK: This model will be displayed in a _TreeView_ + // which has a tendency to expand 'children' on double click + bool hasChildren(const QModelIndex &parent) const + { return !parent.isValid(); } + +private: + QList<Phrase *> plist; +}; + +QT_END_NAMESPACE + +#endif // PHRASEMODEL_H diff --git a/src/linguist/linguist/phraseview.cpp b/src/linguist/linguist/phraseview.cpp new file mode 100644 index 000000000..f39a5fb36 --- /dev/null +++ b/src/linguist/linguist/phraseview.cpp @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "globals.h" +#include "mainwindow.h" +#include "messagemodel.h" +#include "phrase.h" +#include "phraseview.h" +#include "phrasemodel.h" +#include "simtexth.h" + +#include <QHeaderView> +#include <QKeyEvent> +#include <QSettings> +#include <QTreeView> +#include <QWidget> +#include <QDebug> + + +QT_BEGIN_NAMESPACE + +// Maximum number of guesses to display +static const int MaxCandidates = 5; + +static QString phraseViewHeaderKey() +{ + return settingPath("PhraseViewHeader"); +} + +PhraseView::PhraseView(MultiDataModel *model, QList<QHash<QString, QList<Phrase *> > > *phraseDict, QWidget *parent) + : QTreeView(parent), + m_dataModel(model), + m_phraseDict(phraseDict), + m_modelIndex(-1), + m_doGuesses(true) +{ + setObjectName(QLatin1String("phrase list view")); + + m_phraseModel = new PhraseModel(this); + + setModel(m_phraseModel); + setAlternatingRowColors(true); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + setRootIsDecorated(false); + setItemsExpandable(false); + + for (int i = 0; i < 10; i++) + (void) new GuessShortcut(i, this, SLOT(guessShortcut(int))); + + header()->setResizeMode(QHeaderView::Interactive); + header()->setClickable(true); + header()->restoreState(QSettings().value(phraseViewHeaderKey()).toByteArray()); + + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(selectPhrase(QModelIndex))); +} + +PhraseView::~PhraseView() +{ + QSettings().setValue(phraseViewHeaderKey(), header()->saveState()); + deleteGuesses(); +} + +void PhraseView::toggleGuessing() +{ + m_doGuesses = !m_doGuesses; + update(); +} + +void PhraseView::update() +{ + setSourceText(m_modelIndex, m_sourceText); +} + + +void PhraseView::contextMenuEvent(QContextMenuEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) + return; + + QMenu *contextMenu = new QMenu(this); + + QAction *insertAction = new QAction(tr("Insert"), contextMenu); + connect(insertAction, SIGNAL(triggered()), this, SLOT(selectPhrase())); + + QAction *editAction = new QAction(tr("Edit"), contextMenu); + connect(editAction, SIGNAL(triggered()), this, SLOT(editPhrase())); + editAction->setEnabled(model()->flags(index) & Qt::ItemIsEditable); + + contextMenu->addAction(insertAction); + contextMenu->addAction(editAction); + + contextMenu->exec(event->globalPos()); + event->accept(); +} + +void PhraseView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) + return; + + emit phraseSelected(m_modelIndex, m_phraseModel->phrase(index)->target()); + event->accept(); +} + +void PhraseView::guessShortcut(int key) +{ + foreach (const Phrase *phrase, m_phraseModel->phraseList()) + if (phrase->shortcut() == key) { + emit phraseSelected(m_modelIndex, phrase->target()); + return; + } +} + +void PhraseView::selectPhrase(const QModelIndex &index) +{ + emit phraseSelected(m_modelIndex, m_phraseModel->phrase(index)->target()); +} + +void PhraseView::selectPhrase() +{ + emit phraseSelected(m_modelIndex, m_phraseModel->phrase(currentIndex())->target()); +} + +void PhraseView::editPhrase() +{ + edit(currentIndex()); +} + +static CandidateList similarTextHeuristicCandidates(MultiDataModel *model, int mi, + const char *text, int maxCandidates) +{ + QList<int> scores; + CandidateList candidates; + + StringSimilarityMatcher stringmatcher(QString::fromLatin1(text)); + + for (MultiDataModelIterator it(model, mi); it.isValid(); ++it) { + MessageItem *m = it.current(); + if (!m) + continue; + + TranslatorMessage mtm = m->message(); + if (mtm.type() == TranslatorMessage::Unfinished + || mtm.translation().isEmpty()) + continue; + + QString s = m->text(); + + int score = stringmatcher.getSimilarityScore(s); + + if (candidates.count() == maxCandidates && score > scores[maxCandidates - 1]) + candidates.removeLast(); + if (candidates.count() < maxCandidates && score >= textSimilarityThreshold ) { + Candidate cand(s, mtm.translation()); + + int i; + for (i = 0; i < candidates.size(); ++i) { + if (score >= scores.at(i)) { + if (score == scores.at(i)) { + if (candidates.at(i) == cand) + goto continue_outer_loop; + } else { + break; + } + } + } + scores.insert(i, score); + candidates.insert(i, cand); + } + continue_outer_loop: + ; + } + return candidates; +} + + +void PhraseView::setSourceText(int model, const QString &sourceText) +{ + m_modelIndex = model; + m_sourceText = sourceText; + m_phraseModel->removePhrases(); + deleteGuesses(); + + if (model < 0) + return; + + foreach (Phrase *p, getPhrases(model, sourceText)) + m_phraseModel->addPhrase(p); + + if (!sourceText.isEmpty() && m_doGuesses) { + CandidateList cl = similarTextHeuristicCandidates(m_dataModel, model, + sourceText.toLatin1(), MaxCandidates); + int n = 0; + foreach (const Candidate &candidate, cl) { + QString def; + if (n < 9) + def = tr("Guess (%1)").arg(QString(QKeySequence(Qt::CTRL | (Qt::Key_0 + (n + 1))))); + else + def = tr("Guess"); + Phrase *guess = new Phrase(candidate.source, candidate.target, def, n); + m_guesses.append(guess); + m_phraseModel->addPhrase(guess); + ++n; + } + } +} + +QList<Phrase *> PhraseView::getPhrases(int model, const QString &source) +{ + QList<Phrase *> phrases; + QString f = MainWindow::friendlyString(source); + QStringList lookupWords = f.split(QLatin1Char(' ')); + + foreach (const QString &s, lookupWords) { + if (m_phraseDict->at(model).contains(s)) { + foreach (Phrase *p, m_phraseDict->at(model).value(s)) { + if (f.contains(MainWindow::friendlyString(p->source()))) + phrases.append(p); + } + } + } + return phrases; +} + +void PhraseView::deleteGuesses() +{ + qDeleteAll(m_guesses); + m_guesses.clear(); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/phraseview.h b/src/linguist/linguist/phraseview.h new file mode 100644 index 000000000..6feedffa3 --- /dev/null +++ b/src/linguist/linguist/phraseview.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PHRASEVIEW_H +#define PHRASEVIEW_H + +#include <QList> +#include <QShortcut> +#include <QTreeView> +#include "phrase.h" + +QT_BEGIN_NAMESPACE + +class MultiDataModel; +class PhraseModel; + +class GuessShortcut : public QShortcut +{ + Q_OBJECT +public: + GuessShortcut(int nkey, QWidget *parent, const char *member) + : QShortcut(parent), nrkey(nkey) + { + setKey(Qt::CTRL + (Qt::Key_1 + nrkey)); + connect(this, SIGNAL(activated()), this, SLOT(keyActivated())); + connect(this, SIGNAL(activated(int)), parent, member); + } + +private slots: + void keyActivated() { emit activated(nrkey); } + +signals: + void activated(int nkey); + +private: + int nrkey; +}; + +class PhraseView : public QTreeView +{ + Q_OBJECT + +public: + PhraseView(MultiDataModel *model, QList<QHash<QString, QList<Phrase *> > > *phraseDict, QWidget *parent = 0); + ~PhraseView(); + void setSourceText(int model, const QString &sourceText); + +public slots: + void toggleGuessing(); + void update(); + +signals: + void phraseSelected(int latestModel, const QString &phrase); + +protected: + // QObject + virtual void contextMenuEvent(QContextMenuEvent *event); + // QAbstractItemView + virtual void mouseDoubleClickEvent(QMouseEvent *event); + +private slots: + void guessShortcut(int nkey); + void selectPhrase(const QModelIndex &index); + void selectPhrase(); + void editPhrase(); + +private: + QList<Phrase *> getPhrases(int model, const QString &sourceText); + void deleteGuesses(); + + MultiDataModel *m_dataModel; + QList<QHash<QString, QList<Phrase *> > > *m_phraseDict; + QList<Phrase *> m_guesses; + PhraseModel *m_phraseModel; + QString m_sourceText; + int m_modelIndex; + bool m_doGuesses; +}; + +QT_END_NAMESPACE + +#endif // PHRASEVIEW_H diff --git a/src/linguist/linguist/printout.cpp b/src/linguist/linguist/printout.cpp new file mode 100644 index 000000000..342f24081 --- /dev/null +++ b/src/linguist/linguist/printout.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "printout.h" + +#include <QPrinter> +#include <QFontMetrics> + +QT_BEGIN_NAMESPACE + +PrintOut::PrintOut(QPrinter *printer) + : pr(printer), nextRule(NoRule), page(0) +{ + p.begin(pr); + QFont f(QLatin1String("Arial")); + f8 = f; + f8.setPointSize(8); + f10 = f; + f10.setPointSize(10); + p.setFont(f10); + fmetrics = new QFontMetrics(p.fontMetrics()); + hmargin = 5 * printer->width() / printer->widthMM(); // 5 mm + vmargin = 5 * printer->height() / printer->heightMM(); // 5 mm + hsize = printer->width() - 2 * hmargin; + vsize = printer->height() - vmargin; + dateTime = QDateTime::currentDateTime(); + breakPage(true); // init vsize and draw first header + cp = Paragraph(QPoint(hmargin, voffset)); +} + +PrintOut::~PrintOut() +{ + flushLine(); + delete fmetrics; + p.end(); +} + +void PrintOut::setRule(Rule rule) +{ + if (nextRule < rule) + nextRule = rule; +} + +void PrintOut::setGuide(const QString &guide) +{ + g = guide; +} + +void PrintOut::vskip() +{ + if (!firstParagraph) + voffset += 14; +} + +void PrintOut::flushLine(bool /* mayBreak */) +{ + if (voffset + cp.rect.height() > vsize) + breakPage(); + else if (!firstParagraph) + drawRule(nextRule); + + for (int i = 0; i < cp.boxes.count(); ++i) { + Box b = cp.boxes[i]; + b.rect.translate(0, voffset); + QRect r = b.rect; + p.setFont(b.font); + p.drawText(r, b.text, b.options); + } + voffset += cp.rect.height(); + + nextRule = NoRule; + cp = Paragraph(QPoint(hmargin, voffset)); + firstParagraph = false; +} + +void PrintOut::addBox(int percent, const QString &text, Style style, Qt::Alignment halign) +{ + QTextOption options; + options.setAlignment(halign | Qt::AlignTop); + options.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + QFont f = f10; + if (style == Strong) + f.setBold(true); + else if (style == Emphasis) + f.setItalic(true); + int wd = hsize * percent / 100; + QRect r(cp.rect.x() + cp.rect.width(), 0, wd, vsize); + const int ht = static_cast<int>(p.boundingRect(r, text, options).height()); + + Box b(r, text, f, options); + cp.boxes.append(b); + cp.rect.setSize(QSize(cp.rect.width() + wd, qMax(cp.rect.height(), ht))); +} + +// use init if initial vsize should be calculated (first breakPage call) +void PrintOut::breakPage(bool init) +{ + static const int LeftAlign = Qt::AlignLeft | Qt::AlignTop; + static const int RightAlign = Qt::AlignRight | Qt::AlignTop; + QRect r1, r2; + int h1 = 0; + int h2 = 0; + + if (page > 0) + pr->newPage(); + + if (!init) + page++; + + voffset = 0; + + p.setFont(f10); + r1 = QRect(hmargin, voffset, 3 * hsize / 4, vsize); + r2 = QRect(r1.x() + r1.width(), voffset, hsize - r1.width(), vsize); + h1 = p.boundingRect(r1, LeftAlign, pr->docName()).height(); + if (!init) + p.drawText(r1, LeftAlign, pr->docName()); + h2 = p.boundingRect(r2, RightAlign, QString::number(page)).height(); + if (!init) + p.drawText(r2, RightAlign, QString::number(page)); + voffset += qMax(h1, h2 ); + + r1 = QRect(hmargin, voffset, hsize / 2, LeftAlign); + p.setFont(f8); + h1 = p.boundingRect(r1, LeftAlign, dateTime.toString()).height(); + if (!init) + p.drawText(r1, LeftAlign, dateTime.toString()); + p.setFont(f10); + voffset += qMax(h1, h2); + + voffset += 4; + if (!init) + p.drawLine(QPoint(hmargin, voffset), QPoint(hmargin + hsize, voffset)); + voffset += 14; + + firstParagraph = true; + + if (init) { + vsize -= voffset; + breakPage(); // now draw it when the vsize is ok + } + +} + +void PrintOut::drawRule(Rule rule) +{ + QPen pen; + + switch (rule) { + case NoRule: + voffset += 5; + break; + case ThinRule: + pen.setColor(QColor(192, 192, 192)); + pen.setStyle(Qt::DotLine); + pen.setWidth(0); + p.setPen(pen); + voffset += 5; + p.drawLine(QPoint(hmargin, voffset), + QPoint(hmargin + hsize, voffset)); + p.setPen(QPen()); + voffset += 2; + break; + case ThickRule: + voffset += 7; + p.drawLine(QPoint(hmargin, voffset), + QPoint(hmargin + hsize, voffset)); + voffset += 4; + } +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/printout.h b/src/linguist/linguist/printout.h new file mode 100644 index 000000000..c46c0fabf --- /dev/null +++ b/src/linguist/linguist/printout.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PRINTOUT_H +#define PRINTOUT_H + +#include <QFont> +#include <QPainter> +#include <QRect> +#include <QTextOption> +#include <QList> +#include <QDateTime> + +QT_BEGIN_NAMESPACE + +class QPrinter; +class QFontMetrics; + +class PrintOut +{ +public: + enum Rule { NoRule, ThinRule, ThickRule }; + enum Style { Normal, Strong, Emphasis }; + + PrintOut(QPrinter *printer); + ~PrintOut(); + + void setRule(Rule rule); + void setGuide(const QString &guide); + void vskip(); + void flushLine(bool mayBreak = false); + void addBox(int percent, const QString &text = QString(), + Style style = Normal, + Qt::Alignment halign = Qt::AlignLeft); + + int pageNum() const { return page; } + + struct Box + { + QRect rect; + QString text; + QFont font; + QTextOption options; + + Box( const QRect& r, const QString& t, const QFont& f, const QTextOption &o ) + : rect( r ), text( t ), font( f ), options( o ) { } + }; + +private: + void breakPage(bool init = false); + void drawRule( Rule rule ); + + struct Paragraph { + QRect rect; + QList<Box> boxes; + + Paragraph() { } + Paragraph( QPoint p ) : rect( p, QSize(0, 0) ) { } + }; + + QPrinter *pr; + QPainter p; + QFont f8; + QFont f10; + QFontMetrics *fmetrics; + Rule nextRule; + Paragraph cp; + int page; + bool firstParagraph; + QString g; + QDateTime dateTime; + + int hmargin; + int vmargin; + int voffset; + int hsize; + int vsize; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/linguist/linguist/recentfiles.cpp b/src/linguist/linguist/recentfiles.cpp new file mode 100644 index 000000000..e1d79ce24 --- /dev/null +++ b/src/linguist/linguist/recentfiles.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "recentfiles.h" +#include "globals.h" + +#include <QtCore/QDebug> +#include <QtCore/QFileInfo> +#include <QtCore/QSettings> +#include <QtCore/QString> +#include <QtCore/QStringList> + +QT_BEGIN_NAMESPACE + +static QString configKey() +{ + return settingPath("RecentlyOpenedFiles"); +} + + +RecentFiles::RecentFiles(const int maxEntries) + : m_groupOpen(false), + m_clone1st(false), + m_maxEntries(maxEntries) +{ + m_timer.setSingleShot(true); + m_timer.setInterval(3 * 60 * 1000); + connect(&m_timer, SIGNAL(timeout()), SLOT(closeGroup())); +} + +/* + * The logic is as follows: + * - The most recent (i.e., topmost) item can be open ("in flux") + * - The item is closed by either a timeout (3 min or so) or a + * "terminal action" (e.g., closing all files) + * - While the item is open, modifications to the set of open files + * will modify that item instead of creating new items + * - If the open item is modified to be equal to an existing item, + * the existing item is deleted, but will be re-created when the + * open item is modified even further + * Cases (actions in parentheses are no-ops): + * - identical to top item => (do nothing) + * - closed, new item => insert at top, (clear marker) + * - closed, existing item => move to top, mark for cloning + * - open, new item, not marked => replace top, (clear marker) + * - open, new item, marked => insert at top, clear marker + * - open, existing item, not marked => replace top, delete copy, mark for cloning + * - open, existing item, marked => insert at top, delete copy, (mark for cloning) + * - closing clears marker + */ +void RecentFiles::addFiles(const QStringList &names) +{ + if (m_strLists.isEmpty() || names != m_strLists.first()) { + if (m_groupOpen && !m_clone1st) + // Group being open implies at least one item in the list + m_strLists.removeFirst(); + m_groupOpen = true; + + // We do *not* sort the actual entries, as that would destroy the user's + // chosen arrangement. However, we do the searching on sorted lists, so + // we throw out (probably) obsolete arrangements. + QList<QStringList> sortedLists = m_strLists; + for (int i = 0; i < sortedLists.size(); ++i) + sortedLists[i].sort(); + QStringList sortedNames = names; + sortedNames.sort(); + + int index = sortedLists.indexOf(sortedNames); + if (index >= 0) { + m_strLists.removeAt(index); + m_clone1st = true; + } else { + if (m_strLists.count() >= m_maxEntries) + m_strLists.removeLast(); + m_clone1st = false; + } + m_strLists.prepend(names); + } + m_timer.start(); +} + +void RecentFiles::closeGroup() +{ + m_timer.stop(); + m_groupOpen = false; +} + +void RecentFiles::readConfig() +{ + m_strLists.clear(); + QVariant val = QSettings().value(configKey()); + if (val.type() == QVariant::StringList) // Backwards compat to Qt < 4.5 + foreach (const QString &s, val.toStringList()) + m_strLists << QStringList(QFileInfo(s).canonicalFilePath()); + else + foreach (const QVariant &v, val.toList()) + m_strLists << v.toStringList(); +} + +void RecentFiles::writeConfig() const +{ + QList<QVariant> vals; + foreach (const QStringList &sl, m_strLists) + vals << sl; + QSettings().setValue(configKey(), vals); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/recentfiles.h b/src/linguist/linguist/recentfiles.h new file mode 100644 index 000000000..09c2b407c --- /dev/null +++ b/src/linguist/linguist/recentfiles.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef RECENTFILES_H +#define RECENTFILES_H + +#include <QString> +#include <QStringList> +#include <QTimer> + +QT_BEGIN_NAMESPACE + +class RecentFiles : public QObject +{ + Q_OBJECT + +public: + explicit RecentFiles(const int maxEntries); + + bool isEmpty() { return m_strLists.isEmpty(); } + void addFiles(const QStringList &names); + QString lastOpenedFile() const { + if (m_strLists.isEmpty() || m_strLists.first().isEmpty()) + return QString::null; + return m_strLists.at(0).at(0); + } + const QList<QStringList>& filesLists() const { return m_strLists; } + + void readConfig(); + void writeConfig() const; + +public slots: + void closeGroup(); + +private: + bool m_groupOpen; + bool m_clone1st; + int m_maxEntries; + QList<QStringList> m_strLists; + QTimer m_timer; +}; + +QT_END_NAMESPACE + +#endif // RECENTFILES_H diff --git a/src/linguist/linguist/sourcecodeview.cpp b/src/linguist/linguist/sourcecodeview.cpp new file mode 100644 index 000000000..b6c011714 --- /dev/null +++ b/src/linguist/linguist/sourcecodeview.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "sourcecodeview.h" + +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTextStream> + +#include <QtGui/QTextCharFormat> +#include <QtGui/QTextBlock> +#include <QtGui/QTextCursor> + +QT_BEGIN_NAMESPACE + +SourceCodeView::SourceCodeView(QWidget *parent) + : QPlainTextEdit(parent), + m_isActive(true), + m_lineNumToLoad(0) +{ + setReadOnly(true); +} + +void SourceCodeView::setSourceContext(const QString &fileName, const int lineNum) +{ + m_fileToLoad.clear(); + setToolTip(fileName); + + if (fileName.isEmpty()) { + clear(); + m_currentFileName.clear(); + appendHtml(tr("<i>Source code not available</i>")); + return; + } + + if (m_isActive) { + showSourceCode(fileName, lineNum); + } else { + m_fileToLoad = fileName; + m_lineNumToLoad = lineNum; + } +} + +void SourceCodeView::setActivated(bool activated) +{ + m_isActive = activated; + if (activated && !m_fileToLoad.isEmpty()) { + showSourceCode(m_fileToLoad, m_lineNumToLoad); + m_fileToLoad.clear(); + } +} + +void SourceCodeView::showSourceCode(const QString &absFileName, const int lineNum) +{ + QString fileText = fileHash.value(absFileName); + + if (fileText.isNull()) { // File not in hash + m_currentFileName.clear(); + + // Assume fileName is relative to directory + QFile file(absFileName); + + if (!file.exists()) { + clear(); + appendHtml(tr("<i>File %1 not available</i>").arg(absFileName)); + return; + } + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + clear(); + appendHtml(tr("<i>File %1 not readable</i>").arg(absFileName)); + return; + } + fileText = QString::fromLatin1(file.readAll()); + fileHash.insert(absFileName, fileText); + } + + + if (m_currentFileName != absFileName) { + setPlainText(fileText); + m_currentFileName = absFileName; + } + + QTextCursor cursor = textCursor(); + cursor.setPosition(document()->findBlockByNumber(lineNum - 1).position()); + setTextCursor(cursor); + centerCursor(); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + + QTextEdit::ExtraSelection selectedLine; + selectedLine.cursor = cursor; + + // Define custom color for line selection + const QColor fg = palette().color(QPalette::Highlight); + const QColor bg = palette().color(QPalette::Base); + QColor col; + const qreal ratio = 0.25; + col.setRedF(fg.redF() * ratio + bg.redF() * (1 - ratio)); + col.setGreenF(fg.greenF() * ratio + bg.greenF() * (1 - ratio)); + col.setBlueF(fg.blueF() * ratio + bg.blueF() * (1 - ratio)); + + selectedLine.format.setBackground(col); + selectedLine.format.setProperty(QTextFormat::FullWidthSelection, true); + setExtraSelections(QList<QTextEdit::ExtraSelection>() << selectedLine); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/sourcecodeview.h b/src/linguist/linguist/sourcecodeview.h new file mode 100644 index 000000000..6f42a31cb --- /dev/null +++ b/src/linguist/linguist/sourcecodeview.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef SOURCECODEVIEW_H +#define SOURCECODEVIEW_H + +#include <QDir> +#include <QHash> +#include <QPlainTextEdit> + +QT_BEGIN_NAMESPACE + +class SourceCodeView : public QPlainTextEdit +{ + Q_OBJECT +public: + SourceCodeView(QWidget *parent = 0); + void setSourceContext(const QString &fileName, const int lineNum); + +public slots: + void setActivated(bool activated); + +private: + void showSourceCode(const QString &fileName, const int lineNum); + + bool m_isActive; + QString m_fileToLoad; + int m_lineNumToLoad; + QString m_currentFileName; + + QHash<QString, QString> fileHash; +}; + +QT_END_NAMESPACE + +#endif // SOURCECODEVIEW_H diff --git a/src/linguist/linguist/statistics.cpp b/src/linguist/linguist/statistics.cpp new file mode 100644 index 000000000..821ce7ab4 --- /dev/null +++ b/src/linguist/linguist/statistics.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "statistics.h" + +QT_BEGIN_NAMESPACE + +Statistics::Statistics(QWidget* parent, Qt::WindowFlags fl) + : QDialog(parent, fl) +{ + setupUi(this); +} + +void Statistics::languageChange() +{ + retranslateUi(this); +} + +void Statistics::updateStats(int sW,int sC,int sCS,int trW,int trC,int trCS) +{ + untrWords->setText(QString::number(sW)); + untrChars->setText(QString::number(sC)); + untrCharsSpc->setText(QString::number(sCS)); + trWords->setText(QString::number(trW)); + trChars->setText(QString::number(trC)); + trCharsSpc->setText(QString::number(trCS)); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/statistics.h b/src/linguist/linguist/statistics.h new file mode 100644 index 000000000..fe1f6cfa2 --- /dev/null +++ b/src/linguist/linguist/statistics.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef STATISTICS_H +#define STATISTICS_H + +#include "ui_statistics.h" +#include <QVariant> + +QT_BEGIN_NAMESPACE + +class Statistics : public QDialog, public Ui::Statistics +{ + Q_OBJECT + +public: + Statistics(QWidget *parent = 0, Qt::WindowFlags fl = 0); + ~Statistics() {} + +public slots: + virtual void updateStats(int w1, int c1, int cs1, int w2, int c2, int cs2); + +protected slots: + virtual void languageChange(); +}; + +QT_END_NAMESPACE + +#endif // STATISTICS_H diff --git a/src/linguist/linguist/statistics.ui b/src/linguist/linguist/statistics.ui new file mode 100644 index 000000000..28402b0e4 --- /dev/null +++ b/src/linguist/linguist/statistics.ui @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <comment>********************************************************************* +** +** 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$ +** +*********************************************************************</comment> + <class>Statistics</class> + <widget class="QDialog" name="linguist_stats"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>336</width> + <height>169</height> + </rect> + </property> + <property name="windowTitle"> + <string>Statistics</string> + </property> + <layout class="QGridLayout"> + <item row="1" column="0"> + <layout class="QHBoxLayout"> + <item> + <spacer name="spacer4_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="closeBtn"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + <item> + <spacer name="spacer4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QFrame" name="frame4"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout"> + <item row="0" column="2"> + <widget class="QLabel" name="textLabel4"> + <property name="text"> + <string>Translation</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="textLabel5"> + <property name="text"> + <string>Source</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="untrWords"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="trWords"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="textLabel1"> + <property name="text"> + <string>Words:</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="trChars"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="untrChars"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="textLabel3"> + <property name="text"> + <string>Characters:</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="textLabel6"> + <property name="text"> + <string>Characters (with spaces):</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLabel" name="trCharsSpc"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="untrCharsSpc"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections> + <connection> + <sender>closeBtn</sender> + <signal>clicked()</signal> + <receiver>linguist_stats</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>179</x> + <y>144</y> + </hint> + <hint type="destinationlabel"> + <x>239</x> + <y>142</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/linguist/linguist/translatedialog.cpp b/src/linguist/linguist/translatedialog.cpp new file mode 100644 index 000000000..94acfd5fd --- /dev/null +++ b/src/linguist/linguist/translatedialog.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "translatedialog.h" + +QT_BEGIN_NAMESPACE + +TranslateDialog::TranslateDialog(QWidget *parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + connect(m_ui.findNxt, SIGNAL(clicked()), this, SLOT(emitFindNext())); + connect(m_ui.translate, SIGNAL(clicked()), this, SLOT(emitTranslateAndFindNext())); + connect(m_ui.translateAll, SIGNAL(clicked()), this, SLOT(emitTranslateAll())); + connect(m_ui.ledFindWhat, SIGNAL(textChanged(QString)), SLOT(verifyText())); + connect(m_ui.ckMatchCase, SIGNAL(toggled(bool)), SLOT(verifyText())); +} + +void TranslateDialog::showEvent(QShowEvent *) +{ + verifyText(); + m_ui.ledFindWhat->setFocus(); +} + +void TranslateDialog::verifyText() +{ + QString text = m_ui.ledFindWhat->text(); + bool canFind = !text.isEmpty(); + bool hit = false; + if (canFind) + emit requestMatchUpdate(hit); + m_ui.findNxt->setEnabled(canFind); + m_ui.translate->setEnabled(canFind && hit); + m_ui.translateAll->setEnabled(canFind); +} + +void TranslateDialog::emitFindNext() +{ + emit activated(Skip); +} + +void TranslateDialog::emitTranslateAndFindNext() +{ + emit activated(Translate); +} + +void TranslateDialog::emitTranslateAll() +{ + emit activated(TranslateAll); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/translatedialog.h b/src/linguist/linguist/translatedialog.h new file mode 100644 index 000000000..80c09a74a --- /dev/null +++ b/src/linguist/linguist/translatedialog.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TRANSLATEDIALOG_H +#define TRANSLATEDIALOG_H + +#include "ui_translatedialog.h" +#include <QDialog> + +QT_BEGIN_NAMESPACE + +class TranslateDialog : public QDialog +{ + Q_OBJECT + +public: + enum { + Skip, + Translate, + TranslateAll + }; + + TranslateDialog(QWidget *parent = 0); + + bool markFinished() const { return m_ui.ckMarkFinished->isChecked(); } + Qt::CaseSensitivity caseSensitivity() const + { return m_ui.ckMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive; } + QString findText() const { return m_ui.ledFindWhat->text(); } + QString replaceText() const { return m_ui.ledTranslateTo->text(); } + +signals: + void requestMatchUpdate(bool &hit); + void activated(int mode); + +protected: + virtual void showEvent(QShowEvent *event); + +private slots: + void emitFindNext(); + void emitTranslateAndFindNext(); + void emitTranslateAll(); + void verifyText(); + +private: + Ui::TranslateDialog m_ui; +}; + + +QT_END_NAMESPACE +#endif //TRANSLATEDIALOG_H + diff --git a/src/linguist/linguist/translatedialog.ui b/src/linguist/linguist/translatedialog.ui new file mode 100644 index 000000000..7c0baa722 --- /dev/null +++ b/src/linguist/linguist/translatedialog.ui @@ -0,0 +1,260 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <comment>********************************************************************* +** +** 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$ +** +*********************************************************************</comment> + <class>TranslateDialog</class> + <widget class="QDialog" name="translateDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>407</width> + <height>174</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis"> + <string>This window allows you to search for some text in the translation source file.</string> + </property> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QGridLayout"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="1" column="1"> + <widget class="QLineEdit" name="ledTranslateTo"> + <property name="whatsThis"> + <string>Type in the text to search for.</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="findWhat"> + <property name="text"> + <string>Find &source text:</string> + </property> + <property name="buddy"> + <cstring>ledFindWhat</cstring> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="translateTo"> + <property name="text"> + <string>&Translate to:</string> + </property> + <property name="buddy"> + <cstring>ledTranslateTo</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="ledFindWhat"> + <property name="whatsThis"> + <string>Type in the text to search for.</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Search options</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QCheckBox" name="ckMatchCase"> + <property name="whatsThis"> + <string>Texts such as 'TeX' and 'tex' are considered as different when checked.</string> + </property> + <property name="text"> + <string>Match &case</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="ckMarkFinished"> + <property name="text"> + <string>Mark new translation as &finished</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="findNxt"> + <property name="whatsThis"> + <string>Click here to find the next occurrence of the text you typed in.</string> + </property> + <property name="text"> + <string>Find Next</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="translate"> + <property name="text"> + <string>Translate</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="translateAll"> + <property name="text"> + <string>Translate All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancel"> + <property name="whatsThis"> + <string>Click here to close this window.</string> + </property> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>51</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <tabstops> + <tabstop>ledFindWhat</tabstop> + <tabstop>ledTranslateTo</tabstop> + <tabstop>findNxt</tabstop> + <tabstop>translate</tabstop> + <tabstop>translateAll</tabstop> + <tabstop>cancel</tabstop> + <tabstop>ckMatchCase</tabstop> + <tabstop>ckMarkFinished</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>cancel</sender> + <signal>clicked()</signal> + <receiver>translateDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>360</x> + <y>132</y> + </hint> + <hint type="destinationlabel"> + <x>357</x> + <y>151</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/linguist/linguist/translationsettings.ui b/src/linguist/linguist/translationsettings.ui new file mode 100644 index 000000000..c868360e6 --- /dev/null +++ b/src/linguist/linguist/translationsettings.ui @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TranslationSettingsDialog</class> + <widget class="QDialog" name="translationSettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>416</width> + <height>263</height> + </rect> + </property> + <property name="windowTitle"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="srcGroupBox"> + <property name="title"> + <string>Source language</string> + </property> + <layout class="QGridLayout" name="_2"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="1"> + <widget class="QComboBox" name="srcCbLanguageList"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="srcLblLanguage"> + <property name="text"> + <string>Language</string> + </property> + <property name="buddy"> + <cstring>tgtCbLanguageList</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="srcCbCountryList"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="srcLblCountry"> + <property name="text"> + <string>Country/Region</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="tgtGroupBox"> + <property name="title"> + <string>Target language</string> + </property> + <layout class="QGridLayout"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="1"> + <widget class="QComboBox" name="tgtCbLanguageList"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="tgtLblLanguage"> + <property name="text"> + <string>Language</string> + </property> + <property name="buddy"> + <cstring>tgtCbLanguageList</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="tgtCbCountryList"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="tgtLblCountry"> + <property name="text"> + <string>Country/Region</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>100</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>translationSettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>216</x> + <y>241</y> + </hint> + <hint type="destinationlabel"> + <x>222</x> + <y>213</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/linguist/linguist/translationsettingsdialog.cpp b/src/linguist/linguist/translationsettingsdialog.cpp new file mode 100644 index 000000000..2049af12b --- /dev/null +++ b/src/linguist/linguist/translationsettingsdialog.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + + +#include "translationsettingsdialog.h" +#include "messagemodel.h" +#include "phrase.h" + +#include <QtCore/QLocale> + +QT_BEGIN_NAMESPACE + +TranslationSettingsDialog::TranslationSettingsDialog(QWidget *parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + for (int i = QLocale::C + 1; i < QLocale::LastLanguage; ++i) { + QString lang = QLocale::languageToString(QLocale::Language(i)); + m_ui.srcCbLanguageList->addItem(lang, QVariant(i)); + } + m_ui.srcCbLanguageList->model()->sort(0, Qt::AscendingOrder); + m_ui.srcCbLanguageList->insertItem(0, QLatin1String("POSIX"), QVariant(QLocale::C)); + + m_ui.tgtCbLanguageList->setModel(m_ui.srcCbLanguageList->model()); +} + +void TranslationSettingsDialog::setDataModel(DataModel *dataModel) +{ + m_dataModel = dataModel; + m_phraseBook = 0; + QString fn = QFileInfo(dataModel->srcFileName()).baseName(); + setWindowTitle(tr("Settings for '%1' - Qt Linguist").arg(fn)); +} + +void TranslationSettingsDialog::setPhraseBook(PhraseBook *phraseBook) +{ + m_phraseBook = phraseBook; + m_dataModel = 0; + QString fn = QFileInfo(phraseBook->fileName()).baseName(); + setWindowTitle(tr("Settings for '%1' - Qt Linguist").arg(fn)); +} + +static void fillCountryCombo(const QVariant &lng, QComboBox *combo) +{ + combo->clear(); + QLocale::Language lang = QLocale::Language(lng.toInt()); + if (lang != QLocale::C) { + foreach (QLocale::Country cntr, QLocale::countriesForLanguage(lang)) { + QString country = QLocale::countryToString(cntr); + combo->addItem(country, QVariant(cntr)); + } + combo->model()->sort(0, Qt::AscendingOrder); + } + combo->insertItem(0, TranslationSettingsDialog::tr("Any Country"), QVariant(QLocale::AnyCountry)); + combo->setCurrentIndex(0); +} + +void TranslationSettingsDialog::on_srcCbLanguageList_currentIndexChanged(int idx) +{ + fillCountryCombo(m_ui.srcCbLanguageList->itemData(idx), m_ui.srcCbCountryList); +} + +void TranslationSettingsDialog::on_tgtCbLanguageList_currentIndexChanged(int idx) +{ + fillCountryCombo(m_ui.tgtCbLanguageList->itemData(idx), m_ui.tgtCbCountryList); +} + +void TranslationSettingsDialog::on_buttonBox_accepted() +{ + int itemindex = m_ui.tgtCbLanguageList->currentIndex(); + QVariant var = m_ui.tgtCbLanguageList->itemData(itemindex); + QLocale::Language lang = QLocale::Language(var.toInt()); + + itemindex = m_ui.tgtCbCountryList->currentIndex(); + var = m_ui.tgtCbCountryList->itemData(itemindex); + QLocale::Country country = QLocale::Country(var.toInt()); + + itemindex = m_ui.srcCbLanguageList->currentIndex(); + var = m_ui.srcCbLanguageList->itemData(itemindex); + QLocale::Language lang2 = QLocale::Language(var.toInt()); + + itemindex = m_ui.srcCbCountryList->currentIndex(); + var = m_ui.srcCbCountryList->itemData(itemindex); + QLocale::Country country2 = QLocale::Country(var.toInt()); + + if (m_phraseBook) { + m_phraseBook->setLanguageAndCountry(lang, country); + m_phraseBook->setSourceLanguageAndCountry(lang2, country2); + } else { + m_dataModel->setLanguageAndCountry(lang, country); + m_dataModel->setSourceLanguageAndCountry(lang2, country2); + } + + accept(); +} + +void TranslationSettingsDialog::showEvent(QShowEvent *) +{ + QLocale::Language lang, lang2; + QLocale::Country country, country2; + + if (m_phraseBook) { + lang = m_phraseBook->language(); + country = m_phraseBook->country(); + lang2 = m_phraseBook->sourceLanguage(); + country2 = m_phraseBook->sourceCountry(); + } else { + lang = m_dataModel->language(); + country = m_dataModel->country(); + lang2 = m_dataModel->sourceLanguage(); + country2 = m_dataModel->sourceCountry(); + } + + int itemindex = m_ui.tgtCbLanguageList->findData(QVariant(int(lang))); + m_ui.tgtCbLanguageList->setCurrentIndex(itemindex == -1 ? 0 : itemindex); + + itemindex = m_ui.tgtCbCountryList->findData(QVariant(int(country))); + m_ui.tgtCbCountryList->setCurrentIndex(itemindex == -1 ? 0 : itemindex); + + itemindex = m_ui.srcCbLanguageList->findData(QVariant(int(lang2))); + m_ui.srcCbLanguageList->setCurrentIndex(itemindex == -1 ? 0 : itemindex); + + itemindex = m_ui.srcCbCountryList->findData(QVariant(int(country2))); + m_ui.srcCbCountryList->setCurrentIndex(itemindex == -1 ? 0 : itemindex); +} + +QT_END_NAMESPACE diff --git a/src/linguist/linguist/translationsettingsdialog.h b/src/linguist/linguist/translationsettingsdialog.h new file mode 100644 index 000000000..9eee406fa --- /dev/null +++ b/src/linguist/linguist/translationsettingsdialog.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TRANSLATIONSETTINGSDIALOG_H +#define TRANSLATIONSETTINGSDIALOG_H + +#include "ui_translationsettings.h" + +#include <QtCore/QLocale> +#include <QtGui/QDialog> + +QT_BEGIN_NAMESPACE + +class DataModel; +class PhraseBook; + +class TranslationSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + TranslationSettingsDialog(QWidget *parent = 0); + void setDataModel(DataModel *model); + void setPhraseBook(PhraseBook *phraseBook); + +private: + virtual void showEvent(QShowEvent *e); + +private slots: + void on_buttonBox_accepted(); + void on_srcCbLanguageList_currentIndexChanged(int idx); + void on_tgtCbLanguageList_currentIndexChanged(int idx); + +private: + Ui::TranslationSettingsDialog m_ui; + DataModel *m_dataModel; + PhraseBook *m_phraseBook; + +}; + +QT_END_NAMESPACE + +#endif // TRANSLATIONSETTINGSDIALOG_H |