1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Linguist of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #ifndef MESSAGEMODEL_H
30 #define MESSAGEMODEL_H
31 
32 #include "translator.h"
33 
34 #include <QtCore/QAbstractItemModel>
35 #include <QtCore/QList>
36 #include <QtCore/QHash>
37 #include <QtCore/QLocale>
38 #include <QtGui/QColor>
39 #include <QtGui/QBitmap>
40 
41 QT_BEGIN_NAMESPACE
42 
43 class DataModel;
44 class MultiDataModel;
45 
46 class MessageItem
47 {
48 public:
49     MessageItem(const TranslatorMessage &message);
50 
danger()51     bool danger() const { return m_danger; }
setDanger(bool danger)52     void setDanger(bool danger) { m_danger = danger; }
53 
setTranslation(const QString & translation)54     void setTranslation(const QString &translation)
55         { m_message.setTranslation(translation); }
56 
id()57     QString id() const { return m_message.id(); }
context()58     QString context() const { return m_message.context(); }
text()59     QString text() const { return m_message.sourceText(); }
pluralText()60     QString pluralText() const { return m_message.extra(QLatin1String("po-msgid_plural")); }
comment()61     QString comment() const { return m_message.comment(); }
fileName()62     QString fileName() const { return m_message.fileName(); }
extraComment()63     QString extraComment() const { return m_message.extraComment(); }
translatorComment()64     QString translatorComment() const { return m_message.translatorComment(); }
setTranslatorComment(const QString & cmt)65     void setTranslatorComment(const QString &cmt) { m_message.setTranslatorComment(cmt); }
lineNumber()66     int lineNumber() const { return m_message.lineNumber(); }
translation()67     QString translation() const { return m_message.translation(); }
translations()68     QStringList translations() const { return m_message.translations(); }
setTranslations(const QStringList & translations)69     void setTranslations(const QStringList &translations)
70         { m_message.setTranslations(translations); }
71 
type()72     TranslatorMessage::Type type() const { return m_message.type(); }
setType(TranslatorMessage::Type type)73     void setType(TranslatorMessage::Type type) { m_message.setType(type); }
74 
isFinished()75     bool isFinished() const { return type() == TranslatorMessage::Finished; }
isObsolete()76     bool isObsolete() const
77         { return type() == TranslatorMessage::Obsolete || type() == TranslatorMessage::Vanished; }
message()78     const TranslatorMessage &message() const { return m_message; }
79 
80     bool compare(const QString &findText, bool matchSubstring,
81         Qt::CaseSensitivity cs) const;
82 
83 private:
84     TranslatorMessage m_message;
85     bool m_danger;
86 };
87 
88 
89 class ContextItem
90 {
91 public:
92     ContextItem(const QString &context);
93 
finishedDangerCount()94     int finishedDangerCount() const { return m_finishedDangerCount; }
unfinishedDangerCount()95     int unfinishedDangerCount() const { return m_unfinishedDangerCount; }
96 
finishedCount()97     int finishedCount() const { return m_finishedCount; }
unfinishedCount()98     int unfinishedCount() const { return m_nonobsoleteCount - m_finishedCount; }
nonobsoleteCount()99     int nonobsoleteCount() const { return m_nonobsoleteCount; }
100 
context()101     QString context() const { return m_context; }
comment()102     QString comment() const { return m_comment; }
fullContext()103     QString fullContext() const { return m_comment.trimmed(); }
104 
105     // For item status in context list
isObsolete()106     bool isObsolete() const { return !nonobsoleteCount(); }
isFinished()107     bool isFinished() const { return unfinishedCount() == 0; }
108 
109     MessageItem *messageItem(int i) const;
messageCount()110     int messageCount() const { return msgItemList.count(); }
111 
112     MessageItem *findMessage(const QString &sourcetext, const QString &comment) const;
113 
114 private:
115     friend class DataModel;
116     friend class MultiDataModel;
appendMessage(const MessageItem & msg)117     void appendMessage(const MessageItem &msg) { msgItemList.append(msg); }
118     void appendToComment(const QString &x);
incrementFinishedCount()119     void incrementFinishedCount() { ++m_finishedCount; }
decrementFinishedCount()120     void decrementFinishedCount() { --m_finishedCount; }
incrementFinishedDangerCount()121     void incrementFinishedDangerCount() { ++m_finishedDangerCount; }
decrementFinishedDangerCount()122     void decrementFinishedDangerCount() { --m_finishedDangerCount; }
incrementUnfinishedDangerCount()123     void incrementUnfinishedDangerCount() { ++m_unfinishedDangerCount; }
decrementUnfinishedDangerCount()124     void decrementUnfinishedDangerCount() { --m_unfinishedDangerCount; }
incrementNonobsoleteCount()125     void incrementNonobsoleteCount() { ++m_nonobsoleteCount; }
126 
127     QString m_comment;
128     QString m_context;
129     int m_finishedCount;
130     int m_finishedDangerCount;
131     int m_unfinishedDangerCount;
132     int m_nonobsoleteCount;
133     QList<MessageItem> msgItemList;
134 };
135 
136 
137 class DataIndex
138 {
139 public:
DataIndex()140     DataIndex() : m_context(-1), m_message(-1) {}
DataIndex(int context,int message)141     DataIndex(int context, int message) : m_context(context), m_message(message) {}
context()142     int context() const { return m_context; }
message()143     int message() const { return m_message; }
isValid()144     bool isValid() const { return m_context >= 0; }
145 protected:
146     int m_context;
147     int m_message;
148 };
149 
150 
151 class DataModelIterator : public DataIndex
152 {
153 public:
154     DataModelIterator(DataModel *model, int contextNo = 0, int messageNo = 0);
155     MessageItem *current() const;
156     bool isValid() const;
157     void operator++();
158 private:
DataModelIterator()159     DataModelIterator() {}
160     DataModel *m_model; // not owned
161 };
162 
163 
164 class DataModel : public QObject
165 {
166     Q_OBJECT
167 public:
168     DataModel(QObject *parent = 0);
169 
170     enum FindLocation { NoLocation = 0, SourceText = 0x1, Translations = 0x2, Comments = 0x4 };
171 
172     // Specializations
contextCount()173     int contextCount() const { return m_contextList.count(); }
174     ContextItem *findContext(const QString &context) const;
175     MessageItem *findMessage(const QString &context, const QString &sourcetext,
176         const QString &comment) const;
177 
178     ContextItem *contextItem(int index) const;
179     MessageItem *messageItem(const DataIndex &index) const;
180 
messageCount()181     int messageCount() const { return m_numMessages; }
isEmpty()182     bool isEmpty() const { return m_numMessages == 0; }
isModified()183     bool isModified() const { return m_modified; }
184     void setModified(bool dirty);
isWritable()185     bool isWritable() const { return m_writable; }
setWritable(bool writable)186     void setWritable(bool writable) { m_writable = writable; }
187 
188     bool isWellMergeable(const DataModel *other) const;
189     bool load(const QString &fileName, bool *langGuessed, QWidget *parent);
save(QWidget * parent)190     bool save(QWidget *parent) { return save(m_srcFileName, parent); }
191     bool saveAs(const QString &newFileName, QWidget *parent);
192     bool release(const QString &fileName, bool verbose,
193         bool ignoreUnfinished, TranslatorSaveMode mode, QWidget *parent);
194     QString srcFileName(bool pretty = false) const
195         { return pretty ? prettifyPlainFileName(m_srcFileName) : m_srcFileName; }
196 
197     static QString prettifyPlainFileName(const QString &fn);
198     static QString prettifyFileName(const QString &fn);
199 
200     bool setLanguageAndCountry(QLocale::Language lang, QLocale::Country country);
language()201     QLocale::Language language() const { return m_language; }
country()202     QLocale::Country country() const { return m_country; }
203     void setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country);
sourceLanguage()204     QLocale::Language sourceLanguage() const { return m_sourceLanguage; }
sourceCountry()205     QLocale::Country sourceCountry() const { return m_sourceCountry; }
206 
localizedLanguage()207     const QString &localizedLanguage() const { return m_localizedLanguage; }
numerusForms()208     const QStringList &numerusForms() const { return m_numerusForms; }
countRefNeeds()209     const QList<bool> &countRefNeeds() const { return m_countRefNeeds; }
210 
211     QStringList normalizedTranslations(const MessageItem &m) const;
212     void doCharCounting(const QString& text, int& trW, int& trC, int& trCS);
213     void updateStatistics();
214 
getSrcWords()215     int getSrcWords() const { return m_srcWords; }
getSrcChars()216     int getSrcChars() const { return m_srcChars; }
getSrcCharsSpc()217     int getSrcCharsSpc() const { return m_srcCharsSpc; }
218 
219 signals:
220     void statsChanged(int words, int characters, int cs, int words2, int characters2, int cs2);
221     void progressChanged(int finishedCount, int oldFinishedCount);
222     void languageChanged();
223     void modifiedChanged();
224 
225 private:
226     friend class DataModelIterator;
227     QList<ContextItem> m_contextList;
228 
229     bool save(const QString &fileName, QWidget *parent);
230     void updateLocale();
231 
232     bool m_writable;
233     bool m_modified;
234 
235     int m_numMessages;
236 
237     // For statistics
238     int m_srcWords;
239     int m_srcChars;
240     int m_srcCharsSpc;
241 
242     QString m_srcFileName;
243     QLocale::Language m_language;
244     QLocale::Language m_sourceLanguage;
245     QLocale::Country m_country;
246     QLocale::Country m_sourceCountry;
247     bool m_relativeLocations;
248     Translator::ExtraData m_extra;
249 
250     QString m_localizedLanguage;
251     QStringList m_numerusForms;
252     QList<bool> m_countRefNeeds;
253 };
254 
255 
256 struct MultiMessageItem
257 {
258 public:
259     MultiMessageItem(const MessageItem *m);
idMultiMessageItem260     QString id() const { return m_id; }
textMultiMessageItem261     QString text() const { return m_text; }
pluralTextMultiMessageItem262     QString pluralText() const { return m_pluralText; }
commentMultiMessageItem263     QString comment() const { return m_comment; }
isEmptyMultiMessageItem264     bool isEmpty() const { return !m_nonnullCount; }
265     // The next two include also read-only
isObsoleteMultiMessageItem266     bool isObsolete() const { return m_nonnullCount && !m_nonobsoleteCount; }
countNonobsoleteMultiMessageItem267     int countNonobsolete() const { return m_nonobsoleteCount; }
268     // The next three include only read-write
countEditableMultiMessageItem269     int countEditable() const { return m_editableCount; }
isUnfinishedMultiMessageItem270     bool isUnfinished() const { return m_unfinishedCount != 0; }
countUnfinishedMultiMessageItem271     int countUnfinished() const { return m_unfinishedCount; }
272 
273 private:
274     friend class MultiDataModel;
incrementNonnullCountMultiMessageItem275     void incrementNonnullCount() { ++m_nonnullCount; }
decrementNonnullCountMultiMessageItem276     void decrementNonnullCount() { --m_nonnullCount; }
incrementNonobsoleteCountMultiMessageItem277     void incrementNonobsoleteCount() { ++m_nonobsoleteCount; }
decrementNonobsoleteCountMultiMessageItem278     void decrementNonobsoleteCount() { --m_nonobsoleteCount; }
incrementEditableCountMultiMessageItem279     void incrementEditableCount() { ++m_editableCount; }
decrementEditableCountMultiMessageItem280     void decrementEditableCount() { --m_editableCount; }
incrementUnfinishedCountMultiMessageItem281     void incrementUnfinishedCount() { ++m_unfinishedCount; }
decrementUnfinishedCountMultiMessageItem282     void decrementUnfinishedCount() { --m_unfinishedCount; }
283 
284     QString m_id;
285     QString m_text;
286     QString m_pluralText;
287     QString m_comment;
288     int m_nonnullCount; // all
289     int m_nonobsoleteCount; // all
290     int m_editableCount; // read-write
291     int m_unfinishedCount; // read-write
292 };
293 
294 struct MultiContextItem
295 {
296 public:
297     MultiContextItem(int oldCount, ContextItem *ctx, bool writable);
298 
contextItemMultiContextItem299     ContextItem *contextItem(int model) const { return m_contextList[model]; }
300 
multiMessageItemMultiContextItem301     MultiMessageItem *multiMessageItem(int msgIdx) const
302         { return const_cast<MultiMessageItem *>(&m_multiMessageList[msgIdx]); }
messageItemMultiContextItem303     MessageItem *messageItem(int model, int msgIdx) const { return m_messageLists[model][msgIdx]; }
304     int firstNonobsoleteMessageIndex(int msgIdx) const;
305     int findMessage(const QString &sourcetext, const QString &comment) const;
306     int findMessageById(const QString &id) const;
307 
contextMultiContextItem308     QString context() const { return m_context; }
commentMultiContextItem309     QString comment() const { return m_comment; }
messageCountMultiContextItem310     int messageCount() const { return m_messageLists.isEmpty() ? 0 : m_messageLists[0].count(); }
311     // For item count in context list
getNumFinishedMultiContextItem312     int getNumFinished() const { return m_finishedCount; }
getNumEditableMultiContextItem313     int getNumEditable() const { return m_editableCount; }
314     // For background in context list
isObsoleteMultiContextItem315     bool isObsolete() const { return messageCount() && !m_nonobsoleteCount; }
316 
317 private:
318     friend class MultiDataModel;
319     void appendEmptyModel();
320     void assignLastModel(ContextItem *ctx, bool writable);
321     void removeModel(int pos);
322     void moveModel(int oldPos, int newPos); // newPos is *before* removing at oldPos
323     void putMessageItem(int pos, MessageItem *m);
324     void appendMessageItems(const QList<MessageItem *> &m);
325     void removeMultiMessageItem(int pos);
incrementFinishedCountMultiContextItem326     void incrementFinishedCount() { ++m_finishedCount; }
decrementFinishedCountMultiContextItem327     void decrementFinishedCount() { --m_finishedCount; }
incrementEditableCountMultiContextItem328     void incrementEditableCount() { ++m_editableCount; }
decrementEditableCountMultiContextItem329     void decrementEditableCount() { --m_editableCount; }
incrementNonobsoleteCountMultiContextItem330     void incrementNonobsoleteCount() { ++m_nonobsoleteCount; }
decrementNonobsoleteCountMultiContextItem331     void decrementNonobsoleteCount() { --m_nonobsoleteCount; }
332 
333     QString m_context;
334     QString m_comment;
335     QList<MultiMessageItem> m_multiMessageList;
336     QList<ContextItem *> m_contextList;
337     // The next two could be in the MultiMessageItems, but are here for efficiency
338     QList<QList<MessageItem *> > m_messageLists;
339     QList<QList<MessageItem *> *> m_writableMessageLists;
340     int m_finishedCount; // read-write
341     int m_editableCount; // read-write
342     int m_nonobsoleteCount; // all (note: this counts messages, not multi-messages)
343 };
344 
345 
346 class MultiDataIndex
347 {
348 public:
MultiDataIndex()349     MultiDataIndex() : m_model(-1), m_context(-1), m_message(-1) {}
MultiDataIndex(int model,int context,int message)350     MultiDataIndex(int model, int context, int message)
351         : m_model(model), m_context(context), m_message(message) {}
setModel(int model)352     void setModel(int model) { m_model = model; }
model()353     int model() const { return m_model; }
context()354     int context() const { return m_context; }
message()355     int message() const { return m_message; }
isValid()356     bool isValid() const { return m_context >= 0; }
357     bool operator==(const MultiDataIndex &other) const
358         { return m_model == other.m_model && m_context == other.m_context && m_message == other.m_message; }
359     bool operator!=(const MultiDataIndex &other) const { return !(*this == other); }
360 protected:
361     int m_model;
362     int m_context;
363     int m_message;
364 };
365 
366 
367 class MultiDataModelIterator : public MultiDataIndex
368 {
369 public:
370     MultiDataModelIterator(MultiDataModel *model, int modelNo, int contextNo = 0, int messageNo = 0);
371     MessageItem *current() const;
372     bool isValid() const;
373     void operator++();
374 private:
MultiDataModelIterator()375     MultiDataModelIterator() {}
376     MultiDataModel *m_dataModel; // not owned
377 };
378 
379 
380 class MessageModel;
381 
382 class MultiDataModel : public QObject
383 {
384     Q_OBJECT
385 
386 public:
387     MultiDataModel(QObject *parent = 0);
388     ~MultiDataModel();
389 
390     bool isWellMergeable(const DataModel *dm) const;
391     void append(DataModel *dm, bool readWrite);
save(int model,QWidget * parent)392     bool save(int model, QWidget *parent) { return m_dataModels[model]->save(parent); }
saveAs(int model,const QString & newFileName,QWidget * parent)393     bool saveAs(int model, const QString &newFileName, QWidget *parent)
394         { return m_dataModels[model]->saveAs(newFileName, parent); }
release(int model,const QString & fileName,bool verbose,bool ignoreUnfinished,TranslatorSaveMode mode,QWidget * parent)395     bool release(int model, const QString &fileName, bool verbose, bool ignoreUnfinished, TranslatorSaveMode mode, QWidget *parent)
396         { return m_dataModels[model]->release(fileName, verbose, ignoreUnfinished, mode, parent); }
397     void close(int model);
398     void closeAll();
399     int isFileLoaded(const QString &name) const;
400     void moveModel(int oldPos, int newPos); // newPos is *before* removing at oldPos; note that this does not emit update signals
401 
402     // Entire multi-model
modelCount()403     int modelCount() const { return m_dataModels.count(); }
contextCount()404     int contextCount() const { return m_multiContextList.count(); }
messageCount()405     int messageCount() const { return m_numMessages; }
406     // Next two needed for progress indicator in main window
getNumFinished()407     int getNumFinished() const { return m_numFinished; }
getNumEditable()408     int getNumEditable() const { return m_numEditable; }
409     bool isModified() const;
410     QStringList srcFileNames(bool pretty = false) const;
411     QString condensedSrcFileNames(bool pretty = false) const;
412 
413     // Per submodel
414     QString srcFileName(int model, bool pretty = false) const { return m_dataModels[model]->srcFileName(pretty); }
isModelWritable(int model)415     bool isModelWritable(int model) const { return m_dataModels[model]->isWritable(); }
isModified(int model)416     bool isModified(int model) const { return m_dataModels[model]->isModified(); }
setModified(int model,bool dirty)417     void setModified(int model, bool dirty) { m_dataModels[model]->setModified(dirty); }
language(int model)418     QLocale::Language language(int model) const { return m_dataModels[model]->language(); }
sourceLanguage(int model)419     QLocale::Language sourceLanguage(int model) const { return m_dataModels[model]->sourceLanguage(); }
420 
421     // Per message
422     void setTranslation(const MultiDataIndex &index, const QString &translation);
423     void setFinished(const MultiDataIndex &index, bool finished);
424     void setDanger(const MultiDataIndex &index, bool danger);
425 
426     // Retrieve items
model(int i)427     DataModel *model(int i) { return m_dataModels[i]; }
multiContextItem(int ctxIdx)428     MultiContextItem *multiContextItem(int ctxIdx) const
429         { return const_cast<MultiContextItem *>(&m_multiContextList[ctxIdx]); }
multiMessageItem(const MultiDataIndex & index)430     MultiMessageItem *multiMessageItem(const MultiDataIndex &index) const
431         { return multiContextItem(index.context())->multiMessageItem(index.message()); }
432     MessageItem *messageItem(const MultiDataIndex &index, int model) const;
messageItem(const MultiDataIndex & index)433     MessageItem *messageItem(const MultiDataIndex &index) const { return messageItem(index, index.model()); }
434     int findContextIndex(const QString &context) const;
435     MultiContextItem *findContext(const QString &context) const;
436 
437     static QString condenseFileNames(const QStringList &names);
438     static QStringList prettifyFileNames(const QStringList &names);
439 
440     QBrush brushForModel(int model) const;
441 
442 signals:
443     void modelAppended();
444     void modelDeleted(int model);
445     void allModelsDeleted();
446     void languageChanged(int model);
447     void statsChanged(int words, int characters, int cs, int words2, int characters2, int cs2);
448     void modifiedChanged(bool);
449     void multiContextDataChanged(const MultiDataIndex &index);
450     void contextDataChanged(const MultiDataIndex &index);
451     void messageDataChanged(const MultiDataIndex &index);
452     void translationChanged(const MultiDataIndex &index); // Only the primary one
453 
454 private slots:
455     void onModifiedChanged();
456     void onLanguageChanged();
457 
458 private:
459     friend class MultiDataModelIterator;
460     friend class MessageModel;
461 
contextItem(const MultiDataIndex & index)462     ContextItem *contextItem(const MultiDataIndex &index) const
463         { return multiContextItem(index.context())->contextItem(index.model()); }
464 
465     void updateCountsOnAdd(int model, bool writable);
466     void updateCountsOnRemove(int model, bool writable);
incrementFinishedCount()467     void incrementFinishedCount() { ++m_numFinished; }
decrementFinishedCount()468     void decrementFinishedCount() { --m_numFinished; }
incrementEditableCount()469     void incrementEditableCount() { ++m_numEditable; }
decrementEditableCount()470     void decrementEditableCount() { --m_numEditable; }
471 
472     int m_numFinished;
473     int m_numEditable;
474     int m_numMessages;
475 
476     bool m_modified;
477 
478     QList<MultiContextItem> m_multiContextList;
479     QList<DataModel *> m_dataModels;
480 
481     MessageModel *m_msgModel;
482 
483     QColor m_colors[7];
484     QBitmap m_bitmap;
485 };
486 
487 class MessageModel : public QAbstractItemModel
488 {
489     Q_OBJECT
490 
491 public:
492     enum { SortRole = Qt::UserRole };
493 
494     MessageModel(QObject *parent, MultiDataModel *data);
495 
496     // QAbstractItemModel
497     QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
498     QModelIndex parent(const QModelIndex& index) const;
499     int rowCount(const QModelIndex &parent = QModelIndex()) const;
500     int columnCount(const QModelIndex &parent = QModelIndex()) const;
501     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
502 
503     // Convenience
504     MultiDataIndex dataIndex(const QModelIndex &index, int model) const;
dataIndex(const QModelIndex & index)505     MultiDataIndex dataIndex(const QModelIndex &index) const
506         { return dataIndex(index, index.column() - 1 < m_data->modelCount() ? index.column() - 1 : -1); }
507     QModelIndex modelIndex(const MultiDataIndex &index);
508 
509 private slots:
510     void multiContextItemChanged(const MultiDataIndex &index);
511     void contextItemChanged(const MultiDataIndex &index);
512     void messageItemChanged(const MultiDataIndex &index);
513 
514 private:
515     friend class MultiDataModel;
516 
517     MultiDataModel *m_data; // not owned
518 };
519 
520 QT_END_NAMESPACE
521 
522 #endif // MESSAGEMODEL_H
523