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(), 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 const auto &translations = mi->translations();
561 for (
const QString &trnsl : translations) {
562 doCharCounting(trnsl, stats.wordsFinished, stats.charsFinished, stats.charsSpacesFinished);
563 hasDanger |= mi->danger();
570 bool hasDanger =
false;
571 const auto &translations = mi->translations();
572 for (
const QString &trnsl : translations) {
573 doCharCounting(trnsl, stats.wordsUnfinished, stats.charsUnfinished, stats.charsSpacesUnfinished);
574 hasDanger |= mi->danger();
591 emit statsChanged(stats);
596 if (m_modified == isModified)
598 m_modified = isModified;
599 emit modifiedChanged();
602QString
DataModel::prettifyPlainFileName(
const QString &fn)
604 static QString workdir = QDir::currentPath() + u'/';
606 return QDir::toNativeSeparators(fn.startsWith(workdir) ? fn.mid(workdir.size()) : fn);
611 if (fn.startsWith(u'='))
612 return u'=' + prettifyPlainFileName(fn.mid(1));
614 return prettifyPlainFileName(fn);
618
619
620
621
631 const qsizetype size =
632 isIdBased() ? m_model->m_labelList.size() : m_model->m_contextList.size();
640 : m_model->m_contextList.at(
m_group).messageCount();
653
654
655
656
663 QList<MessageItem *> mList;
664 QList<MessageItem *> eList;
669 m_multiMessageList.append(MultiMessageItem(m));
671 for (
int i = 0; i < oldCount; ++i) {
672 m_messageLists.append(eList);
673 m_writableMessageLists.append(0);
674 m_groupList.append(0);
676 m_messageLists.append(mList);
677 m_writableMessageLists.append(writable ? &m_messageLists.last() : 0);
678 m_groupList.append(groupItem);
683 QList<MessageItem *> eList;
686 m_messageLists.append(eList);
687 m_writableMessageLists.append(0);
688 m_groupList.append(0);
694 m_writableMessageLists.last() = &m_messageLists.last();
695 m_groupList.last() = g;
701 m_groupList.insert(newPos, m_groupList[oldPos]);
702 m_messageLists.insert(newPos, m_messageLists[oldPos]);
703 m_writableMessageLists.insert(newPos, m_writableMessageLists[oldPos]);
704 removeModel(oldPos < newPos ? oldPos : oldPos + 1);
709 m_groupList.removeAt(pos);
710 m_messageLists.removeAt(pos);
711 m_writableMessageLists.removeAt(pos);
716 m_messageLists.last()[pos] = m;
719void MultiGroupItem::appendMessageItems(
const QList<MessageItem *> &m)
721 QList<MessageItem *> nullItems = m;
722 for (
int i = 0; i < nullItems.size(); ++i)
724 for (
int i = 0; i < m_messageLists.size() - 1; ++i)
725 m_messageLists[i] += nullItems;
726 m_messageLists.last() += m;
727 for (MessageItem *mi : m)
728 m_multiMessageList.append(MultiMessageItem(mi));
733 for (
int i = 0; i < m_messageLists.size(); ++i)
734 m_messageLists[i].removeAt(pos);
735 m_multiMessageList.removeAt(pos);
740 for (
int i = 0; i < m_messageLists.size(); ++i)
741 if (m_messageLists[i][msgIdx] && !m_messageLists[i][msgIdx]->isObsolete())
750 if (m->text() == sourcetext && m->comment() == comment)
767
768
769
770
773 QColor(210, 235, 250),
774 QColor(210, 250, 220),
775 QColor(250, 240, 210),
776 QColor(210, 250, 250),
777 QColor(250, 230, 200),
778 QColor(250, 210, 210),
779 QColor(235, 210, 250),
801 m_bitmap = QBitmap(8, 8);
803 QPainter p(&m_bitmap);
804 for (
int j = 0; j < 8; ++j)
805 for (
int k = 0; k < 8; ++k)
812 qDeleteAll(m_dataModels);
817 QBrush brush(m_colors[model % 7]);
819 brush.setTexture(m_bitmap);
825 m_colors = isDarkMode() ? darkPaletteColors : lightPaletteColors;
835 auto countInBothNew = [dm, &inBothNew,
this](
int count,
TranslationType type) {
836 for (
int i = 0; i < count; ++i) {
847 if ((type ==
TEXTBASED && mgi->findMessage(m->text(), m->comment()) >= 0)
848 || (type ==
IDBASED && mgi->findMessageById(m->id()) >= 0))
861 auto countInBothOld = [
this, dm, &inBothOld](
int count,
TranslationType type) {
862 for (
int k = 0; k < count; ++k) {
864 if (
GroupItem *g = dm->findGroup(mgi->group(), type)) {
867 if ((type ==
TEXTBASED && g->findMessage(m->text(), m->comment()))
868 || (type ==
IDBASED && g->findMessageById(m->id())))
880 return newRatio + oldRatio > 90;
886 m_dataModels.append(dm);
889 QList<MultiGroupItem> &multiGroupList) {
891 msgModel->beginInsertColumns(QModelIndex(), insCol, insCol);
892 for (
int j = 0; j < count; ++j) {
893 msgModel->beginInsertColumns(msgModel->createIndex(j, 0), insCol, insCol);
894 multiGroupList[j].appendEmptyModel();
895 msgModel->endInsertColumns();
897 msgModel->endInsertColumns();
899 int appendedGroups = 0;
900 for (
int i = 0; i < count; ++i) {
902 int gidx = findGroupIndex(g->group(), type);
905 mgi->assignLastModel(g, readWrite);
906 QList<MessageItem *> appendItems;
910 int msgIdx = type ==
IDBASED ? mgi->findMessageById(m->id())
911 : mgi->findMessage(m->text(), m->comment());
914 mgi->putMessageItem(msgIdx, m);
918 if (!appendItems.isEmpty()) {
920 msgModel->beginInsertRows(msgModel->createIndex(gidx, 0), msgCnt,
921 msgCnt + appendItems.size() - 1);
922 mgi->appendMessageItems(appendItems);
923 msgModel->endInsertRows();
924 m_numMessages += appendItems.size();
932 if (appendedGroups) {
936 msgModel->beginInsertRows(QModelIndex(), groupCount - appendedGroups, groupCount - 1);
937 msgModel->endInsertRows();
941 appendGroups(
TEXTBASED, m_textBasedMsgModel, m_multiContextList);
942 appendGroups(
IDBASED, m_idBasedMsgModel, m_multiLabelList);
947 this, &MultiDataModel::onModifiedChanged);
950 connect(dm, &DataModel::statsChanged,
952 emit modelAppended();
957 if (m_dataModels.size() == 1) {
960 int delCol = model + 1;
961 auto removeModel = [delCol, model](
auto *msgModel,
auto &list) {
962 msgModel->beginRemoveColumns(QModelIndex(), delCol, delCol);
963 for (
int i = list.size(); --i >= 0;) {
964 msgModel->beginRemoveColumns(msgModel->createIndex(i, 0), delCol, delCol);
965 list[i].removeModel(model);
966 msgModel->endRemoveColumns();
968 msgModel->endRemoveColumns();
972 removeModel(m_idBasedMsgModel, m_multiLabelList);
973 removeModel(m_textBasedMsgModel, m_multiContextList);
974 delete m_dataModels.takeAt(model);
975 emit modelDeleted(model);
977 auto removeMessages = [
this](
auto *msgModel,
auto &list) {
978 for (
int i = list.size(); --i >= 0;) {
980 QModelIndex idx = msgModel->createIndex(i, 0);
981 for (
int j = mi.messageCount(); --j >= 0;)
982 if (mi.multiMessageItem(j)->isEmpty()) {
983 msgModel->beginRemoveRows(idx, j, j);
984 mi.removeMultiMessageItem(j);
985 msgModel->endRemoveRows();
988 if (!mi.messageCount()) {
989 msgModel->beginRemoveRows(QModelIndex(), i, i);
991 msgModel->endRemoveRows();
996 removeMessages(m_idBasedMsgModel, m_multiLabelList);
997 removeMessages(m_textBasedMsgModel, m_multiContextList);
1004 m_idBasedMsgModel->beginResetModel();
1005 m_textBasedMsgModel->beginResetModel();
1009 qDeleteAll(m_dataModels);
1010 m_dataModels.clear();
1011 m_multiContextList.clear();
1012 m_multiLabelList.clear();
1013 m_textBasedMsgModel->endResetModel();
1014 m_idBasedMsgModel->endResetModel();
1015 emit allModelsDeleted();
1016 onModifiedChanged();
1022 int delPos = oldPos < newPos ? oldPos : oldPos + 1;
1023 m_dataModels.insert(newPos, m_dataModels[oldPos]);
1024 m_dataModels.removeAt(delPos);
1025 for (
int i = 0; i < m_multiContextList.size(); ++i)
1026 m_multiContextList[i].moveModel(oldPos, newPos);
1027 for (
int i = 0; i < m_multiLabelList.size(); ++i)
1028 m_multiLabelList[i].moveModel(oldPos, newPos);
1035 for (
const QString &name : names)
1036 out << DataModel::prettifyFileName(name);
1042 if (names.isEmpty())
1045 if (names.size() < 2)
1046 return names.first();
1048 QString prefix = names.first();
1049 if (prefix.startsWith(u'='))
1050 prefix.remove(0, 1);
1051 QString suffix = prefix;
1052 for (
int i = 1; i < names.size(); ++i) {
1053 QString fn = names[i];
1054 if (fn.startsWith(u'='))
1056 for (
int j = 0; j < prefix.size(); ++j)
1057 if (fn[j] != prefix[j]) {
1058 if (j < prefix.size()) {
1059 while (j > 0 && prefix[j - 1].isLetterOrNumber())
1065 int fnl = fn.size() - 1;
1066 int sxl = suffix.size() - 1;
1067 for (
int k = 0; k <= sxl; ++k)
1068 if (fn[fnl - k] != suffix[sxl - k]) {
1070 while (k > 0 && suffix[sxl - k + 1].isLetterOrNumber())
1072 if (prefix.size() + k > fnl)
1074 suffix.remove(0, sxl - k + 1);
1079 QString ret = prefix + u'{';
1080 int pxl = prefix.size();
1081 int sxl = suffix.size();
1082 for (
int j = 0; j < names.size(); ++j) {
1086 QString fn = names[j];
1087 if (fn.startsWith(u'=')) {
1091 ret += fn.mid(off, fn.size() - sxl - off);
1093 ret += u'}' + suffix;
1100 for (DataModel *dm : m_dataModels)
1101 names << (dm->isWritable() ? QString() : QString::fromLatin1(
"=")) + dm->srcFileName(pretty);
1107 return condenseFileNames(srcFileNames(pretty));
1117 for (
const DataModel *mdl : m_dataModels)
1118 if (mdl->isModified())
1126 if (modified != m_modified) {
1127 emit modifiedChanged(modified);
1128 m_modified = modified;
1135 while (sender() != m_dataModels[i])
1137 emit languageChanged(i);
1147 for (
int i = 0; i < m_dataModels.size(); ++i)
1148 if (m_dataModels[i]->srcFileName() == name)
1155 const auto &list = type == IDBASED ? m_multiLabelList : m_multiContextList;
1156 for (
int i = 0; i < list.size(); ++i) {
1158 if (mg.group() == group)
1166 const auto &list = type == IDBASED ? m_multiLabelList : m_multiContextList;
1167 for (
int i = 0; i < list.size(); ++i) {
1169 if (mgi.group() == group)
1177 const auto &list = index.isIdBased() ? m_multiLabelList : m_multiContextList;
1190 Q_ASSERT(index
.group() < groupCount);
1197 if (translation == m->translation())
1199 m->setTranslation(translation);
1201 emit translationChanged(index);
1207 if (translations == m->translations())
1209 m->setTranslations(translations);
1211 emit translationChanged(index);
1223 mm->decrementUnfinishedCount();
1225 incrementFinishedCount();
1226 mgi->incrementFinishedCount();
1227 emit multiGroupDataChanged(index);
1229 gi->incrementFinishedCount();
1231 gi->incrementFinishedDangerCount();
1232 gi->decrementUnfinishedDangerCount();
1234 emit groupDataChanged(index);
1236 emit groupDataChanged(index);
1238 emit messageDataChanged(index);
1242 mm->incrementUnfinishedCount();
1244 decrementFinishedCount();
1245 mgi->decrementFinishedCount();
1246 emit multiGroupDataChanged(index);
1248 gi->decrementFinishedCount();
1250 gi->decrementFinishedDangerCount();
1251 gi->incrementUnfinishedDangerCount();
1254 emit groupDataChanged(index);
1256 emit groupDataChanged(index);
1258 emit messageDataChanged(index);
1269 gi->incrementFinishedDangerCount();
1271 emit groupDataChanged(index);
1273 gi->incrementUnfinishedDangerCount();
1275 emit groupDataChanged(index);
1277 emit messageDataChanged(index);
1281 gi->decrementFinishedDangerCount();
1283 emit groupDataChanged(index);
1285 gi->decrementUnfinishedDangerCount();
1287 emit groupDataChanged(index);
1289 emit messageDataChanged(index);
1296 auto updateCount = [model, writable,
this](
auto &mg) {
1297 for (
int j = 0; j < mg.messageCount(); ++j)
1300 mm->incrementNonnullCount();
1304 mg.incrementEditableCount();
1305 incrementEditableCount();
1307 mg.incrementFinishedCount();
1308 incrementFinishedCount();
1310 mm->incrementUnfinishedCount();
1314 mg.decrementFinishedCount();
1315 decrementFinishedCount();
1317 mm->incrementUnfinishedCount();
1319 mm->incrementEditableCount();
1321 mg.incrementNonobsoleteCount();
1322 mm->incrementNonobsoleteCount();
1326 for (
auto &mg : m_multiContextList)
1328 for (
auto &mg : m_multiLabelList)
1332void MultiDataModel::updateCountsOnRemove(
int model,
bool writable)
1334 auto updateCount = [model, writable,
this](
auto &mg) {
1335 for (
int j = 0; j < mg.messageCount(); ++j)
1338 mm->decrementNonnullCount();
1340 mm->decrementNonobsoleteCount();
1341 mg.decrementNonobsoleteCount();
1343 mm->decrementEditableCount();
1345 mg.decrementEditableCount();
1346 decrementEditableCount();
1348 mg.decrementFinishedCount();
1349 decrementFinishedCount();
1351 mm->decrementUnfinishedCount();
1354 mm->decrementUnfinishedCount();
1356 mg.incrementFinishedCount();
1357 incrementFinishedCount();
1364 for (
auto &mg : m_multiContextList)
1366 for (
auto &mg : m_multiLabelList)
1371
1372
1373
1374
1377 int model,
int group,
int message)
1387 ? m_dataModel->m_multiLabelList.at(
m_group).messageCount()
1388 : m_dataModel->m_multiContextList.at(
m_group).messageCount();
1397 const qsizetype size =
isIdBased() ? m_dataModel->m_multiLabelList.size()
1398 : m_dataModel->m_multiContextList.size();
1408
1409
1410
1411
1416 if (translationType ==
IDBASED)
1417 data->m_idBasedMsgModel =
this;
1419 data->m_textBasedMsgModel =
this;
1421 &MessageModel::multiGroupItemChanged);
1428 if (!parent.isValid())
1429 return createIndex(row, column);
1430 if (!parent.internalId())
1431 return createIndex(row, column, parent.row() + 1);
1432 return QModelIndex();
1437 if (index.internalId())
1438 return createIndex(index.internalId() - 1, 0);
1439 return QModelIndex();
1447 emit dataChanged(idx, idx);
1455 emit dataChanged(idx, idx);
1463 emit dataChanged(idx, idx);
1475 if (!parent.isValid())
1478 if (!parent.internalId())
1479 return m_data->multiGroupItem(parent.row(), m_translationType)->messageCount();
1490 static QVariant pxOn;
1491 static QVariant pxOff;
1492 static QVariant pxObsolete;
1493 static QVariant pxDanger;
1494 static QVariant pxWarning;
1495 static QVariant pxEmpty;
1497 static Qt::ColorScheme mode = Qt::ColorScheme::Unknown;
1501 (dark && mode != Qt::ColorScheme::Dark) || (!dark && mode != Qt::ColorScheme::Light)) {
1502 pxOn = createMarkIcon(TranslationMarks::OnMark, dark);
1503 pxOff = createMarkIcon(TranslationMarks::OffMark, dark);
1504 pxObsolete = createMarkIcon(TranslationMarks::ObsoleteMark, dark);
1505 pxDanger = createMarkIcon(TranslationMarks::DangerMark, dark);
1506 pxWarning = createMarkIcon(TranslationMarks::WarningMark, dark);
1507 pxEmpty = createMarkIcon(TranslationMarks::EmptyMark, dark);
1508 mode = dark ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
1511 int row = index.row();
1512 int column = index.column() - 1;
1519 if (role == Qt::ToolTipRole && column < numLangs) {
1520 return tr(
"Completion status for %1").arg(m_data
->model(column
)->localizedLanguage());
1521 }
else if (index.internalId()) {
1523 int crow = index.internalId() - 1;
1528 if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column == numLangs)) {
1529 switch (column - numLangs) {
1532 if (m_translationType ==
IDBASED)
1535 return text.simplified();
1537 return tr(
"<context comment>");
1540 if (m_translationType ==
IDBASED)
1547 else if (role == Qt::DecorationRole && column < numLangs) {
1551 if (msgItem->translation().isEmpty())
1566 else if (role == SortRole) {
1567 switch (column - numLangs) {
1574 int rslt = !msgItem->translation().isEmpty();
1585 }
else if (role == Qt::ForegroundRole && column > 0
1586 && mgi->multiMessageItem(row)->isObsolete()) {
1587 return QBrush(Qt::darkGray);
1588 }
else if (role == Qt::ForegroundRole && column == numLangs
1589 && mgi->multiMessageItem(row)->text().isEmpty()) {
1590 return QBrush(QColor(0, 0xa0, 0xa0));
1591 }
else if (role == Qt::BackgroundRole) {
1592 if (column < numLangs && numLangs != 1)
1593 return m_data->brushForModel(column);
1597 const qsizetype groupCount =
1599 if (row >= groupCount || !index.isValid())
1603 if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
1604 switch (column - numLangs) {
1607 const QString groupName = mgi->group().simplified();
1608 if (m_translationType ==
IDBASED and groupName.isEmpty()) {
1609 return tr(
"<unnamed label>");
1614 if (role == Qt::ToolTipRole) {
1615 return tr(
"%n unfinished message(s) left.", 0,
1618 return QString::asprintf(
"%d/%d", mgi->getNumFinished(), mgi->getNumEditable());
1624 else if (role == Qt::FontRole && column == m_data->modelCount()) {
1626 boldFont.setBold(
true);
1629 else if (role == Qt::DecorationRole && column < numLangs) {
1639 else if (role == SortRole) {
1640 switch (column - numLangs) {
1642 return mgi->group().simplified();
1650 int rslt = percent * (((1 << 28) - 1) / 100) + totalItems;
1665 }
else if (role == Qt::ForegroundRole && column >= numLangs && mgi->isObsolete()) {
1666 return QBrush(Qt::darkGray);
1667 }
else if (role == Qt::ForegroundRole && column == numLangs
1668 && m_translationType == IDBASED) {
1669 return QBrush(QColor(0, 0xa0, 0xa0));
1670 }
else if (role == Qt::BackgroundRole) {
1671 if (column < numLangs && numLangs != 1) {
1672 QBrush brush = m_data->brushForModel(column);
1674 brush.setColor(brush.color().darker(108));
1685 Q_ASSERT(index.isValid());
1686 Q_ASSERT(index.internalId());
1687 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 setTranslations(const MultiDataIndex &index, const QStringList &translations)
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)
void statsChanged(const StatisticalData &newStats)
void multiGroupDataChanged(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