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 | |
51 | bool danger() const { return m_danger; } |
52 | void setDanger(bool danger) { m_danger = danger; } |
53 | |
54 | void setTranslation(const QString &translation) |
55 | { m_message.setTranslation(translation); } |
56 | |
57 | QString id() const { return m_message.id(); } |
58 | QString context() const { return m_message.context(); } |
59 | QString text() const { return m_message.sourceText(); } |
60 | QString pluralText() const { return m_message.extra(ba: QLatin1String("po-msgid_plural" )); } |
61 | QString () const { return m_message.comment(); } |
62 | QString fileName() const { return m_message.fileName(); } |
63 | QString () const { return m_message.extraComment(); } |
64 | QString () const { return m_message.translatorComment(); } |
65 | void (const QString &cmt) { m_message.setTranslatorComment(cmt); } |
66 | int lineNumber() const { return m_message.lineNumber(); } |
67 | QString translation() const { return m_message.translation(); } |
68 | QStringList translations() const { return m_message.translations(); } |
69 | void setTranslations(const QStringList &translations) |
70 | { m_message.setTranslations(translations); } |
71 | |
72 | TranslatorMessage::Type type() const { return m_message.type(); } |
73 | void setType(TranslatorMessage::Type type) { m_message.setType(type); } |
74 | |
75 | bool isFinished() const { return type() == TranslatorMessage::Finished; } |
76 | bool isObsolete() const |
77 | { return type() == TranslatorMessage::Obsolete || type() == TranslatorMessage::Vanished; } |
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 | |
94 | int finishedDangerCount() const { return m_finishedDangerCount; } |
95 | int unfinishedDangerCount() const { return m_unfinishedDangerCount; } |
96 | |
97 | int finishedCount() const { return m_finishedCount; } |
98 | int unfinishedCount() const { return m_nonobsoleteCount - m_finishedCount; } |
99 | int nonobsoleteCount() const { return m_nonobsoleteCount; } |
100 | |
101 | QString context() const { return m_context; } |
102 | QString () const { return m_comment; } |
103 | QString fullContext() const { return m_comment.trimmed(); } |
104 | |
105 | // For item status in context list |
106 | bool isObsolete() const { return !nonobsoleteCount(); } |
107 | bool isFinished() const { return unfinishedCount() == 0; } |
108 | |
109 | MessageItem *messageItem(int i) const; |
110 | int messageCount() const { return msgItemList.count(); } |
111 | |
112 | MessageItem *findMessage(const QString &sourcetext, const QString &) const; |
113 | |
114 | private: |
115 | friend class DataModel; |
116 | friend class MultiDataModel; |
117 | void appendMessage(const MessageItem &msg) { msgItemList.append(t: msg); } |
118 | void (const QString &x); |
119 | void incrementFinishedCount() { ++m_finishedCount; } |
120 | void decrementFinishedCount() { --m_finishedCount; } |
121 | void incrementFinishedDangerCount() { ++m_finishedDangerCount; } |
122 | void decrementFinishedDangerCount() { --m_finishedDangerCount; } |
123 | void incrementUnfinishedDangerCount() { ++m_unfinishedDangerCount; } |
124 | void decrementUnfinishedDangerCount() { --m_unfinishedDangerCount; } |
125 | void incrementNonobsoleteCount() { ++m_nonobsoleteCount; } |
126 | |
127 | QString ; |
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: |
140 | DataIndex() : m_context(-1), m_message(-1) {} |
141 | DataIndex(int context, int message) : m_context(context), m_message(message) {} |
142 | int context() const { return m_context; } |
143 | int message() const { return m_message; } |
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: |
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, = 0x4 }; |
171 | |
172 | // Specializations |
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 &) const; |
177 | |
178 | ContextItem *contextItem(int index) const; |
179 | MessageItem *messageItem(const DataIndex &index) const; |
180 | |
181 | int messageCount() const { return m_numMessages; } |
182 | bool isEmpty() const { return m_numMessages == 0; } |
183 | bool isModified() const { return m_modified; } |
184 | void setModified(bool dirty); |
185 | bool isWritable() const { return m_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); |
190 | bool save(QWidget *parent) { return save(fileName: 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(fn: 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); |
201 | QLocale::Language language() const { return m_language; } |
202 | QLocale::Country country() const { return m_country; } |
203 | void setSourceLanguageAndCountry(QLocale::Language lang, QLocale::Country country); |
204 | QLocale::Language sourceLanguage() const { return m_sourceLanguage; } |
205 | QLocale::Country sourceCountry() const { return m_sourceCountry; } |
206 | |
207 | const QString &localizedLanguage() const { return m_localizedLanguage; } |
208 | const QStringList &numerusForms() const { return m_numerusForms; } |
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 | |
215 | int getSrcWords() const { return m_srcWords; } |
216 | int getSrcChars() const { return m_srcChars; } |
217 | int () 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 ; |
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 ; |
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); |
260 | QString id() const { return m_id; } |
261 | QString text() const { return m_text; } |
262 | QString pluralText() const { return m_pluralText; } |
263 | QString () const { return m_comment; } |
264 | bool isEmpty() const { return !m_nonnullCount; } |
265 | // The next two include also read-only |
266 | bool isObsolete() const { return m_nonnullCount && !m_nonobsoleteCount; } |
267 | int countNonobsolete() const { return m_nonobsoleteCount; } |
268 | // The next three include only read-write |
269 | int countEditable() const { return m_editableCount; } |
270 | bool isUnfinished() const { return m_unfinishedCount != 0; } |
271 | int countUnfinished() const { return m_unfinishedCount; } |
272 | |
273 | private: |
274 | friend class MultiDataModel; |
275 | void incrementNonnullCount() { ++m_nonnullCount; } |
276 | void decrementNonnullCount() { --m_nonnullCount; } |
277 | void incrementNonobsoleteCount() { ++m_nonobsoleteCount; } |
278 | void decrementNonobsoleteCount() { --m_nonobsoleteCount; } |
279 | void incrementEditableCount() { ++m_editableCount; } |
280 | void decrementEditableCount() { --m_editableCount; } |
281 | void incrementUnfinishedCount() { ++m_unfinishedCount; } |
282 | void decrementUnfinishedCount() { --m_unfinishedCount; } |
283 | |
284 | QString m_id; |
285 | QString m_text; |
286 | QString m_pluralText; |
287 | QString ; |
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 | |
299 | ContextItem *contextItem(int model) const { return m_contextList[model]; } |
300 | |
301 | MultiMessageItem *multiMessageItem(int msgIdx) const |
302 | { return const_cast<MultiMessageItem *>(&m_multiMessageList[msgIdx]); } |
303 | 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 &) const; |
306 | int findMessageById(const QString &id) const; |
307 | |
308 | QString context() const { return m_context; } |
309 | QString () const { return m_comment; } |
310 | int messageCount() const { return m_messageLists.isEmpty() ? 0 : m_messageLists[0].count(); } |
311 | // For item count in context list |
312 | int getNumFinished() const { return m_finishedCount; } |
313 | int getNumEditable() const { return m_editableCount; } |
314 | // For background in context list |
315 | 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); |
326 | void incrementFinishedCount() { ++m_finishedCount; } |
327 | void decrementFinishedCount() { --m_finishedCount; } |
328 | void incrementEditableCount() { ++m_editableCount; } |
329 | void decrementEditableCount() { --m_editableCount; } |
330 | void incrementNonobsoleteCount() { ++m_nonobsoleteCount; } |
331 | void decrementNonobsoleteCount() { --m_nonobsoleteCount; } |
332 | |
333 | QString m_context; |
334 | QString ; |
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: |
349 | MultiDataIndex() : m_model(-1), m_context(-1), m_message(-1) {} |
350 | MultiDataIndex(int model, int context, int message) |
351 | : m_model(model), m_context(context), m_message(message) {} |
352 | void setModel(int model) { m_model = model; } |
353 | int model() const { return m_model; } |
354 | int context() const { return m_context; } |
355 | int message() const { return m_message; } |
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: |
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); |
392 | bool save(int model, QWidget *parent) { return m_dataModels[model]->save(parent); } |
393 | bool saveAs(int model, const QString &newFileName, QWidget *parent) |
394 | { return m_dataModels[model]->saveAs(newFileName, 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 |
403 | int modelCount() const { return m_dataModels.count(); } |
404 | int contextCount() const { return m_multiContextList.count(); } |
405 | int messageCount() const { return m_numMessages; } |
406 | // Next two needed for progress indicator in main window |
407 | int getNumFinished() const { return m_numFinished; } |
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); } |
415 | bool isModelWritable(int model) const { return m_dataModels[model]->isWritable(); } |
416 | bool isModified(int model) const { return m_dataModels[model]->isModified(); } |
417 | void setModified(int model, bool dirty) { m_dataModels[model]->setModified(dirty); } |
418 | QLocale::Language language(int model) const { return m_dataModels[model]->language(); } |
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 |
427 | DataModel *model(int i) { return m_dataModels[i]; } |
428 | MultiContextItem *multiContextItem(int ctxIdx) const |
429 | { return const_cast<MultiContextItem *>(&m_multiContextList[ctxIdx]); } |
430 | MultiMessageItem *multiMessageItem(const MultiDataIndex &index) const |
431 | { return multiContextItem(ctxIdx: index.context())->multiMessageItem(msgIdx: index.message()); } |
432 | MessageItem *messageItem(const MultiDataIndex &index, int model) const; |
433 | MessageItem *messageItem(const MultiDataIndex &index) const { return messageItem(index, model: 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 | |
462 | ContextItem *contextItem(const MultiDataIndex &index) const |
463 | { return multiContextItem(ctxIdx: index.context())->contextItem(model: index.model()); } |
464 | |
465 | void updateCountsOnAdd(int model, bool writable); |
466 | void updateCountsOnRemove(int model, bool writable); |
467 | void incrementFinishedCount() { ++m_numFinished; } |
468 | void decrementFinishedCount() { --m_numFinished; } |
469 | void incrementEditableCount() { ++m_numEditable; } |
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; |
505 | MultiDataIndex dataIndex(const QModelIndex &index) const |
506 | { return dataIndex(index, model: 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 | |