| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the Qt Linguist of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #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 |