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