9#include <QtCore/QCoreApplication>
10#include <QtCore/QDebug>
12#include <QtWidgets/QMessageBox>
13#include <QtGui/QPainter>
14#include <QtGui/QPixmap>
15#include <QtGui/QTextDocument>
17#include <private/qtranslator_p.h>
21using namespace Qt::Literals::StringLiterals;
25 constexpr QStringView notation = u"&#";
26 constexpr QChar cx = u'x';
27 constexpr QChar ce = u';';
30 result.reserve(str.size());
31 qsizetype offset = str.indexOf(notation);
34 qsizetype metaLen = 2;
35 if (str.size() <= offset + metaLen)
39 if (
const QChar ch = str[offset + metaLen]; ch == cx) {
45 const qsizetype end = str.sliced(offset).indexOf(ce);
48 if (
const uint c = str.sliced(offset, end).toUInt(&valid, base);
49 valid && c <= QChar::LastValidCodePoint) {
50 if (QChar::requiresSurrogates(c))
51 result += str.sliced(0, offset - metaLen) + QChar(QChar::highSurrogate(c))
52 + QChar(QChar::lowSurrogate(c));
54 result += str.sliced(0, offset - metaLen) + QChar(c);
55 str.slice(offset + end + 1);
56 offset = str.indexOf(notation);
60 result += str.sliced(0, offset);
62 offset = str.indexOf(notation);
71 result.reserve(str.size());
72 for (
const QChar ch : str) {
73 if (uint c = ch.unicode(); Q_UNLIKELY(!ch.isPrint() && c > 0x20))
74 result += QString(
"&#x%1;"_L1).arg(c, 0, 16);
83 return ncrMode ? showNcr(str) : resolveNcr(str);
89
90
91
92
94MessageItem::MessageItem(
const TranslatorMessage &message)
95 : m_message(message), m_danger(
false), m_ncrMode(
false)
97 if (m_message.translation().isEmpty())
98 m_message.setTranslation(QString());
103 Qt::CaseSensitivity cs)
const
105 return matchSubstring
106 ? text().indexOf(findText, 0, cs) >= 0
107 : text().compare(findText, cs) == 0;
112 m_message.setTranslation(resolveNcr(translation));
117 return adjustNcrVisibility(m_message.sourceText(), m_ncrMode);
122 return adjustNcrVisibility(m_message.extra(
"po-msgid_plural"_L1), m_ncrMode);
127 return adjustNcrVisibility(m_message.translation(), m_ncrMode);
132 QStringList translations;
133 translations.reserve(m_message.translations().size());
134 for (QString &trans : m_message.translations())
135 translations.append(adjustNcrVisibility(trans, m_ncrMode));
142 trans.reserve(translations.size());
143 for (
const QString &t : translations)
144 trans.append(resolveNcr(t));
146 m_message.setTranslations(trans);
150
151
152
153
155void GroupItem::appendToComment(
const QString &str)
157 if (!m_comment.isEmpty())
158 m_comment +=
"\n\n"_L1;
164 if (i >= 0 && i < msgItemList.size())
165 return const_cast<MessageItem *>(&msgItemList[i]);
166 Q_ASSERT(i >= 0 && i < msgItemList.size());
174 if (mi->text() == sourcetext && mi->comment() == comment)
184 if (m->id() == msgid)
191
192
193
194
203 m_language(QLocale::Language(-1)),
204 m_sourceLanguage(QLocale::Language(-1)),
205 m_territory(QLocale::Territory(-1)),
206 m_sourceTerritory(QLocale::Territory(-1))
211 QStringList translations =
212 Translator::normalizedTranslations(m.message(), m_numerusForms.size());
213 QStringList ncrTranslations;
214 ncrTranslations.reserve(translations.size());
215 for (
const QString &translate : std::as_const(translations))
216 ncrTranslations.append(adjustNcrVisibility(translate, m.ncrMode()));
217 return ncrTranslations;
222 const auto &list = type == IDBASED ? m_labelList : m_contextList;
223 if (groupId >= 0 && groupId < list.size())
224 return const_cast<
GroupItem *>(&list[groupId]);
242 const auto &list = type == IDBASED ? m_labelList : m_contextList;
243 for (
int g = 0; g < list.size(); ++g) {
245 if (gi->group() == groupName)
252 const QString &sourcetext,
const QString &comment)
const
254 if (context.isEmpty()) {
255 if (
GroupItem *gi = findGroup(label, IDBASED))
256 return gi->findMessage(sourcetext, comment);
258 if (
GroupItem *gi = findGroup(context, TEXTBASED))
259 return gi->findMessage(sourcetext, comment);
268 auto countSameMessages = [two, one, &inBoth](
int count,
TranslationType type) {
269 for (
int i = 0; i < count; ++i) {
271 if (
GroupItem *g = one->findGroup(gi->group(), type)) {
274 if (g->findMessage(m->text(), m->comment()))
295bool DataModel::
load(
const QString &fileName,
bool *langGuessed, QWidget *parent)
299 bool ok = tor.load(fileName, cd,
"auto"_L1);
301 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"), cd.error());
306 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"),
307 tr(
"The translation file '%1' will not be loaded because it is empty.")
308 .arg(fileName.toHtmlEscaped()));
313 if (!dupes.byId.isEmpty() || !dupes.byContents.isEmpty()) {
314 QString err = tr(
"<qt>Duplicate messages found in '%1':").arg(fileName.toHtmlEscaped());
316 for (
auto it = dupes.byId.begin(); it != dupes.byId.end(); ++it) {
317 if (++numdups >= 5) {
318 err += tr(
"<p>[more duplicates omitted]");
321 err += tr(
"<p>* ID: %1").arg(tor.message(it.key()).id().toHtmlEscaped());
323 for (
auto it = dupes.byContents.begin(); it != dupes.byContents.end(); ++it) {
325 if (++numdups >= 5) {
326 err += tr(
"<p>[more duplicates omitted]");
329 err += tr(
"<p>* Context: %1<br>* Source: %2")
330 .arg(msg.context().toHtmlEscaped(), msg.sourceText().toHtmlEscaped());
331 if (!msg.comment().isEmpty())
332 err += tr(
"<br>* Comment: %3").arg(msg.comment().toHtmlEscaped());
335 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"), err);
338 m_srcFileName = fileName;
341 m_contextList.clear();
350 QList<GroupItem> &list, QHash<QString,
int> &groups,
352 if (!groups.contains(group)) {
353 groups.insert(group, list.size());
356 GroupItem *gi = groupItem(groups.value(group), type);
358 gi->appendToComment(msg.comment());
362 gi->incrementFinishedCount();
365 doCharCounting(tmp.text(), m_srcWords, m_srcChars, m_srcCharsSpc);
366 doCharCounting(tmp.pluralText(), m_srcWords, m_srcChars, m_srcCharsSpc);
367 gi->incrementNonobsoleteCount();
369 gi->appendMessage(tmp);
374 QHash<QString,
int> labels;
375 QHash<QString,
int> contexts;
376 for (
const TranslatorMessage &msg : tor.messages()) {
377 if (
const QString ctx = msg.context(); !ctx.isEmpty())
378 loadMessage(msg, ctx, m_contextList, contexts, TEXTBASED);
380 loadMessage(msg, msg.label(), m_labelList, labels, IDBASED);
390 *langGuessed =
false;
391 QString lang = tor.languageCode();
392 if (lang.isEmpty()) {
393 lang = QFileInfo(fileName).baseName();
394 int pos = lang.indexOf(u'_');
396 lang.remove(0, pos + 1);
402 QLocale::Territory c;
403 Translator::languageAndTerritory(lang, &l, &c);
404 if (l == QLocale::C) {
410 if (!setLanguageAndTerritory(l, c))
411 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"),
412 tr(
"Linguist does not know the plural rules for '%1'.\n"
413 "Will assume a single universal form.")
414 .arg(m_localizedLanguage));
419 lang = tor.sourceLanguageCode();
420 if (lang.isEmpty()) {
422 c = QLocale::AnyTerritory;
424 Translator::languageAndTerritory(lang, &l, &c);
426 setSourceLanguageAndTerritory(l, c);
433bool DataModel::save(
const QString &fileName, QWidget *parent)
441 tor.setLanguageCode(Translator::makeLanguageCode(m_language, m_territory));
442 tor.setSourceLanguageCode(Translator::makeLanguageCode(m_sourceLanguage, m_sourceTerritory));
448 bool ok = tor.save(fileName, cd,
"auto"_L1);
451 if (!cd.error().isEmpty())
452 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"), cd.error());
458 if (!save(newFileName, parent))
460 m_srcFileName = newFileName;
467 QFile file(fileName);
468 if (!file.open(QIODevice::WriteOnly)) {
469 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"),
470 tr(
"Cannot create '%2': %1").arg(file.errorString()).arg(fileName));
474 QLocale locale(m_language, m_territory);
475 tor.setLanguageCode(locale.name());
484 bool ok = saveQM(tor, file, cd);
486 QMessageBox::warning(parent, QObject::tr(
"Qt Linguist"), cd.error());
494 for (
int i = 0; i < text.size(); ++i) {
495 if (text[i].isLetterOrNumber() || text[i] == u'_') {
503 if (!text[i].isSpace())
508bool DataModel::setLanguageAndTerritory(QLocale::Language lang, QLocale::Territory territory)
510 if (m_language == lang && m_territory == territory)
513 m_territory = territory;
515 if (lang == QLocale::C || uint(lang) > uint(QLocale::LastLanguage))
516 lang = QLocale::English;
517 bool ok = getCountNeed(lang, territory, m_countRefNeeds, &m_numerusForms);
518 QLocale loc(lang, territory);
521 const bool mentionTerritory = (loc.territory() != territory) || [lang, territory]() {
522 const auto locales = QLocale::matchingLocales(lang, QLocale::AnyScript,
523 QLocale::AnyTerritory);
524 return std::any_of(locales.cbegin(), locales.cend(), [territory](
const QLocale &locale) {
525 return locale.territory() != territory;
528 m_localizedLanguage = mentionTerritory
530 ? tr(
"%1 (%2)").arg(loc.nativeLanguageName(), loc.nativeTerritoryName())
531 : loc.nativeLanguageName();
533 m_numerusForms.clear();
534 m_numerusForms << tr(
"Universal Form");
536 emit languageChanged();
541void DataModel::setSourceLanguageAndTerritory(QLocale::Language lang, QLocale::Territory territory)
543 if (m_sourceLanguage == lang && m_sourceTerritory == territory)
545 m_sourceLanguage = lang;
546 m_sourceTerritory = territory;
555 auto updateMessageStatistics = [&stats,
this](
const MessageItem *mi) {
559 bool hasDanger =
false;
560 for (
const QString &trnsl : mi->translations()) {
561 doCharCounting(trnsl, stats.wordsFinished, stats.charsFinished, stats.charsSpacesFinished);
562 hasDanger |= mi->danger();
569 bool hasDanger =
false;
570 for (
const QString &trnsl : mi->translations()) {
571 doCharCounting(trnsl, stats.wordsUnfinished, stats.charsUnfinished, stats.charsSpacesUnfinished);
572 hasDanger |= mi->danger();
589 emit statsChanged(stats);
594 if (m_modified == isModified)
596 m_modified = isModified;
597 emit modifiedChanged();
600QString
DataModel::prettifyPlainFileName(
const QString &fn)
602 static QString workdir = QDir::currentPath() + u'/';
604 return QDir::toNativeSeparators(fn.startsWith(workdir) ? fn.mid(workdir.size()) : fn);
609 if (fn.startsWith(u'='))
610 return u'=' + prettifyPlainFileName(fn.mid(1));
612 return prettifyPlainFileName(fn);
616
617
618
619
629 const qsizetype size =
630 isIdBased() ? m_model->m_labelList.size() : m_model->m_contextList.size();
638 : m_model->m_contextList.at(
m_group).messageCount();
651
652
653
654
661 QList<MessageItem *> mList;
662 QList<MessageItem *> eList;
667 m_multiMessageList.append(MultiMessageItem(m));
669 for (
int i = 0; i < oldCount; ++i) {
670 m_messageLists.append(eList);
671 m_writableMessageLists.append(0);
672 m_groupList.append(0);
674 m_messageLists.append(mList);
675 m_writableMessageLists.append(writable ? &m_messageLists.last() : 0);
676 m_groupList.append(groupItem);
681 QList<MessageItem *> eList;
684 m_messageLists.append(eList);
685 m_writableMessageLists.append(0);
686 m_groupList.append(0);
692 m_writableMessageLists.last() = &m_messageLists.last();
693 m_groupList.last() = g;
699 m_groupList.insert(newPos, m_groupList[oldPos]);
700 m_messageLists.insert(newPos, m_messageLists[oldPos]);
701 m_writableMessageLists.insert(newPos, m_writableMessageLists[oldPos]);
702 removeModel(oldPos < newPos ? oldPos : oldPos + 1);
707 m_groupList.removeAt(pos);
708 m_messageLists.removeAt(pos);
709 m_writableMessageLists.removeAt(pos);
714 m_messageLists.last()[pos] = m;
717void MultiGroupItem::appendMessageItems(
const QList<MessageItem *> &m)
719 QList<MessageItem *> nullItems = m;
720 for (
int i = 0; i < nullItems.size(); ++i)
722 for (
int i = 0; i < m_messageLists.size() - 1; ++i)
723 m_messageLists[i] += nullItems;
724 m_messageLists.last() += m;
725 for (MessageItem *mi : m)
726 m_multiMessageList.append(MultiMessageItem(mi));
731 for (
int i = 0; i < m_messageLists.size(); ++i)
732 m_messageLists[i].removeAt(pos);
733 m_multiMessageList.removeAt(pos);
738 for (
int i = 0; i < m_messageLists.size(); ++i)
739 if (m_messageLists[i][msgIdx] && !m_messageLists[i][msgIdx]->isObsolete())
748 if (m->text() == sourcetext && m->comment() == comment)
765
766
767
768
771 QColor(210, 235, 250),
772 QColor(210, 250, 220),
773 QColor(250, 240, 210),
774 QColor(210, 250, 250),
775 QColor(250, 230, 200),
776 QColor(250, 210, 210),
777 QColor(235, 210, 250),
799 m_bitmap = QBitmap(8, 8);
801 QPainter p(&m_bitmap);
802 for (
int j = 0; j < 8; ++j)
803 for (
int k = 0; k < 8; ++k)
810 qDeleteAll(m_dataModels);
815 QBrush brush(m_colors[model % 7]);
816 if (!isModelWritable(model))
817 brush.setTexture(m_bitmap);
823 m_colors = isDarkMode() ? darkPaletteColors : lightPaletteColors;
833 auto countInBothNew = [dm, &inBothNew,
this](
int count,
TranslationType type) {
834 for (
int i = 0; i < count; ++i) {
845 if ((type ==
TEXTBASED && mgi->findMessage(m->text(), m->comment()) >= 0)
846 || (type ==
IDBASED && mgi->findMessageById(m->id()) >= 0))
859 auto countInBothOld = [
this, dm, &inBothOld](
int count,
TranslationType type) {
860 for (
int k = 0; k < count; ++k) {
862 if (
GroupItem *g = dm->findGroup(mgi->group(), type)) {
865 if ((type ==
TEXTBASED && g->findMessage(m->text(), m->comment()))
866 || (type ==
IDBASED && g->findMessageById(m->id())))
878 return newRatio + oldRatio > 90;
884 m_dataModels.append(dm);
887 QList<MultiGroupItem> &multiGroupList) {
889 msgModel->beginInsertColumns(QModelIndex(), insCol, insCol);
890 for (
int j = 0; j < count; ++j) {
891 msgModel->beginInsertColumns(msgModel->createIndex(j, 0), insCol, insCol);
892 multiGroupList[j].appendEmptyModel();
893 msgModel->endInsertColumns();
895 msgModel->endInsertColumns();
897 int appendedGroups = 0;
898 for (
int i = 0; i < count; ++i) {
900 int gidx = findGroupIndex(g->group(), type);
903 mgi->assignLastModel(g, readWrite);
904 QList<MessageItem *> appendItems;
908 int msgIdx = type ==
IDBASED ? mgi->findMessageById(m->id())
909 : mgi->findMessage(m->text(), m->comment());
912 mgi->putMessageItem(msgIdx, m);
916 if (!appendItems.isEmpty()) {
918 msgModel->beginInsertRows(msgModel->createIndex(gidx, 0), msgCnt,
919 msgCnt + appendItems.size() - 1);
920 mgi->appendMessageItems(appendItems);
921 msgModel->endInsertRows();
922 m_numMessages += appendItems.size();
930 if (appendedGroups) {
934 msgModel->beginInsertRows(QModelIndex(), groupCount - appendedGroups, groupCount - 1);
935 msgModel->endInsertRows();
939 appendGroups(TEXTBASED, m_textBasedMsgModel, m_multiContextList);
940 appendGroups(IDBASED, m_idBasedMsgModel, m_multiLabelList);
944 connect(dm, &DataModel::modifiedChanged,
945 this, &MultiDataModel::onModifiedChanged);
948 connect(dm, &DataModel::statsChanged,
949 this, &MultiDataModel::statsChanged);
950 emit modelAppended();
955 if (m_dataModels.size() == 1) {
958 int delCol = model + 1;
959 auto removeModel = [delCol, model](
auto *msgModel,
auto &list) {
960 msgModel->beginRemoveColumns(QModelIndex(), delCol, delCol);
961 for (
int i = list.size(); --i >= 0;) {
962 msgModel->beginRemoveColumns(msgModel->createIndex(i, 0), delCol, delCol);
963 list[i].removeModel(model);
964 msgModel->endRemoveColumns();
966 msgModel->endRemoveColumns();
970 removeModel(m_idBasedMsgModel, m_multiLabelList);
971 removeModel(m_textBasedMsgModel, m_multiContextList);
972 delete m_dataModels.takeAt(model);
973 emit modelDeleted(model);
975 auto removeMessages = [
this](
auto *msgModel,
auto &list) {
976 for (
int i = list.size(); --i >= 0;) {
978 QModelIndex idx = msgModel->createIndex(i, 0);
979 for (
int j = mi.messageCount(); --j >= 0;)
980 if (mi.multiMessageItem(j)->isEmpty()) {
981 msgModel->beginRemoveRows(idx, j, j);
982 mi.removeMultiMessageItem(j);
983 msgModel->endRemoveRows();
986 if (!mi.messageCount()) {
987 msgModel->beginRemoveRows(QModelIndex(), i, i);
989 msgModel->endRemoveRows();
994 removeMessages(m_idBasedMsgModel, m_multiLabelList);
995 removeMessages(m_textBasedMsgModel, m_multiContextList);
1002 m_idBasedMsgModel->beginResetModel();
1003 m_textBasedMsgModel->beginResetModel();
1007 qDeleteAll(m_dataModels);
1008 m_dataModels.clear();
1009 m_multiContextList.clear();
1010 m_multiLabelList.clear();
1011 m_textBasedMsgModel->endResetModel();
1012 m_idBasedMsgModel->endResetModel();
1013 emit allModelsDeleted();
1014 onModifiedChanged();
1020 int delPos = oldPos < newPos ? oldPos : oldPos + 1;
1021 m_dataModels.insert(newPos, m_dataModels[oldPos]);
1022 m_dataModels.removeAt(delPos);
1023 for (
int i = 0; i < m_multiContextList.size(); ++i)
1024 m_multiContextList[i].moveModel(oldPos, newPos);
1025 for (
int i = 0; i < m_multiLabelList.size(); ++i)
1026 m_multiLabelList[i].moveModel(oldPos, newPos);
1033 for (
const QString &name : names)
1034 out << DataModel::prettifyFileName(name);
1040 if (names.isEmpty())
1043 if (names.size() < 2)
1044 return names.first();
1046 QString prefix = names.first();
1047 if (prefix.startsWith(u'='))
1048 prefix.remove(0, 1);
1049 QString suffix = prefix;
1050 for (
int i = 1; i < names.size(); ++i) {
1051 QString fn = names[i];
1052 if (fn.startsWith(u'='))
1054 for (
int j = 0; j < prefix.size(); ++j)
1055 if (fn[j] != prefix[j]) {
1056 if (j < prefix.size()) {
1057 while (j > 0 && prefix[j - 1].isLetterOrNumber())
1063 int fnl = fn.size() - 1;
1064 int sxl = suffix.size() - 1;
1065 for (
int k = 0; k <= sxl; ++k)
1066 if (fn[fnl - k] != suffix[sxl - k]) {
1068 while (k > 0 && suffix[sxl - k + 1].isLetterOrNumber())
1070 if (prefix.size() + k > fnl)
1072 suffix.remove(0, sxl - k + 1);
1077 QString ret = prefix + u'{';
1078 int pxl = prefix.size();
1079 int sxl = suffix.size();
1080 for (
int j = 0; j < names.size(); ++j) {
1084 QString fn = names[j];
1085 if (fn.startsWith(u'=')) {
1089 ret += fn.mid(off, fn.size() - sxl - off);
1091 ret += u'}' + suffix;
1098 for (DataModel *dm : m_dataModels)
1099 names << (dm->isWritable() ? QString() : QString::fromLatin1(
"=")) + dm->srcFileName(pretty);
1105 return condenseFileNames(srcFileNames(pretty));
1115 for (
const DataModel *mdl : m_dataModels)
1116 if (mdl->isModified())
1124 if (modified != m_modified) {
1125 emit modifiedChanged(modified);
1126 m_modified = modified;
1133 while (sender() != m_dataModels[i])
1135 emit languageChanged(i);
1145 for (
int i = 0; i < m_dataModels.size(); ++i)
1146 if (m_dataModels[i]->srcFileName() == name)
1153 const auto &list = type == IDBASED ? m_multiLabelList : m_multiContextList;
1154 for (
int i = 0; i < list.size(); ++i) {
1156 if (mg.group() == group)
1164 const auto &list = type == IDBASED ? m_multiLabelList : m_multiContextList;
1165 for (
int i = 0; i < list.size(); ++i) {
1167 if (mgi.group() == group)
1175 const auto &list = index.isIdBased() ? m_multiLabelList : m_multiContextList;
1188 Q_ASSERT(index
.group() < groupCount);
1195 if (translation == m->translation())
1197 m->setTranslation(translation);
1199 emit translationChanged(index);
1211 mm->decrementUnfinishedCount();
1213 incrementFinishedCount();
1214 mgi->incrementFinishedCount();
1215 emit multiGroupDataChanged(index);
1217 gi->incrementFinishedCount();
1219 gi->incrementFinishedDangerCount();
1220 gi->decrementUnfinishedDangerCount();
1222 emit groupDataChanged(index);
1224 emit groupDataChanged(index);
1226 emit messageDataChanged(index);
1230 mm->incrementUnfinishedCount();
1232 decrementFinishedCount();
1233 mgi->decrementFinishedCount();
1234 emit multiGroupDataChanged(index);
1236 gi->decrementFinishedCount();
1238 gi->decrementFinishedDangerCount();
1239 gi->incrementUnfinishedDangerCount();
1242 emit groupDataChanged(index);
1244 emit groupDataChanged(index);
1246 emit messageDataChanged(index);
1257 gi->incrementFinishedDangerCount();
1259 emit groupDataChanged(index);
1261 gi->incrementUnfinishedDangerCount();
1263 emit groupDataChanged(index);
1265 emit messageDataChanged(index);
1269 gi->decrementFinishedDangerCount();
1271 emit groupDataChanged(index);
1273 gi->decrementUnfinishedDangerCount();
1275 emit groupDataChanged(index);
1277 emit messageDataChanged(index);
1284 auto updateCount = [model, writable,
this](
auto &mg) {
1285 for (
int j = 0; j < mg.messageCount(); ++j)
1288 mm->incrementNonnullCount();
1292 mg.incrementEditableCount();
1293 incrementEditableCount();
1295 mg.incrementFinishedCount();
1296 incrementFinishedCount();
1298 mm->incrementUnfinishedCount();
1302 mg.decrementFinishedCount();
1303 decrementFinishedCount();
1305 mm->incrementUnfinishedCount();
1307 mm->incrementEditableCount();
1309 mg.incrementNonobsoleteCount();
1310 mm->incrementNonobsoleteCount();
1314 for (
auto &mg : m_multiContextList)
1316 for (
auto &mg : m_multiLabelList)
1320void MultiDataModel::updateCountsOnRemove(
int model,
bool writable)
1322 auto updateCount = [model, writable,
this](
auto &mg) {
1323 for (
int j = 0; j < mg.messageCount(); ++j)
1326 mm->decrementNonnullCount();
1328 mm->decrementNonobsoleteCount();
1329 mg.decrementNonobsoleteCount();
1331 mm->decrementEditableCount();
1333 mg.decrementEditableCount();
1334 decrementEditableCount();
1336 mg.decrementFinishedCount();
1337 decrementFinishedCount();
1339 mm->decrementUnfinishedCount();
1342 mm->decrementUnfinishedCount();
1344 mg.incrementFinishedCount();
1345 incrementFinishedCount();
1352 for (
auto &mg : m_multiContextList)
1354 for (
auto &mg : m_multiLabelList)
1359
1360
1361
1362
1365 int model,
int group,
int message)
1375 ? m_dataModel->m_multiLabelList.at(
m_group).messageCount()
1376 : m_dataModel->m_multiContextList.at(
m_group).messageCount();
1385 const qsizetype size =
isIdBased() ? m_dataModel->m_multiLabelList.size()
1386 : m_dataModel->m_multiContextList.size();
1396
1397
1398
1399
1404 if (translationType ==
IDBASED)
1405 data->m_idBasedMsgModel =
this;
1407 data->m_textBasedMsgModel =
this;
1408 connect(m_data, &MultiDataModel::multiGroupDataChanged,
this,
1409 &MessageModel::multiGroupItemChanged);
1416 if (!parent.isValid())
1417 return createIndex(row, column);
1418 if (!parent.internalId())
1419 return createIndex(row, column, parent.row() + 1);
1420 return QModelIndex();
1425 if (index.internalId())
1426 return createIndex(index.internalId() - 1, 0);
1427 return QModelIndex();
1435 emit dataChanged(idx, idx);
1443 emit dataChanged(idx, idx);
1451 emit dataChanged(idx, idx);
1463 if (!parent.isValid())
1466 if (!parent.internalId())
1467 return m_data->multiGroupItem(parent.row(), m_translationType)->messageCount();
1478 static QVariant pxOn;
1479 static QVariant pxOff;
1480 static QVariant pxObsolete;
1481 static QVariant pxDanger;
1482 static QVariant pxWarning;
1483 static QVariant pxEmpty;
1485 static Qt::ColorScheme mode = Qt::ColorScheme::Unknown;
1489 (dark && mode != Qt::ColorScheme::Dark) || (!dark && mode != Qt::ColorScheme::Light)) {
1490 pxOn = createMarkIcon(TranslationMarks::OnMark, dark);
1491 pxOff = createMarkIcon(TranslationMarks::OffMark, dark);
1492 pxObsolete = createMarkIcon(TranslationMarks::ObsoleteMark, dark);
1493 pxDanger = createMarkIcon(TranslationMarks::DangerMark, dark);
1494 pxWarning = createMarkIcon(TranslationMarks::WarningMark, dark);
1495 pxEmpty = createMarkIcon(TranslationMarks::EmptyMark, dark);
1496 mode = dark ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
1499 int row = index.row();
1500 int column = index.column() - 1;
1507 if (role == Qt::ToolTipRole && column < numLangs) {
1508 return tr(
"Completion status for %1").arg(m_data
->model(column
)->localizedLanguage());
1509 }
else if (index.internalId()) {
1511 int crow = index.internalId() - 1;
1516 if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column == numLangs)) {
1517 switch (column - numLangs) {
1520 if (m_translationType ==
IDBASED)
1523 return text.simplified();
1525 return tr(
"<context comment>");
1528 if (m_translationType ==
IDBASED)
1535 else if (role == Qt::DecorationRole && column < numLangs) {
1539 if (msgItem->translation().isEmpty())
1554 else if (role == SortRole) {
1555 switch (column - numLangs) {
1562 int rslt = !msgItem->translation().isEmpty();
1573 }
else if (role == Qt::ForegroundRole && column > 0
1574 && mgi->multiMessageItem(row)->isObsolete()) {
1575 return QBrush(Qt::darkGray);
1576 }
else if (role == Qt::ForegroundRole && column == numLangs
1577 && mgi->multiMessageItem(row)->text().isEmpty()) {
1578 return QBrush(QColor(0, 0xa0, 0xa0));
1579 }
else if (role == Qt::BackgroundRole) {
1580 if (column < numLangs && numLangs != 1)
1581 return m_data->brushForModel(column);
1585 const qsizetype groupCount =
1587 if (row >= groupCount || !index.isValid())
1591 if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
1592 switch (column - numLangs) {
1595 const QString groupName = mgi->group().simplified();
1596 if (m_translationType ==
IDBASED and groupName.isEmpty()) {
1597 return tr(
"<unnamed label>");
1602 if (role == Qt::ToolTipRole) {
1603 return tr(
"%n unfinished message(s) left.", 0,
1606 return QString::asprintf(
"%d/%d", mgi->getNumFinished(), mgi->getNumEditable());
1612 else if (role == Qt::FontRole && column == m_data->modelCount()) {
1614 boldFont.setBold(
true);
1617 else if (role == Qt::DecorationRole && column < numLangs) {
1627 else if (role == SortRole) {
1628 switch (column - numLangs) {
1630 return mgi->group().simplified();
1638 int rslt = percent * (((1 << 28) - 1) / 100) + totalItems;
1653 }
else if (role == Qt::ForegroundRole && column >= numLangs && mgi->isObsolete()) {
1654 return QBrush(Qt::darkGray);
1655 }
else if (role == Qt::ForegroundRole && column == numLangs
1656 && m_translationType == IDBASED) {
1657 return QBrush(QColor(0, 0xa0, 0xa0));
1658 }
else if (role == Qt::BackgroundRole) {
1659 if (column < numLangs && numLangs != 1) {
1660 QBrush brush = m_data->brushForModel(column);
1662 brush.setColor(brush.color().darker(108));
1673 Q_ASSERT(index.isValid());
1674 Q_ASSERT(index.internalId());
1675 return MultiDataIndex(m_translationType, model, index.internalId() - 1, index.row());
TranslatorSaveMode m_saveMode
DataIndex(TranslationType type, int group=-1, int message=-1)
TranslationType translationType() const
DataModelIterator(TranslationType type, const DataModel *model=0, int groupNo=0, int messageNo=0)
MessageItem * current() const
bool load(const QString &fileName, bool *langGuessed, QWidget *parent)
GroupItem * findGroup(const QString &group, TranslationType type) const
void doCharCounting(const QString &text, int &trW, int &trC, int &trCS)
bool saveAs(const QString &newFileName, QWidget *parent)
MessageItem * messageItem(const DataIndex &index) const
bool isWellMergeable(const DataModel *other) const
void setModified(bool dirty)
MessageItem * findMessage(const QString &context, const QString &label, const QString &sourcetext, const QString &comment) const
bool release(const QString &fileName, bool verbose, bool ignoreUnfinished, TranslatorSaveMode mode, QWidget *parent)
GroupItem * groupItem(int index, TranslationType type) const
void setWritable(bool writable)
GroupItem * groupItem(DataIndex) const
QStringList normalizedTranslations(const MessageItem &m) const
MessageItem * findMessage(const QString &sourcetext, const QString &comment) const
int unfinishedDangerCount() const
int nonobsoleteCount() const
MessageItem * findMessageById(const QString &msgid) const
TranslationType translationType() const
MessageItem * messageItem(int i) const
int finishedDangerCount() const
int finishedCount() const
void setTranslation(const QString &translation)
MessageItem(const TranslatorMessage &message)
bool compare(const QString &findText, bool matchSubstring, Qt::CaseSensitivity cs) const
const TranslatorMessage & message() const
void setDanger(bool danger)
QString pluralText() const
void setTranslations(const QStringList &translations)
QString translation() const
QStringList translations() const
bool isUnfinished() const
TranslatorMessage::Type type() const
void setType(TranslatorMessage::Type type)
QModelIndex modelIndex(const MultiDataIndex &index)
QModelIndex parent(const QModelIndex &index) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Returns the index of the item in the model specified by the given row, column and parent index.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Returns the data stored under the given role for the item referred to by the index.
MessageModel(TranslationType translationType, QObject *parent, MultiDataModel *data)
MultiDataIndex dataIndex(const QModelIndex &index, int model) const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of rows under the given parent.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of columns for the children of the given parent.
MultiDataIndex(TranslationType type=TEXTBASED, int model=-1, int group=-1, int message=-1)
MultiDataModelIterator(TranslationType type, MultiDataModel *model=0, int modelNo=-1, int groupNo=0, int messageNo=0)
MessageItem * current() const
int findGroupIndex(const QString &group, TranslationType type) const
QStringList srcFileNames(bool pretty=false) const
void append(DataModel *dm, bool readWrite)
MessageItem * messageItem(const MultiDataIndex &index, int model) const
void setModified(int model, bool dirty)
MultiGroupItem * multiGroupItem(const MultiDataIndex &index) const
MultiGroupItem * multiGroupItem(int idx, TranslationType type) const
MultiGroupItem * findGroup(const QString &group, TranslationType type) const
QString condensedSrcFileNames(bool pretty=false) const
QBrush brushForModel(int model) const
int isFileLoaded(const QString &name) const
void setTranslation(const MultiDataIndex &index, const QString &translation)
void setDanger(const MultiDataIndex &index, bool danger)
bool isModelWritable(int model) const
MultiMessageItem * multiMessageItem(const MultiDataIndex &index) const
void groupDataChanged(const MultiDataIndex &index)
MessageItem * messageItem(const MultiDataIndex &index) const
void setFinished(const MultiDataIndex &index, bool finished)
void messageDataChanged(const MultiDataIndex &index)
bool isWellMergeable(const DataModel *dm) const
void moveModel(int oldPos, int newPos)
LocationsType locationsType() const
void setExtras(const ExtraData &extras)
void append(const TranslatorMessage &msg)
void setLocationsType(LocationsType lt)
Duplicates resolveDuplicates()
void normalizeTranslations(ConversionData &cd)
const ExtraData & extras() const
static const QColor lightPaletteColors[7]
static QString resolveNcr(QStringView str)
static int calcMergeScore(const DataModel *one, const DataModel *two)
static QString showNcr(const QString &str)
static const QColor darkPaletteColors[7]
static QString adjustNcrVisibility(const QString &str, bool ncrMode)
MultiGroupItem(int oldCount, GroupItem *groupItem, bool writable)
MultiMessageItem * multiMessageItem(int msgIdx) const
GroupItem * groupItem(int model) const
int findMessage(const QString &sourcetext, const QString &comment) const
int getNumEditable() const
int findMessageById(const QString &id) const
int getNumFinished() const
MessageItem * messageItem(int model, int msgIdx) const
int firstNonobsoleteMessageIndex(int msgIdx) const
int countUnfinished() const
int countEditable() const
bool isUnfinished() const
int unfinishedMsgNoDanger
int translatedMsgNoDanger